import { describe, it, expect, vi, beforeEach } from "vitest"; // Mock the database context before importing any model vi.mock("~/database/context", () => ({ database: vi.fn(), })); import { database } from "~/database/context"; import { batchSaveFuturesOddsForSimulator } from "../simulator"; type SetPayload = Record; const setCalls: SetPayload[] = []; const conflictSetCalls: SetPayload[] = []; const tx = { update: vi.fn(() => ({ set: vi.fn((arg: SetPayload) => { setCalls.push(arg); return { where: vi.fn().mockResolvedValue(undefined) }; }), })), insert: vi.fn(() => ({ values: vi.fn(() => ({ onConflictDoUpdate: vi.fn((arg: { set: SetPayload }) => { conflictSetCalls.push(arg.set); return Promise.resolve(undefined); }), })), })), }; const mockDb = { transaction: vi.fn(async (cb: (t: typeof tx) => Promise) => cb(tx)), }; beforeEach(() => { vi.clearAllMocks(); setCalls.length = 0; conflictSetCalls.length = 0; (database as ReturnType).mockReturnValue(mockDb); }); describe("batchSaveFuturesOddsForSimulator", () => { it("clears rating and sourceElo so odds drive the next run", async () => { await batchSaveFuturesOddsForSimulator([ { participantId: "team-1", sportsSeasonId: "season-1", sourceOdds: 550 }, ]); // Pre-update clears the stale rating and sourceElo for these participants. expect(setCalls).toHaveLength(1); expect(setCalls[0]).toMatchObject({ rating: null, sourceElo: null }); // Metadata is rewritten (an SQL fragment that strips ratingMethod/sourceEloMethod). expect(setCalls[0].metadata).toBeDefined(); // The upsert conflict path also nulls both so re-entering odds stays clean. expect(conflictSetCalls).toHaveLength(1); expect(conflictSetCalls[0]).toMatchObject({ rating: null, sourceElo: null }); expect(conflictSetCalls[0].metadata).toBeDefined(); }); it("is a no-op when given no inputs", async () => { await batchSaveFuturesOddsForSimulator([]); expect(mockDb.transaction).not.toHaveBeenCalled(); }); });