67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
|
|
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<string, unknown>;
|
||
|
|
|
||
|
|
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<void>) => cb(tx)),
|
||
|
|
};
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
setCalls.length = 0;
|
||
|
|
conflictSetCalls.length = 0;
|
||
|
|
(database as ReturnType<typeof vi.fn>).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();
|
||
|
|
});
|
||
|
|
});
|