From efbd28b4467f05047d67e6d85e45169bfdf02f61 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:08:25 -0700 Subject: [PATCH] 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 --- .../__tests__/upcoming-calendar.test.ts | 23 ++++++++++++++++++- app/models/scoring-event.ts | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/models/__tests__/upcoming-calendar.test.ts b/app/models/__tests__/upcoming-calendar.test.ts index 83f7aa5..b8212fa 100644 --- a/app/models/__tests__/upcoming-calendar.test.ts +++ b/app/models/__tests__/upcoming-calendar.test.ts @@ -23,7 +23,7 @@ vi.mock("~/database/context", () => ({ 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" }, + 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" }, })); @@ -569,6 +569,27 @@ describe("getUpcomingEventsForDraftedParticipants — bracket misc", () => { 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 diff --git a/app/models/scoring-event.ts b/app/models/scoring-event.ts index bc65ce6..c77392f 100644 --- a/app/models/scoring-event.ts +++ b/app/models/scoring-event.ts @@ -444,6 +444,7 @@ export async function getUpcomingEventsForDraftedParticipants( and( eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId), eq(schema.scoringEvents.isComplete, false), + eq(schema.playoffMatches.isComplete, false), or( inArray(schema.playoffMatches.participant1Id, draftedIds), // participant2Id is nullable UUID — same underlying type as participant1Id;