import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("~/lib/logger", () => ({ logger: { error: vi.fn() }, })); // ── DB mock helpers ──────────────────────────────────────────────────────── function makeInsertChain() { const onConflictDoUpdate = vi.fn().mockResolvedValue(undefined); const values = vi.fn().mockReturnValue({ onConflictDoUpdate }); const insert = vi.fn().mockReturnValue({ values }); return { insert, values, onConflictDoUpdate }; } interface MakeDbOpts { sportsSeason?: { sport: { name: string } } | null; seasonSports?: { seasonId: string }[]; picks?: { teamId: string; seasonId: string }[]; seasons?: { id: string; pointsFor1st: number; pointsFor2nd: number; pointsFor3rd: number; pointsFor4th: number; pointsFor5th: number; pointsFor6th: number; pointsFor7th: number; pointsFor8th: number; }[]; scoreEventRows?: { id: string; teamId: string; teamName?: string; scoringEventId: string | null; scoringEventName: string | null; sportName: string | null; pointsDelta: string; occurredAt: Date; participantIds: string[]; team: { id: string; name: string }; }[]; participantRows?: { id: string; name: string }[]; } function makeDb(opts: MakeDbOpts = {}) { const { sportsSeason = null, seasonSports = [], picks = [], seasons = [], scoreEventRows = [], participantRows = [], } = opts; const chain = makeInsertChain(); return { db: { insert: chain.insert, query: { sportsSeasons: { findFirst: vi.fn().mockResolvedValue(sportsSeason), }, seasonSports: { findMany: vi.fn().mockResolvedValue(seasonSports), }, draftPicks: { findMany: vi.fn().mockResolvedValue(picks), }, seasons: { findMany: vi.fn().mockResolvedValue(seasons), }, teamScoreEvents: { findMany: vi.fn().mockResolvedValue(scoreEventRows), }, participants: { findMany: vi.fn().mockResolvedValue(participantRows), }, }, } as any, chain, }; } import { recordTeamScoreEvent, recordMatchScoreEvents, getRecentTeamScoreEvents } from "../team-score-events"; const BASE_PARAMS = { teamId: "team-1", seasonId: "season-1", scoringEventId: "event-1", scoringEventName: "Round of 16", sportName: "Darts", participantIds: ["p-1"], pointsDelta: 10, }; describe("recordTeamScoreEvent", () => { beforeEach(() => vi.clearAllMocks()); it("uses matchId-based conflict target when matchId provided", async () => { const { db, chain } = makeDb(); await recordTeamScoreEvent({ ...BASE_PARAMS, matchId: "match-1" }, db); expect(chain.insert).toHaveBeenCalled(); expect(chain.values).toHaveBeenCalledWith( expect.objectContaining({ matchId: "match-1", pointsDelta: "10" }) ); const conflictArg = chain.onConflictDoUpdate.mock.calls[0][0]; expect(conflictArg.target).toHaveLength(3); // targetWhere should reference matchId IS NOT NULL expect(conflictArg.targetWhere).toBeDefined(); expect(conflictArg.set).toMatchObject({ pointsDelta: "10", participantIds: ["p-1"] }); }); it("uses scoringEventId-based conflict target when no matchId", async () => { const { db, chain } = makeDb(); await recordTeamScoreEvent(BASE_PARAMS, db); expect(chain.insert).toHaveBeenCalled(); expect(chain.values).toHaveBeenCalledWith( expect.objectContaining({ matchId: null }) ); const conflictArg = chain.onConflictDoUpdate.mock.calls[0][0]; expect(conflictArg.target).toHaveLength(3); expect(conflictArg.targetWhere).toBeDefined(); }); it("stores pointsDelta as string", async () => { const { db, chain } = makeDb(); await recordTeamScoreEvent({ ...BASE_PARAMS, pointsDelta: 42 }, db); expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ pointsDelta: "42" })); }); it("defaults matchId to null when not provided", async () => { const { db, chain } = makeDb(); await recordTeamScoreEvent(BASE_PARAMS, db); expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ matchId: null })); }); }); describe("recordMatchScoreEvents", () => { beforeEach(() => vi.clearAllMocks()); const BASE_EVENT = { participantId: "p-1", sportsSeasonId: "ss-1", oldFloor: 0, newFloor: 2, bracketTemplateId: null as string | null, matchId: "match-1", eventId: "event-1", eventName: "Semifinals", }; const SEASON_ROW = { id: "season-1", pointsFor1st: 100, pointsFor2nd: 75, pointsFor3rd: 50, pointsFor4th: 40, pointsFor5th: 30, pointsFor6th: 20, pointsFor7th: 10, pointsFor8th: 5, }; it("returns early when no fantasy seasons use this sports season", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Darts" } }, seasonSports: [], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.insert).not.toHaveBeenCalled(); }); it("returns early when no draft picks found for participant", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Darts" } }, seasonSports: [{ seasonId: "season-1" }], picks: [], seasons: [SEASON_ROW], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.insert).not.toHaveBeenCalled(); }); it("skips seasons where team did not draft the participant", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Darts" } }, seasonSports: [{ seasonId: "season-1" }, { seasonId: "season-2" }], picks: [{ teamId: "team-1", seasonId: "season-1" }], // only season-1 has pick seasons: [SEASON_ROW, { ...SEASON_ROW, id: "season-2" }], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.insert).toHaveBeenCalledTimes(1); expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ seasonId: "season-1" })); }); it("skips seasons where point delta is zero or negative", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Darts" } }, seasonSports: [{ seasonId: "season-1" }], picks: [{ teamId: "team-1", seasonId: "season-1" }], seasons: [{ ...SEASON_ROW, pointsFor1st: 0, pointsFor2nd: 0 }], // all zeros → delta=0 }); // With all-zero scoring rules, positions map to 0 points → delta = 0 → skip await recordMatchScoreEvents({ ...BASE_EVENT, oldFloor: 1, newFloor: 2 }, db); expect(chain.insert).not.toHaveBeenCalled(); }); it("records score event with correct teamId, seasonId, and matchId", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Darts" } }, seasonSports: [{ seasonId: "season-1" }], picks: [{ teamId: "team-1", seasonId: "season-1" }], seasons: [SEASON_ROW], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.values).toHaveBeenCalledWith( expect.objectContaining({ teamId: "team-1", seasonId: "season-1", matchId: "match-1", sportName: "Darts", participantIds: ["p-1"], }) ); }); it("uses sport name from sportsSeasons query", async () => { const { db, chain } = makeDb({ sportsSeason: { sport: { name: "Snooker" } }, seasonSports: [{ seasonId: "season-1" }], picks: [{ teamId: "team-1", seasonId: "season-1" }], seasons: [SEASON_ROW], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.values).toHaveBeenCalledWith( expect.objectContaining({ sportName: "Snooker" }) ); }); it("handles null sportsSeason gracefully (sportName = null)", async () => { const { db, chain } = makeDb({ sportsSeason: null, seasonSports: [{ seasonId: "season-1" }], picks: [{ teamId: "team-1", seasonId: "season-1" }], seasons: [SEASON_ROW], }); await recordMatchScoreEvents(BASE_EVENT, db); expect(chain.values).toHaveBeenCalledWith( expect.objectContaining({ sportName: null }) ); }); }); describe("getRecentTeamScoreEvents", () => { beforeEach(() => vi.clearAllMocks()); it("returns empty array when no rows found", async () => { const { db } = makeDb({ scoreEventRows: [] }); const result = await getRecentTeamScoreEvents("season-1", 10, db); expect(result).toEqual([]); }); it("does not query participants when rows array is empty", async () => { const { db } = makeDb({ scoreEventRows: [] }); await getRecentTeamScoreEvents("season-1", 10, db); expect(db.query.participants.findMany).not.toHaveBeenCalled(); }); it("returns mapped entries with resolved participant names", async () => { const occurredAt = new Date("2024-01-15"); const { db } = makeDb({ scoreEventRows: [ { id: "tse-1", teamId: "team-1", scoringEventId: "event-1", scoringEventName: "Semifinals", sportName: "Darts", pointsDelta: "75", occurredAt, participantIds: ["p-1", "p-2"], team: { id: "team-1", name: "Team Alpha" }, }, ], participantRows: [ { id: "p-1", name: "Luke Littler" }, { id: "p-2", name: "Michael van Gerwen" }, ], }); const result = await getRecentTeamScoreEvents("season-1", 10, db); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ id: "tse-1", teamId: "team-1", teamName: "Team Alpha", scoringEventId: "event-1", scoringEventName: "Semifinals", sportName: "Darts", pointsDelta: "75", occurredAt, }); expect(result[0].participants).toEqual([ { id: "p-1", name: "Luke Littler" }, { id: "p-2", name: "Michael van Gerwen" }, ]); }); it("omits participants whose names cannot be resolved", async () => { const { db } = makeDb({ scoreEventRows: [ { id: "tse-1", teamId: "team-1", scoringEventId: "event-1", scoringEventName: null, sportName: null, pointsDelta: "50", occurredAt: new Date(), participantIds: ["p-1", "p-missing"], team: { id: "team-1", name: "Team Beta" }, }, ], participantRows: [{ id: "p-1", name: "Known Player" }], }); const result = await getRecentTeamScoreEvents("season-1", 10, db); expect(result[0].participants).toEqual([{ id: "p-1", name: "Known Player" }]); }); it("handles null participantIds gracefully", async () => { const { db } = makeDb({ scoreEventRows: [ { id: "tse-1", teamId: "team-1", scoringEventId: "event-1", scoringEventName: null, sportName: null, pointsDelta: "10", occurredAt: new Date(), participantIds: null as any, team: { id: "team-1", name: "Team Gamma" }, }, ], participantRows: [], }); const result = await getRecentTeamScoreEvents("season-1", 10, db); expect(result[0].participants).toEqual([]); }); });