import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("~/database/context", () => ({ database: vi.fn(), })); vi.mock("~/database/schema", () => ({ seasonParticipants: { id: "sp.id", sportsSeasonId: "sp.sports_season_id", name: "sp.name", shortName: "sp.short_name", externalId: "sp.external_id", participantId: "sp.participant_id" }, seasonParticipantExpectedValues: { sportsSeasonId: "pev.sports_season_id" }, })); vi.mock("drizzle-orm", () => ({ eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }), inArray: vi.fn(), count: vi.fn(), and: (...args: unknown[]) => ({ type: "and", args }), sql: (strings: TemplateStringsArray, ...values: unknown[]) => ({ type: "sql", strings, values }), asc: (col: unknown) => ({ type: "asc", col }), desc: (col: unknown) => ({ type: "desc", col }), })); import * as schema from "~/database/schema"; import { copyParticipantsFromSeason } from "../season-participant"; import { database } from "~/database/context"; const SOURCE_ID = "source-season"; const TARGET_ID = "target-season"; function makeSourceParticipant(overrides: Record = {}) { return { id: "sp-src-1", sportsSeasonId: SOURCE_ID, name: "Tiger Woods", shortName: "T. Woods", externalId: "tiger", participantId: "canon-1", ...overrides, }; } function makeSourceEv(overrides: Record = {}) { return { id: "ev-src-1", participantId: "sp-src-1", sportsSeasonId: SOURCE_ID, sourceOdds: 800, sourceElo: null, source: "futures", worldRanking: 5, ...overrides, }; } function makeMockDb({ targetParticipants = [] as object[], sourceParticipants = [] as object[], sourceEvRows = [] as object[], newParticipantRows = sourceParticipants.map((p, i) => ({ ...(p as Record), id: `sp-new-${i + 1}`, sportsSeasonId: TARGET_ID, })), }: { targetParticipants?: object[]; sourceParticipants?: object[]; sourceEvRows?: object[]; newParticipantRows?: object[]; } = {}) { const insertedRows: Record = {}; const findManyCalls: { table: string; args: unknown }[] = []; const db = { query: { seasonParticipants: { findMany: vi.fn().mockImplementation((opts: unknown) => { findManyCalls.push({ table: "seasonParticipants", args: opts }); const where = (opts as Record)?.where as { val: string } | undefined; if (where?.val === SOURCE_ID) return sourceParticipants; if (where?.val === TARGET_ID) return targetParticipants; return targetParticipants; }), }, seasonParticipantExpectedValues: { findMany: vi.fn().mockResolvedValue(sourceEvRows), }, }, insert: vi.fn().mockImplementation((table: object) => { let key: string; let returnRows: object[]; if (table === schema.seasonParticipants) { key = "seasonParticipants"; returnRows = newParticipantRows; } else { key = "participantExpectedValues"; returnRows = []; } return { values: vi.fn().mockImplementation((vals: unknown) => { insertedRows[key] = vals; if (key === "seasonParticipants") { return { returning: vi.fn().mockResolvedValue(returnRows) }; } return Promise.resolve(undefined); }), }; }), transaction: vi.fn().mockImplementation(async (fn: (tx: typeof db) => Promise) => fn(db)), _insertedRows: insertedRows, _findManyCalls: findManyCalls, }; return db; } beforeEach(() => { vi.clearAllMocks(); }); describe("copyParticipantsFromSeason", () => { it("returns empty result when source has no participants", async () => { const db = makeMockDb({ sourceParticipants: [] }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result).toEqual({ copied: 0, skipped: 0, evStubsCopied: 0 }); expect(db.transaction).not.toHaveBeenCalled(); }); it("skips participants already present in target (case-insensitive)", async () => { const db = makeMockDb({ targetParticipants: [makeSourceParticipant({ id: "sp-tgt-1", sportsSeasonId: TARGET_ID })], sourceParticipants: [makeSourceParticipant()], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result).toEqual({ copied: 0, skipped: 1, evStubsCopied: 0 }); expect(db.transaction).not.toHaveBeenCalled(); }); it("copies participants and returns counts", async () => { const srcP1 = makeSourceParticipant({ name: "Tiger Woods", externalId: "tiger", participantId: "canon-1" }); const srcP2 = makeSourceParticipant({ id: "sp-src-2", name: "Rory McIlroy", externalId: "rory", participantId: "canon-2" }); const db = makeMockDb({ sourceParticipants: [srcP1, srcP2], newParticipantRows: [ { ...srcP1, id: "sp-new-1", sportsSeasonId: TARGET_ID }, { ...srcP2, id: "sp-new-2", sportsSeasonId: TARGET_ID }, ], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result.copied).toBe(2); expect(result.skipped).toBe(0); expect(db._insertedRows.seasonParticipants).toHaveLength(2); const values = db._insertedRows.seasonParticipants as Array>; expect(values[0]).toMatchObject({ name: "Tiger Woods", externalId: "tiger", participantId: "canon-1" }); expect(values[1]).toMatchObject({ name: "Rory McIlroy", externalId: "rory", participantId: "canon-2" }); }); it("copies EV stubs matched by externalId", async () => { const srcP = makeSourceParticipant({ externalId: "tiger" }); const srcEv = makeSourceEv({ participantId: "sp-src-1", sourceOdds: 800 }); const db = makeMockDb({ sourceParticipants: [srcP], sourceEvRows: [srcEv], newParticipantRows: [{ ...srcP, id: "sp-new-1", sportsSeasonId: TARGET_ID }], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result.evStubsCopied).toBe(1); const evRows = db._insertedRows.participantExpectedValues as Array>; expect(evRows).toHaveLength(1); expect(evRows[0]).toMatchObject({ participantId: "sp-new-1", sourceOdds: 800, source: "futures", worldRanking: 5, }); }); it("copies EV stubs matched by name when externalId is null", async () => { const srcP = makeSourceParticipant({ externalId: null, name: "Tiger Woods" }); const srcEv = makeSourceEv({ participantId: "sp-src-1" }); const db = makeMockDb({ sourceParticipants: [srcP], sourceEvRows: [srcEv], newParticipantRows: [{ ...srcP, id: "sp-new-1", sportsSeasonId: TARGET_ID }], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result.evStubsCopied).toBe(1); }); it("skips EV rows with neither sourceOdds nor sourceElo", async () => { const srcP = makeSourceParticipant(); const srcEv = makeSourceEv({ sourceOdds: null, sourceElo: null }); const db = makeMockDb({ sourceParticipants: [srcP], sourceEvRows: [srcEv], newParticipantRows: [{ ...srcP, id: "sp-new-1", sportsSeasonId: TARGET_ID }], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result.evStubsCopied).toBe(0); expect(db._insertedRows.participantExpectedValues).toBeUndefined(); }); it("wraps writes in a transaction", async () => { const srcP = makeSourceParticipant(); const db = makeMockDb({ sourceParticipants: [srcP], newParticipantRows: [{ ...srcP, id: "sp-new-1", sportsSeasonId: TARGET_ID }], }); vi.mocked(database).mockReturnValue(db as never); await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(db.transaction).toHaveBeenCalledOnce(); }); it("handles partial duplicates (some copied, some skipped)", async () => { const srcP1 = makeSourceParticipant({ name: "Tiger Woods" }); const srcP2 = makeSourceParticipant({ id: "sp-src-2", name: "Rory McIlroy", participantId: "canon-2" }); const db = makeMockDb({ targetParticipants: [makeSourceParticipant({ id: "sp-tgt-1", sportsSeasonId: TARGET_ID, name: "Tiger Woods" })], sourceParticipants: [srcP1, srcP2], newParticipantRows: [{ ...srcP2, id: "sp-new-1", sportsSeasonId: TARGET_ID }], }); vi.mocked(database).mockReturnValue(db as never); const result = await copyParticipantsFromSeason(SOURCE_ID, TARGET_ID); expect(result.copied).toBe(1); expect(result.skipped).toBe(1); const values = db._insertedRows.seasonParticipants as Array>; expect(values).toHaveLength(1); expect(values[0]).toMatchObject({ name: "Rory McIlroy" }); }); });