brackt/app/models/__tests__/team-score-events.test.ts

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([]);
});
});