import { describe, it, expect, vi, beforeEach } from "vitest"; // populateBracketFromDraw upserts a full draw into playoff_matches and reports // which losers' matches *transitioned to complete on this run* — the signal used // to announce knockouts once, idempotently across re-syncs. const existingRows: Array> = []; const mockDb = { query: { playoffMatches: { findMany: vi.fn(async () => existingRows), }, }, update: vi.fn(() => ({ set: vi.fn(() => ({ where: vi.fn().mockResolvedValue(undefined) })), })), insert: vi.fn(() => ({ values: vi.fn(() => ({ returning: vi.fn().mockResolvedValue([]) })), })), }; vi.mock("~/database/context", () => ({ database: () => mockDb })); import { populateBracketFromDraw, type ResolvedDrawMatch } from "../playoff-match"; const EVENT_ID = "ev-1"; function match(overrides: Partial = {}): ResolvedDrawMatch { return { externalMatchId: "m-1", round: "Round of 64", matchNumber: 1, participant1Id: "winner", participant2Id: "loser", winnerId: "winner", loserId: "loser", isScoring: false, ...overrides, }; } beforeEach(() => { vi.clearAllMocks(); existingRows.length = 0; }); describe("populateBracketFromDraw newlyDecidedLoserIds", () => { it("reports the loser of a brand-new completed match", async () => { const { newlyDecidedLoserIds } = await populateBracketFromDraw(EVENT_ID, [ match({ externalMatchId: "m-1", loserId: "mensik" }), ]); expect(newlyDecidedLoserIds).toEqual(["mensik"]); }); it("reports the loser of an existing match that just reached completion", async () => { existingRows.push({ id: "row-1", externalMatchId: "m-1", isComplete: false, loserId: null, winnerId: null, }); const { newlyDecidedLoserIds } = await populateBracketFromDraw(EVENT_ID, [ match({ externalMatchId: "m-1", loserId: "mensik" }), ]); expect(newlyDecidedLoserIds).toEqual(["mensik"]); }); it("reports nothing on an idempotent re-sync of an already-complete match", async () => { existingRows.push({ id: "row-1", externalMatchId: "m-1", isComplete: true, loserId: "mensik", winnerId: "winner", }); const { newlyDecidedLoserIds } = await populateBracketFromDraw(EVENT_ID, [ match({ externalMatchId: "m-1", loserId: "mensik" }), ]); expect(newlyDecidedLoserIds).toEqual([]); }); it("ignores incomplete matches (no winner/loser yet)", async () => { const { newlyDecidedLoserIds, completed } = await populateBracketFromDraw(EVENT_ID, [ match({ externalMatchId: "m-2", winnerId: null, loserId: null }), ]); expect(newlyDecidedLoserIds).toEqual([]); expect(completed).toBe(0); }); it("collects only the newly-decided losers in a mixed batch", async () => { existingRows.push( { id: "row-1", externalMatchId: "m-1", isComplete: true, loserId: "old", winnerId: "w1" }, { id: "row-2", externalMatchId: "m-2", isComplete: false, loserId: null, winnerId: null }, ); const { newlyDecidedLoserIds } = await populateBracketFromDraw(EVENT_ID, [ match({ externalMatchId: "m-1", loserId: "old" }), // already complete → skip match({ externalMatchId: "m-2", loserId: "freshly-out" }), // transitioned → include match({ externalMatchId: "m-3", loserId: "brand-new-out" }), // new complete → include match({ externalMatchId: "m-4", winnerId: null, loserId: null }), // incomplete → skip ]); expect(newlyDecidedLoserIds.toSorted()).toEqual(["brand-new-out", "freshly-out"]); }); });