brackt/app/models/__tests__/upcoming-calendar.test.ts
Chris Parsons efbd28b446
Exclude completed playoff matches from upcoming events query (#247)
* Exclude scored bracket matches from upcoming events calendar

Add `eq(schema.playoffMatches.isComplete, false)` to the bracket-sports
query in `getUpcomingEventsForDraftedParticipants` so that individual
playoff matches already scored (isComplete = true) no longer appear on
the upcoming events calendar, even when the parent scoring event is still
open.

Also update the schema mock in upcoming-calendar.test.ts to include
`playoffMatches.isComplete` and add a test covering this behaviour.

https://claude.ai/code/session_01GMLmjN9mwoANfpgSciD7Bi

* Fix leaked mockReturnValueOnce in scored-match test

The new test only triggers one selectDistinct call (rows is empty so the
max-game-number lookup is skipped). The second mockReturnValueOnce was
never consumed, leaving a makeMaxGameChain value in the queue that was
then picked up by the subsequent "uses leftJoin" test, causing it to fail
with "leftJoin is not a function".

https://claude.ai/code/session_01GMLmjN9mwoANfpgSciD7Bi

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 10:08:25 -07:00

607 lines
25 KiB
TypeScript

/**
* Tests for getUpcomingEventsForDraftedParticipants logic.
*
* We test the exported function indirectly by exercising the pure
* business-logic paths (all-compete vs bracket, date filtering,
* deduplication, empty inputs) using a vi.mock of the database context
* so no real DB is required.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
// ── DB mock ────────────────────────────────────────────────────────────────
// Must be declared before the import that triggers the module graph.
const mockDb = {
query: {
scoringEvents: { findMany: vi.fn() },
},
selectDistinct: vi.fn(),
};
vi.mock("~/database/context", () => ({
database: () => mockDb,
}));
vi.mock("~/database/schema", () => ({
scoringEvents: { sportsSeasonId: "se.sports_season_id", isComplete: "se.is_complete", eventDate: "se.event_date", eventType: "se.event_type" },
playoffMatches: { scoringEventId: "pm.scoring_event_id", participant1Id: "pm.participant1_id", participant2Id: "pm.participant2_id", round: "pm.round", matchNumber: "pm.match_number", isComplete: "pm.is_complete" },
playoffMatchGames: { playoffMatchId: "pmg.playoff_match_id", scheduledAt: "pmg.scheduled_at", gameNumber: "pmg.game_number" },
}));
vi.mock("drizzle-orm", () => ({
eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }),
and: (...args: unknown[]) => ({ type: "and", args }),
asc: (col: unknown) => ({ type: "asc", col }),
desc: (col: unknown) => ({ type: "desc", col }),
gte: (col: unknown, val: unknown) => ({ type: "gte", col, val }),
lte: (col: unknown, val: unknown) => ({ type: "lte", col, val }),
or: (...args: unknown[]) => ({ type: "or", args }),
inArray: (col: unknown, arr: unknown) => ({ type: "inArray", col, arr }),
isNotNull: (col: unknown) => ({ type: "isNotNull", col }),
}));
import { getUpcomingEventsForDraftedParticipants } from "../scoring-event";
// ── Helpers ────────────────────────────────────────────────────────────────
const DATE_FROM = "2025-04-01";
const DATE_TO = "2025-04-30";
const SPORTS_SEASON_ID = "ss-1";
function makeParticipant(id: string, name: string) {
return { id, name };
}
function makeScoringEvent(overrides: Partial<{
id: string; name: string; eventDate: string | null;
eventStartsAt: Date | null;
eventType: string; sportsSeasonId: string; isComplete: boolean;
}> = {}) {
return {
id: overrides.id ?? "event-1",
name: overrides.name ?? "Test Event",
eventDate: overrides.eventDate ?? "2025-04-10",
eventStartsAt: overrides.eventStartsAt ?? null,
eventType: overrides.eventType ?? "schedule_event",
sportsSeasonId: overrides.sportsSeasonId ?? SPORTS_SEASON_ID,
isComplete: overrides.isComplete ?? false,
};
}
// ── All-compete patterns ───────────────────────────────────────────────────
describe("getUpcomingEventsForDraftedParticipants — all-compete sports", () => {
beforeEach(() => {
vi.clearAllMocks();
// selectDistinct won't be called for all-compete
mockDb.selectDistinct.mockReturnValue({ from: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), orderBy: vi.fn().mockResolvedValue([]) });
});
it("returns [] when draftedParticipants is empty", async () => {
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", [], DATE_FROM, DATE_TO
);
expect(result).toEqual([]);
expect(mockDb.query.scoringEvents.findMany).not.toHaveBeenCalled();
});
it("returns all events with all drafted participants attached", async () => {
const participants = [makeParticipant("p1", "Verstappen"), makeParticipant("p2", "Hamilton")];
const events = [makeScoringEvent({ id: "e1", name: "Australian GP" })];
mockDb.query.scoringEvents.findMany.mockResolvedValue(events);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Australian GP");
expect(result[0].relevantParticipants).toEqual(participants);
});
it("works for qualifying_points pattern", async () => {
const participants = [makeParticipant("g1", "Scheffler")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([makeScoringEvent({ id: "e1", name: "Masters" })]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "qualifying_points", participants, DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
});
it("unknown pattern (e.g. golf_qualifying_points) falls through to bracket path, not all-compete", async () => {
// golf_qualifying_points is a simulatorType, not a scoringPattern.
// Unknown patterns are treated as bracket sports (selectDistinct path).
const participants = [makeParticipant("g1", "McIlroy")];
const selectChain = { from: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(), leftJoin: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), orderBy: vi.fn().mockResolvedValue([]) };
mockDb.selectDistinct.mockReturnValue(selectChain);
await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "golf_qualifying_points", participants, DATE_FROM, DATE_TO
);
// Should NOT call the all-compete findMany path
expect(mockDb.query.scoringEvents.findMany).not.toHaveBeenCalled();
// Should call the bracket selectDistinct path
expect(mockDb.selectDistinct).toHaveBeenCalled();
});
it("returns multiple events sorted by eventDate", async () => {
const participants = [makeParticipant("p1", "Verstappen")];
const events = [
makeScoringEvent({ id: "e2", name: "Miami GP", eventDate: "2025-04-20" }),
makeScoringEvent({ id: "e1", name: "Australian GP", eventDate: "2025-04-06" }),
];
// findMany is ordered by asc(eventDate) in the real query; simulate same order
mockDb.query.scoringEvents.findMany.mockResolvedValue([events[1], events[0]]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
expect(result[0].name).toBe("Australian GP");
expect(result[1].name).toBe("Miami GP");
});
});
// ── Bracket patterns ───────────────────────────────────────────────────────
describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
// Main query chain: resolves on orderBy (has leftJoin, orderBy)
function makeSelectChain(rows: unknown[]) {
const chain = {
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
leftJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockResolvedValue(rows),
};
return chain;
}
// Second query chain (max game lookup): resolves on where (no leftJoin, no orderBy)
function makeMaxGameChain(rows: unknown[]) {
const chain = {
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue(rows),
};
return chain;
}
beforeEach(() => {
vi.clearAllMocks();
// Default for the second selectDistinct call (max-game-number lookup).
// Individual tests override the first call with mockReturnValueOnce.
mockDb.selectDistinct.mockReturnValue(makeMaxGameChain([]));
});
it("returns [] when draftedParticipants is empty", async () => {
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket", [], DATE_FROM, DATE_TO
);
expect(result).toEqual([]);
expect(mockDb.selectDistinct).not.toHaveBeenCalled();
});
it("returns one event with one relevant participant (participant1)", async () => {
const rows = [{
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "bayern",
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("UCL QF");
expect(result[0].relevantParticipants).toEqual([{ id: "man-city", name: "Man City" }]);
});
it("returns one event when both participants are drafted (no duplication)", async () => {
// Two rows for the same event — the DB query uses selectDistinct on event columns
// but we may still get two rows if the WHERE matches on both participants.
// The grouping logic must deduplicate the event itself.
const rows = [
{
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "arsenal",
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
},
];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City"), makeParticipant("arsenal", "Arsenal")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
expect(result[0].relevantParticipants).toHaveLength(2);
const names = result[0].relevantParticipants.map((p) => p.name);
expect(names).toContain("Man City");
expect(names).toContain("Arsenal");
});
it("returns multiple events when picks are in different matches", async () => {
const rows = [
{
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "real-madrid",
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
},
{
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "arsenal", participant2Id: "psg",
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
},
];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City"), makeParticipant("arsenal", "Arsenal")],
DATE_FROM, DATE_TO
);
// Both matches are in the same event → one result with both picks
expect(result).toHaveLength(1);
expect(result[0].relevantParticipants).toHaveLength(2);
});
it("only includes drafted participants in relevantParticipants, not opponents", async () => {
const rows = [{
id: "e1", name: "UCL QF", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "real-madrid",
round: "Quarterfinals", gameNumber: 1, scheduledAt: null,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result[0].relevantParticipants).toEqual([{ id: "man-city", name: "Man City" }]);
// Real Madrid (the opponent) should NOT appear
expect(result[0].relevantParticipants.find((p) => p.id === "real-madrid")).toBeUndefined();
});
it("works for ucl_bracket pattern", async () => {
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain([{
id: "e1", name: "UCL SF", eventDate: "2025-04-29",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "arsenal",
round: "Semifinals", gameNumber: 1, scheduledAt: null,
}]));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "ucl_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
});
it("includes events with null eventDate when they have games scheduled within the window", async () => {
// Real-world case: admin sets game times on individual games (scheduledAt)
// but leaves scoringEvent.eventDate null — the event must still appear.
const rows = [{
id: "e1", name: "UCL QF",
eventDate: null, // ← no date on the event itself
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "barcelona", participant2Id: "sporting",
round: "Knockout Stage", gameNumber: 1, scheduledAt: null,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("barcelona", "Barcelona")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("UCL QF");
expect(result[0].eventDate).toBeNull();
expect(result[0].relevantParticipants).toEqual([{ id: "barcelona", name: "Barcelona" }]);
});
it("surfaces earliestGameTime from scheduledAt when eventDate is null", async () => {
const gameTime = new Date("2025-04-09T14:00:00.000Z");
const rows = [{
id: "e1", name: "UCL QF",
eventDate: null,
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "barcelona", participant2Id: "sporting",
round: "Knockout Stage", gameNumber: 1, scheduledAt: gameTime,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("barcelona", "Barcelona")],
DATE_FROM, DATE_TO
);
expect(result[0].earliestGameTime).toBe(gameTime.toISOString());
expect(result[0].eventDate).toBeNull();
});
it("series with both games in window produces two entries, each with correct time and label", async () => {
const gameTime1 = new Date("2025-04-09T13:00:00.000Z");
const gameTime2 = new Date("2025-04-11T15:00:00.000Z");
const rows = [
{
id: "e1", name: "UCL QF", eventDate: null,
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "barcelona", participant2Id: "sporting",
round: "Knockout Stage", gameNumber: 1, scheduledAt: gameTime1,
},
{
id: "e1", name: "UCL QF", eventDate: null,
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "barcelona", participant2Id: "sporting",
round: "Knockout Stage", gameNumber: 2, scheduledAt: gameTime2,
},
];
mockDb.selectDistinct
.mockReturnValueOnce(makeSelectChain(rows))
.mockReturnValueOnce(makeMaxGameChain([
{ eventId: "e1", gameNumber: 1 },
{ eventId: "e1", gameNumber: 2 },
]));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("barcelona", "Barcelona")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(2);
const game1 = result.find((r) => r.id === "e1|1");
const game2 = result.find((r) => r.id === "e1|2");
expect(game1?.earliestGameTime).toBe(gameTime1.toISOString());
expect(game1?.matchLabel).toBe("Knockout Stage Game #1");
expect(game2?.earliestGameTime).toBe(gameTime2.toISOString());
expect(game2?.matchLabel).toBe("Knockout Stage Game #2");
});
it("sets matchLabel to round name only (no game number) for single-game matchup", async () => {
const rows = [{
id: "e1", name: "PDC World Championship", eventDate: "2025-04-09",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "arsenal",
round: "Semifinals", gameNumber: 1, scheduledAt: null,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result[0].matchLabel).toBe("Semifinals");
});
it("sets matchLabel to null when round name matches event name (single game)", async () => {
// e.g. event named "Knockout Stage", round also "Knockout Stage", single game
// → show null rather than redundant label
const rows = [{
id: "e1", name: "Knockout Stage", eventDate: null,
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "barcelona", participant2Id: "sporting",
round: "Knockout Stage", gameNumber: 1, scheduledAt: null,
}];
mockDb.selectDistinct.mockReturnValueOnce(makeSelectChain(rows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("barcelona", "Barcelona")],
DATE_FROM, DATE_TO
);
expect(result[0].matchLabel).toBeNull();
});
it("shows game number when only game #2 is in the window (game #1 already played)", async () => {
const rows = [{
id: "e1", name: "PDC World Championship", eventDate: "2025-04-10",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "man-city", participant2Id: "arsenal",
round: "Semifinals", gameNumber: 2, scheduledAt: null,
}];
mockDb.selectDistinct
.mockReturnValueOnce(makeSelectChain(rows))
.mockReturnValueOnce(makeMaxGameChain([
{ eventId: "e1", gameNumber: 1 },
{ eventId: "e1", gameNumber: 2 },
]));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result[0].matchLabel).toBe("Semifinals Game #2");
});
it("shows Game #1 label when game #1 is upcoming but series has multiple games", async () => {
// Game #2 exists in DB but is outside the date window — only Game #1 appears
// in the main query. The second (un-date-filtered) query reveals it's a series.
const mainRows = [{
id: "e1", name: "NBA Finals", eventDate: "2025-04-10",
eventType: "playoff_game", sportsSeasonId: SPORTS_SEASON_ID,
participant1Id: "lakers", participant2Id: "celtics",
round: "Finals", gameNumber: 1, scheduledAt: null,
}];
const allGameRows = [
{ eventId: "e1", gameNumber: 1 },
{ eventId: "e1", gameNumber: 2 },
];
mockDb.selectDistinct
.mockReturnValueOnce(makeSelectChain(mainRows))
.mockReturnValueOnce(makeMaxGameChain(allGameRows));
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("lakers", "Lakers")],
DATE_FROM, DATE_TO
);
expect(result[0].matchLabel).toBe("Finals Game #1");
});
it("all-compete events have null earliestGameTime and matchLabel", async () => {
const participants = [makeParticipant("p1", "Verstappen")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([
makeScoringEvent({ id: "e1", name: "Australian GP" }),
]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
expect(result[0].earliestGameTime).toBeNull();
expect(result[0].matchLabel).toBeNull();
});
});
// ── eventStartsAt — all-compete sports ────────────────────────────────────
describe("getUpcomingEventsForDraftedParticipants — eventStartsAt for all-compete sports", () => {
beforeEach(() => {
vi.clearAllMocks();
mockDb.selectDistinct.mockReturnValue({
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockResolvedValue([]),
});
});
it("populates earliestGameTime from eventStartsAt when set", async () => {
const raceStart = new Date("2025-04-06T14:00:00.000Z");
const participants = [makeParticipant("p1", "Verstappen")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([
makeScoringEvent({ id: "e1", name: "Australian GP", eventStartsAt: raceStart }),
]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
expect(result[0].earliestGameTime).toBe(raceStart.toISOString());
});
it("leaves earliestGameTime null when eventStartsAt is not set", async () => {
const participants = [makeParticipant("p1", "Verstappen")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([
makeScoringEvent({ id: "e1", name: "Australian GP", eventStartsAt: null }),
]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
expect(result[0].earliestGameTime).toBeNull();
});
it("works for qualifying_points pattern with eventStartsAt", async () => {
const majorStart = new Date("2025-04-10T12:00:00.000Z");
const participants = [makeParticipant("g1", "Scheffler")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([
makeScoringEvent({ id: "e1", name: "The Masters", eventStartsAt: majorStart }),
]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "qualifying_points", participants, DATE_FROM, DATE_TO
);
expect(result[0].earliestGameTime).toBe(majorStart.toISOString());
});
it("earliestGameTime from eventStartsAt is used as sort key over eventDate", async () => {
// Event on Apr 6, game starts at 14:00 UTC — both should be reflected in earliestGameTime
const raceStart = new Date("2025-04-06T14:00:00.000Z");
const participants = [makeParticipant("p1", "Hamilton")];
mockDb.query.scoringEvents.findMany.mockResolvedValue([
makeScoringEvent({ id: "e1", name: "Bahrain GP", eventDate: "2025-04-06", eventStartsAt: raceStart }),
]);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "season_standings", participants, DATE_FROM, DATE_TO
);
// earliestGameTime carries the full timestamp so the sort key has time precision
expect(result[0].earliestGameTime).toBe("2025-04-06T14:00:00.000Z");
expect(result[0].eventDate).toBe("2025-04-06");
});
});
// ── Bracket: misc ──────────────────────────────────────────────────────────
describe("getUpcomingEventsForDraftedParticipants — bracket misc", () => {
function makeSelectChain(rows: unknown[]) {
return {
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
leftJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockResolvedValue(rows),
};
}
function makeMaxGameChain(rows: unknown[]) {
return {
from: vi.fn().mockReturnThis(),
innerJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue(rows),
};
}
beforeEach(() => vi.clearAllMocks());
it("excludes scored (isComplete) matches by passing the filter to the DB query", async () => {
// The DB mock returns rows only for matches the WHERE clause matches.
// When a match is complete (isComplete=true) the DB should return no rows.
// Simulate this by returning an empty result set.
// No second mockReturnValueOnce is needed: empty rows means eventIdsFromRows
// is empty so the max-game-number lookup is never called.
const chain = makeSelectChain([]);
mockDb.selectDistinct.mockReturnValueOnce(chain);
const result = await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(result).toHaveLength(0);
// The WHERE clause includes eq(schema.playoffMatches.isComplete, false).
// We verify the query was built without error (accessing the field on the mock).
expect(chain.where).toHaveBeenCalled();
});
it("uses leftJoin so events without any games are still considered", async () => {
const chain = makeSelectChain([]);
mockDb.selectDistinct
.mockReturnValueOnce(chain)
.mockReturnValueOnce(makeMaxGameChain([]));
await getUpcomingEventsForDraftedParticipants(
SPORTS_SEASON_ID, "playoff_bracket",
[makeParticipant("man-city", "Man City")],
DATE_FROM, DATE_TO
);
expect(chain.leftJoin).toHaveBeenCalled();
});
});