360 lines
11 KiB
TypeScript
360 lines
11 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
vi.mock("~/lib/logger", () => ({
|
|
logger: { error: vi.fn() },
|
|
}));
|
|
|
|
// ── DB mock helpers ────────────────────────────────────────────────────────
|
|
|
|
function makeInsertChain() {
|
|
const onConflictDoUpdate = vi.fn().mockResolvedValue(undefined);
|
|
const values = vi.fn().mockReturnValue({ onConflictDoUpdate });
|
|
const insert = vi.fn().mockReturnValue({ values });
|
|
return { insert, values, onConflictDoUpdate };
|
|
}
|
|
|
|
interface MakeDbOpts {
|
|
sportsSeason?: { sport: { name: string } } | null;
|
|
seasonSports?: { seasonId: string }[];
|
|
picks?: { teamId: string; seasonId: string }[];
|
|
seasons?: {
|
|
id: string;
|
|
pointsFor1st: number; pointsFor2nd: number; pointsFor3rd: number;
|
|
pointsFor4th: number; pointsFor5th: number; pointsFor6th: number;
|
|
pointsFor7th: number; pointsFor8th: number;
|
|
}[];
|
|
scoreEventRows?: {
|
|
id: string; teamId: string; teamName?: string;
|
|
scoringEventId: string | null; scoringEventName: string | null;
|
|
sportName: string | null; pointsDelta: string; occurredAt: Date;
|
|
participantIds: string[];
|
|
team: { id: string; name: string };
|
|
}[];
|
|
participantRows?: { id: string; name: string }[];
|
|
}
|
|
|
|
function makeDb(opts: MakeDbOpts = {}) {
|
|
const {
|
|
sportsSeason = null,
|
|
seasonSports = [],
|
|
picks = [],
|
|
seasons = [],
|
|
scoreEventRows = [],
|
|
participantRows = [],
|
|
} = opts;
|
|
|
|
const chain = makeInsertChain();
|
|
|
|
return {
|
|
db: {
|
|
insert: chain.insert,
|
|
query: {
|
|
sportsSeasons: {
|
|
findFirst: vi.fn().mockResolvedValue(sportsSeason),
|
|
},
|
|
seasonSports: {
|
|
findMany: vi.fn().mockResolvedValue(seasonSports),
|
|
},
|
|
draftPicks: {
|
|
findMany: vi.fn().mockResolvedValue(picks),
|
|
},
|
|
seasons: {
|
|
findMany: vi.fn().mockResolvedValue(seasons),
|
|
},
|
|
teamScoreEvents: {
|
|
findMany: vi.fn().mockResolvedValue(scoreEventRows),
|
|
},
|
|
participants: {
|
|
findMany: vi.fn().mockResolvedValue(participantRows),
|
|
},
|
|
},
|
|
} as any,
|
|
chain,
|
|
};
|
|
}
|
|
|
|
import { recordTeamScoreEvent, recordMatchScoreEvents, getRecentTeamScoreEvents } from "../team-score-events";
|
|
|
|
const BASE_PARAMS = {
|
|
teamId: "team-1",
|
|
seasonId: "season-1",
|
|
scoringEventId: "event-1",
|
|
scoringEventName: "Round of 16",
|
|
sportName: "Darts",
|
|
participantIds: ["p-1"],
|
|
pointsDelta: 10,
|
|
};
|
|
|
|
describe("recordTeamScoreEvent", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it("uses matchId-based conflict target when matchId provided", async () => {
|
|
const { db, chain } = makeDb();
|
|
|
|
await recordTeamScoreEvent({ ...BASE_PARAMS, matchId: "match-1" }, db);
|
|
|
|
expect(chain.insert).toHaveBeenCalled();
|
|
expect(chain.values).toHaveBeenCalledWith(
|
|
expect.objectContaining({ matchId: "match-1", pointsDelta: "10" })
|
|
);
|
|
const conflictArg = chain.onConflictDoUpdate.mock.calls[0][0];
|
|
expect(conflictArg.target).toHaveLength(3);
|
|
// targetWhere should reference matchId IS NOT NULL
|
|
expect(conflictArg.targetWhere).toBeDefined();
|
|
expect(conflictArg.set).toMatchObject({ pointsDelta: "10", participantIds: ["p-1"] });
|
|
});
|
|
|
|
it("uses scoringEventId-based conflict target when no matchId", async () => {
|
|
const { db, chain } = makeDb();
|
|
|
|
await recordTeamScoreEvent(BASE_PARAMS, db);
|
|
|
|
expect(chain.insert).toHaveBeenCalled();
|
|
expect(chain.values).toHaveBeenCalledWith(
|
|
expect.objectContaining({ matchId: null })
|
|
);
|
|
const conflictArg = chain.onConflictDoUpdate.mock.calls[0][0];
|
|
expect(conflictArg.target).toHaveLength(3);
|
|
expect(conflictArg.targetWhere).toBeDefined();
|
|
});
|
|
|
|
it("stores pointsDelta as string", async () => {
|
|
const { db, chain } = makeDb();
|
|
await recordTeamScoreEvent({ ...BASE_PARAMS, pointsDelta: 42 }, db);
|
|
expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ pointsDelta: "42" }));
|
|
});
|
|
|
|
it("defaults matchId to null when not provided", async () => {
|
|
const { db, chain } = makeDb();
|
|
await recordTeamScoreEvent(BASE_PARAMS, db);
|
|
expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ matchId: null }));
|
|
});
|
|
});
|
|
|
|
describe("recordMatchScoreEvents", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
const BASE_EVENT = {
|
|
participantId: "p-1",
|
|
sportsSeasonId: "ss-1",
|
|
oldFloor: 0,
|
|
newFloor: 2,
|
|
bracketTemplateId: null as string | null,
|
|
matchId: "match-1",
|
|
eventId: "event-1",
|
|
eventName: "Semifinals",
|
|
};
|
|
|
|
const SEASON_ROW = {
|
|
id: "season-1",
|
|
pointsFor1st: 100, pointsFor2nd: 75, pointsFor3rd: 50,
|
|
pointsFor4th: 40, pointsFor5th: 30, pointsFor6th: 20,
|
|
pointsFor7th: 10, pointsFor8th: 5,
|
|
};
|
|
|
|
it("returns early when no fantasy seasons use this sports season", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Darts" } },
|
|
seasonSports: [],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns early when no draft picks found for participant", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Darts" } },
|
|
seasonSports: [{ seasonId: "season-1" }],
|
|
picks: [],
|
|
seasons: [SEASON_ROW],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips seasons where team did not draft the participant", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Darts" } },
|
|
seasonSports: [{ seasonId: "season-1" }, { seasonId: "season-2" }],
|
|
picks: [{ teamId: "team-1", seasonId: "season-1" }], // only season-1 has pick
|
|
seasons: [SEASON_ROW, { ...SEASON_ROW, id: "season-2" }],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.insert).toHaveBeenCalledTimes(1);
|
|
expect(chain.values).toHaveBeenCalledWith(expect.objectContaining({ seasonId: "season-1" }));
|
|
});
|
|
|
|
it("skips seasons where point delta is zero or negative", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Darts" } },
|
|
seasonSports: [{ seasonId: "season-1" }],
|
|
picks: [{ teamId: "team-1", seasonId: "season-1" }],
|
|
seasons: [{ ...SEASON_ROW, pointsFor1st: 0, pointsFor2nd: 0 }], // all zeros → delta=0
|
|
});
|
|
|
|
// With all-zero scoring rules, positions map to 0 points → delta = 0 → skip
|
|
await recordMatchScoreEvents({ ...BASE_EVENT, oldFloor: 1, newFloor: 2 }, db);
|
|
|
|
expect(chain.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("records score event with correct teamId, seasonId, and matchId", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Darts" } },
|
|
seasonSports: [{ seasonId: "season-1" }],
|
|
picks: [{ teamId: "team-1", seasonId: "season-1" }],
|
|
seasons: [SEASON_ROW],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.values).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
teamId: "team-1",
|
|
seasonId: "season-1",
|
|
matchId: "match-1",
|
|
sportName: "Darts",
|
|
participantIds: ["p-1"],
|
|
})
|
|
);
|
|
});
|
|
|
|
it("uses sport name from sportsSeasons query", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: { sport: { name: "Snooker" } },
|
|
seasonSports: [{ seasonId: "season-1" }],
|
|
picks: [{ teamId: "team-1", seasonId: "season-1" }],
|
|
seasons: [SEASON_ROW],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.values).toHaveBeenCalledWith(
|
|
expect.objectContaining({ sportName: "Snooker" })
|
|
);
|
|
});
|
|
|
|
it("handles null sportsSeason gracefully (sportName = null)", async () => {
|
|
const { db, chain } = makeDb({
|
|
sportsSeason: null,
|
|
seasonSports: [{ seasonId: "season-1" }],
|
|
picks: [{ teamId: "team-1", seasonId: "season-1" }],
|
|
seasons: [SEASON_ROW],
|
|
});
|
|
|
|
await recordMatchScoreEvents(BASE_EVENT, db);
|
|
|
|
expect(chain.values).toHaveBeenCalledWith(
|
|
expect.objectContaining({ sportName: null })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("getRecentTeamScoreEvents", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it("returns empty array when no rows found", async () => {
|
|
const { db } = makeDb({ scoreEventRows: [] });
|
|
const result = await getRecentTeamScoreEvents("season-1", 10, db);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("does not query participants when rows array is empty", async () => {
|
|
const { db } = makeDb({ scoreEventRows: [] });
|
|
await getRecentTeamScoreEvents("season-1", 10, db);
|
|
expect(db.query.participants.findMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns mapped entries with resolved participant names", async () => {
|
|
const occurredAt = new Date("2024-01-15");
|
|
const { db } = makeDb({
|
|
scoreEventRows: [
|
|
{
|
|
id: "tse-1",
|
|
teamId: "team-1",
|
|
scoringEventId: "event-1",
|
|
scoringEventName: "Semifinals",
|
|
sportName: "Darts",
|
|
pointsDelta: "75",
|
|
occurredAt,
|
|
participantIds: ["p-1", "p-2"],
|
|
team: { id: "team-1", name: "Team Alpha" },
|
|
},
|
|
],
|
|
participantRows: [
|
|
{ id: "p-1", name: "Luke Littler" },
|
|
{ id: "p-2", name: "Michael van Gerwen" },
|
|
],
|
|
});
|
|
|
|
const result = await getRecentTeamScoreEvents("season-1", 10, db);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
id: "tse-1",
|
|
teamId: "team-1",
|
|
teamName: "Team Alpha",
|
|
scoringEventId: "event-1",
|
|
scoringEventName: "Semifinals",
|
|
sportName: "Darts",
|
|
pointsDelta: "75",
|
|
occurredAt,
|
|
});
|
|
expect(result[0].participants).toEqual([
|
|
{ id: "p-1", name: "Luke Littler" },
|
|
{ id: "p-2", name: "Michael van Gerwen" },
|
|
]);
|
|
});
|
|
|
|
it("omits participants whose names cannot be resolved", async () => {
|
|
const { db } = makeDb({
|
|
scoreEventRows: [
|
|
{
|
|
id: "tse-1",
|
|
teamId: "team-1",
|
|
scoringEventId: "event-1",
|
|
scoringEventName: null,
|
|
sportName: null,
|
|
pointsDelta: "50",
|
|
occurredAt: new Date(),
|
|
participantIds: ["p-1", "p-missing"],
|
|
team: { id: "team-1", name: "Team Beta" },
|
|
},
|
|
],
|
|
participantRows: [{ id: "p-1", name: "Known Player" }],
|
|
});
|
|
|
|
const result = await getRecentTeamScoreEvents("season-1", 10, db);
|
|
|
|
expect(result[0].participants).toEqual([{ id: "p-1", name: "Known Player" }]);
|
|
});
|
|
|
|
it("handles null participantIds gracefully", async () => {
|
|
const { db } = makeDb({
|
|
scoreEventRows: [
|
|
{
|
|
id: "tse-1",
|
|
teamId: "team-1",
|
|
scoringEventId: "event-1",
|
|
scoringEventName: null,
|
|
sportName: null,
|
|
pointsDelta: "10",
|
|
occurredAt: new Date(),
|
|
participantIds: null as any,
|
|
team: { id: "team-1", name: "Team Gamma" },
|
|
},
|
|
],
|
|
participantRows: [],
|
|
});
|
|
|
|
const result = await getRecentTeamScoreEvents("season-1", 10, db);
|
|
|
|
expect(result[0].participants).toEqual([]);
|
|
});
|
|
});
|