Two patterns causing CI timeouts: 1. admin.sports-seasons.$id.test.ts: vi.resetModules() + multiple await import() in beforeEach forced full module re-evaluation on every test, exceeding the 10s hook timeout. 2. participant-expected-value.test.ts: await import() of the module under test inside test bodies caused the first import to run within the 5s test window. Also deleted 13 expect(true).toBe(true) stubs and used vi.hoisted() to make mock factories work with static imports. Both files now use static top-level imports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { calculateReplacementLevel, calculateVORP } from "~/services/ev-calculator";
|
|
import { syncVorpForSeason } from "../participant-expected-value";
|
|
|
|
const { mockUpdate, mockSet, mockDb, mockSqlFn } = vi.hoisted(() => {
|
|
const update = vi.fn();
|
|
const set = vi.fn();
|
|
const sqlFn = Object.assign(vi.fn(() => ({})), { join: vi.fn(() => ({})) });
|
|
const db = { update, select: vi.fn() };
|
|
return { mockUpdate: update, mockSet: set, mockDb: db, mockSqlFn: sqlFn };
|
|
});
|
|
|
|
vi.mock("~/database/context", () => ({
|
|
database: () => mockDb,
|
|
}));
|
|
|
|
vi.mock("~/database/schema", () => ({
|
|
seasonParticipants: { id: "id" },
|
|
seasonParticipantExpectedValues: {
|
|
participantId: "participantId",
|
|
sportsSeasonId: "sportsSeasonId",
|
|
},
|
|
}));
|
|
|
|
vi.mock("drizzle-orm", () => ({
|
|
eq: vi.fn((field, value) => ({ field, value })),
|
|
and: vi.fn((...args) => ({ and: args })),
|
|
count: vi.fn(() => ({ count: true })),
|
|
sql: mockSqlFn,
|
|
}));
|
|
|
|
describe("syncVorpForSeason", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("calculates correct VORP values for 14 participants with EVs 100 down to 35 (step 5)", () => {
|
|
// 14 participants: EVs = 100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35
|
|
// replacement level = avg of positions 12-14 (0-indexed 11-13) = avg(45, 40, 35) = 40
|
|
const evValues = Array.from({ length: 14 }, (_, i) => 100 - i * 5);
|
|
|
|
const replacementLevel = calculateReplacementLevel(evValues);
|
|
expect(replacementLevel).toBe(40);
|
|
expect(calculateVORP(100, replacementLevel)).toBe(60);
|
|
expect(calculateVORP(35, replacementLevel)).toBe(-5);
|
|
});
|
|
|
|
it("returns early when no EVs exist for the season", async () => {
|
|
const mockSelectChain = {
|
|
from: vi.fn().mockReturnThis(),
|
|
where: vi.fn().mockResolvedValue([]),
|
|
};
|
|
mockDb.select = vi.fn().mockReturnValue(mockSelectChain);
|
|
|
|
await syncVorpForSeason("season-empty");
|
|
|
|
expect(mockUpdate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls db.update with correct vorpValue for each participant", async () => {
|
|
// 3 participants: EVs 100, 70, 40
|
|
// replacement level = avg of positions 12-14, clamped to [40] → 40
|
|
// VORP: 100→60, 70→30, 40→0
|
|
const mockEvRecords = [
|
|
{ participantId: "p1", expectedValue: "100", sportsSeasonId: "season-1" },
|
|
{ participantId: "p2", expectedValue: "70", sportsSeasonId: "season-1" },
|
|
{ participantId: "p3", expectedValue: "40", sportsSeasonId: "season-1" },
|
|
];
|
|
|
|
const mockSelectChain = {
|
|
from: vi.fn().mockReturnThis(),
|
|
where: vi.fn().mockResolvedValue(mockEvRecords),
|
|
};
|
|
mockDb.select = vi.fn().mockReturnValue(mockSelectChain);
|
|
|
|
const mockWhereResolved = vi.fn().mockResolvedValue([]);
|
|
mockSet.mockReturnValue({ where: mockWhereResolved });
|
|
mockUpdate.mockReturnValue({ set: mockSet });
|
|
|
|
await syncVorpForSeason("season-1");
|
|
|
|
expect(mockUpdate).toHaveBeenCalledTimes(1);
|
|
expect(mockSet).toHaveBeenCalledTimes(1);
|
|
const setArg = mockSet.mock.calls[0][0];
|
|
expect(setArg).toHaveProperty("vorpValue");
|
|
expect(setArg).toHaveProperty("updatedAt");
|
|
});
|
|
});
|