/** * Tests for getEventsForDates — the admin dashboard "Games to Score" query. */ import { describe, it, expect, vi, beforeEach } from "vitest"; // ── DB mock ────────────────────────────────────────────────────────────────── // selectDistinct returns a fluent chain; findMany is used in step 2. const mockFindMany = vi.fn(); function makeSelectChain(rows: { id: string }[]) { const chain = { from: vi.fn().mockReturnThis(), leftJoin: vi.fn().mockReturnThis(), where: vi.fn().mockResolvedValue(rows), }; return chain; } const mockSelectDistinct = vi.fn(); const mockDb = { query: { scoringEvents: { findMany: mockFindMany }, }, selectDistinct: mockSelectDistinct, }; vi.mock("~/database/context", () => ({ database: () => mockDb, })); vi.mock("~/database/schema", () => ({ scoringEvents: { id: "se.id", eventDate: "se.event_date", eventType: "se.event_type", eventStartsAt: "se.event_starts_at", createdAt: "se.created_at", }, playoffMatches: { scoringEventId: "pm.scoring_event_id", id: "pm.id", }, playoffMatchGames: { playoffMatchId: "pmg.playoff_match_id", scheduledAt: "pmg.scheduled_at", }, })); vi.mock("drizzle-orm", () => ({ eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }), and: (...args: unknown[]) => ({ type: "and", args }), asc: (col: unknown) => ({ type: "asc", col }), desc: (col: unknown) => ({ type: "desc", col }), gte: (col: unknown, val: unknown) => ({ type: "gte", col, val }), lte: (col: unknown, val: unknown) => ({ type: "lte", col, val }), or: (...args: unknown[]) => ({ type: "or", args }), inArray: (col: unknown, arr: unknown) => ({ type: "inArray", col, arr }), isNotNull: (col: unknown) => ({ type: "isNotNull", col }), })); import { getEventsForDates } from "../scoring-event"; // ── Helpers ────────────────────────────────────────────────────────────────── function makeEventRow(overrides: Partial<{ id: string; name: string; eventDate: string | null; eventStartsAt: Date | null; eventType: string; isComplete: boolean; completedAt: Date | null; sportsSeasonId: string; sportsSeason: { name: string; sport: { name: string; slug: string | null } }; }> = {}) { return { id: overrides.id ?? "event-1", name: overrides.name ?? "Test Event", eventDate: overrides.eventDate ?? "2026-03-20", eventStartsAt: overrides.eventStartsAt ?? null, eventType: overrides.eventType ?? "playoff_game", isComplete: overrides.isComplete ?? false, completedAt: overrides.completedAt ?? null, sportsSeasonId: overrides.sportsSeasonId ?? "season-1", sportsSeason: overrides.sportsSeason ?? { name: "2026 NFL Playoffs", sport: { name: "NFL", slug: "nfl" }, }, }; } // ── Tests ───────────────────────────────────────────────────────────────────── describe("getEventsForDates", () => { beforeEach(() => { vi.clearAllMocks(); mockSelectDistinct.mockReturnValue(makeSelectChain([])); }); it("returns empty array and skips DB when no dates given", async () => { const result = await getEventsForDates([]); expect(result).toEqual([]); expect(mockSelectDistinct).not.toHaveBeenCalled(); expect(mockFindMany).not.toHaveBeenCalled(); }); it("returns empty array when selectDistinct finds no matching event IDs", async () => { mockSelectDistinct.mockReturnValue(makeSelectChain([])); const result = await getEventsForDates(["2026-03-20"]); expect(result).toEqual([]); expect(mockFindMany).not.toHaveBeenCalled(); }); it("maps database rows to DashboardScoringEvent shape", async () => { mockSelectDistinct.mockReturnValue(makeSelectChain([{ id: "e1" }])); mockFindMany.mockResolvedValueOnce([ makeEventRow({ id: "e1", name: "Conference Finals", eventDate: "2026-03-20", eventType: "playoff_game", isComplete: false, sportsSeasonId: "ss-1", sportsSeason: { name: "2026 NBA Playoffs", sport: { name: "NBA", slug: "nba" }, }, }), ]); const result = await getEventsForDates(["2026-03-20"]); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ id: "e1", name: "Conference Finals", eventDate: "2026-03-20", eventType: "playoff_game", isComplete: false, sportsSeasonId: "ss-1", sportsSeasonName: "2026 NBA Playoffs", sportName: "NBA", sportSlug: "nba", }); }); it("returns events for multiple dates", async () => { mockSelectDistinct.mockReturnValue( makeSelectChain([{ id: "e1" }, { id: "e2" }]) ); mockFindMany.mockResolvedValueOnce([ makeEventRow({ id: "e1", eventDate: "2026-03-20" }), makeEventRow({ id: "e2", eventDate: "2026-03-21" }), ]); const result = await getEventsForDates(["2026-03-20", "2026-03-21"]); expect(result).toHaveLength(2); expect(result.map((e) => e.id)).toEqual(["e1", "e2"]); }); it("includes completed events with isComplete: true", async () => { mockSelectDistinct.mockReturnValue(makeSelectChain([{ id: "e-done" }])); mockFindMany.mockResolvedValueOnce([ makeEventRow({ id: "e-done", isComplete: true, completedAt: new Date("2026-03-20T18:00:00Z"), }), ]); const result = await getEventsForDates(["2026-03-20"]); expect(result[0].isComplete).toBe(true); expect(result[0].completedAt).toBeInstanceOf(Date); }); it("includes eventStartsAt when present", async () => { const starts = new Date("2026-03-20T19:30:00Z"); mockSelectDistinct.mockReturnValue(makeSelectChain([{ id: "e1" }])); mockFindMany.mockResolvedValueOnce([ makeEventRow({ id: "e1", eventStartsAt: starts }), ]); const result = await getEventsForDates(["2026-03-20"]); expect(result[0].eventStartsAt).toEqual(starts); }); it("uses two DB calls: selectDistinct then findMany", async () => { mockSelectDistinct.mockReturnValue(makeSelectChain([{ id: "e1" }])); mockFindMany.mockResolvedValueOnce([makeEventRow({ id: "e1" })]); await getEventsForDates(["2026-03-20"]); expect(mockSelectDistinct).toHaveBeenCalledOnce(); expect(mockFindMany).toHaveBeenCalledOnce(); }); it("passes event IDs from step 1 into findMany in step 2", async () => { mockSelectDistinct.mockReturnValue( makeSelectChain([{ id: "abc" }, { id: "def" }]) ); mockFindMany.mockResolvedValueOnce([]); await getEventsForDates(["2026-03-20"]); const findManyArgs = mockFindMany.mock.calls[0][0]; const whereStr = JSON.stringify(findManyArgs.where); expect(whereStr).toContain("abc"); expect(whereStr).toContain("def"); }); it("uses leftJoin so bracket events without playoffMatchGames still qualify via eventDate", async () => { const chain = makeSelectChain([]); mockSelectDistinct.mockReturnValue(chain); await getEventsForDates(["2026-03-20"]); expect(chain.leftJoin).toHaveBeenCalled(); }); it("includes both eventDate and scheduledAt timestamp bounds in the where clause", async () => { const chain = makeSelectChain([]); mockSelectDistinct.mockReturnValue(chain); await getEventsForDates(["2026-03-20", "2026-03-21"]); // The where clause is the first (and only) arg to chain.where const whereArg = JSON.stringify((chain.where as ReturnType).mock.calls[0][0]); // eventDate inArray check expect(whereArg).toContain("2026-03-20"); expect(whereArg).toContain("2026-03-21"); // timestamp bounds for scheduledAt expect(whereArg).toContain("T00:00:00.000Z"); expect(whereArg).toContain("T23:59:59.999Z"); }); it("excludes schedule_event type via the where clause", async () => { const chain = makeSelectChain([]); mockSelectDistinct.mockReturnValue(chain); await getEventsForDates(["2026-03-20"]); const whereArg = JSON.stringify((chain.where as ReturnType).mock.calls[0][0]); expect(whereArg).toContain("playoff_game"); expect(whereArg).toContain("major_tournament"); expect(whereArg).toContain("final_standings"); expect(whereArg).not.toContain("schedule_event"); }); });