/** * generate-bracket banks the floors a seeding guarantees before anyone plays (an AFL * top-4 seed cannot finish below the 5th-6th tier). Those floors only reach * teamStandings.totalPoints through a standings recalculation, so the action has to be * sure one ran — markEliminatedAndAnnounce runs one for its Discord announcement in some * cases but not others. */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { generateBracketFromTemplate } from "~/models/playoff-match"; import { findParticipantResultsBySportsSeasonId, setParticipantResult, } from "~/models/participant-result"; import { applyBracketEntryFloors, recalculateAffectedLeagues, } from "~/models/scoring-calculator"; import { getScoringEventById, updateScoringEvent } from "~/models/scoring-event"; import { findParticipantsBySportsSeasonId } from "~/models/season-participant"; import { action } from "../admin.sports-seasons.$id.events.$eventId.bracket.server"; vi.mock("~/database/context", () => ({ database: vi.fn(() => ({})) })); vi.mock("~/models/scoring-event", async (importOriginal) => ({ ...(await importOriginal()), getScoringEventById: vi.fn(), updateScoringEvent: vi.fn(), isReadOnlySibling: vi.fn(() => false), })); vi.mock("~/models/playoff-match", async (importOriginal) => ({ ...(await importOriginal()), generateBracketFromTemplate: vi.fn(), })); vi.mock("~/models/participant-result", async (importOriginal) => ({ ...(await importOriginal()), findParticipantResultsBySportsSeasonId: vi.fn(), setParticipantResult: vi.fn(), })); vi.mock("~/models/scoring-calculator", async (importOriginal) => ({ ...(await importOriginal()), applyBracketEntryFloors: vi.fn(), recalculateAffectedLeagues: vi.fn(), })); vi.mock("~/models/season-participant", async (importOriginal) => ({ ...(await importOriginal()), findParticipantsBySportsSeasonId: vi.fn(), })); const params = { id: "season-1", eventId: "event-1" }; const EVENT = { id: "event-1", name: "AFL Finals", sportsSeasonId: "season-1", isQualifyingEvent: false, bracketTemplateId: "afl_10", }; /** afl_10 takes exactly 10 seeded participants. */ const SEEDED = Array.from({ length: 10 }, (_, i) => `seed-${i + 1}`); function generateRequest(): Request { const body = new FormData(); body.set("intent", "generate-bracket"); body.set("templateId", "afl_10"); SEEDED.forEach((id, i) => body.set(`participant${i}`, id)); return new Request("http://localhost/generate", { method: "POST", body }); } const run = (request: Request) => (action as unknown as (args: { request: Request; params: typeof params }) => Promise<{ error?: string; success?: string; }>)({ request, params }); /** * @param extras participants in the season beyond the 10 seeded into the bracket — * these are the ones generate-bracket marks eliminated. * @param withExistingResults ids that already carry a result row, so * markEliminatedAndAnnounce treats them as not newly eliminated. */ function setSeason(extras: string[], withExistingResults: string[] = []) { vi.mocked(findParticipantsBySportsSeasonId).mockResolvedValue( [...SEEDED, ...extras].map((id) => ({ id })) as unknown as Awaited< ReturnType > ); vi.mocked(findParticipantResultsBySportsSeasonId).mockResolvedValue( withExistingResults.map((participantId) => ({ participantId })) as unknown as Awaited< ReturnType > ); } describe("generate-bracket entry-floor standings recalculation", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(getScoringEventById).mockResolvedValue( EVENT as unknown as Awaited> ); vi.mocked(generateBracketFromTemplate).mockResolvedValue( undefined as unknown as Awaited> ); vi.mocked(updateScoringEvent).mockResolvedValue( undefined as unknown as Awaited> ); vi.mocked(setParticipantResult).mockResolvedValue( undefined as unknown as Awaited> ); vi.mocked(recalculateAffectedLeagues).mockResolvedValue( undefined as unknown as Awaited> ); // afl_10 seeds 1-4 into the Qualifying Finals, whose entry floor is the 5th-6th tier. vi.mocked(applyBracketEntryFloors).mockResolvedValue(4); }); it("recalculates when every eliminated team already had a result row", async () => { // The second run of a generation: the first wrote position 0 for the non-bracket // participants, so nobody is *newly* eliminated and the announcement is skipped. // The floors banked moments ago would never reach the standings. setSeason(["extra-1"], ["extra-1"]); const result = await run(generateRequest()); expect(result.success).toBeDefined(); expect(recalculateAffectedLeagues).toHaveBeenCalledTimes(1); expect(recalculateAffectedLeagues).toHaveBeenCalledWith( "season-1", expect.anything(), expect.objectContaining({ skipDiscord: true }) ); }); it("recalculates for a qualifying event, which never announces eliminations", async () => { vi.mocked(getScoringEventById).mockResolvedValue( { ...EVENT, isQualifyingEvent: true } as unknown as Awaited< ReturnType > ); setSeason(["extra-1"]); await run(generateRequest()); expect(recalculateAffectedLeagues).toHaveBeenCalledTimes(1); expect(recalculateAffectedLeagues).toHaveBeenCalledWith( "season-1", expect.anything(), expect.objectContaining({ skipDiscord: true }) ); }); it("recalculates when the bracket field is the whole season", async () => { setSeason([]); await run(generateRequest()); expect(recalculateAffectedLeagues).toHaveBeenCalledTimes(1); }); it("recalculates when the elimination announcement threw", async () => { // The announcement is best-effort and its failure is swallowed — but a failed recalc // is exactly when the floors still need one. setSeason(["extra-1"]); vi.mocked(recalculateAffectedLeagues) .mockRejectedValueOnce(new Error("discord down")) .mockResolvedValue(undefined as unknown as Awaited>); const result = await run(generateRequest()); expect(result.success).toBeDefined(); expect(recalculateAffectedLeagues).toHaveBeenCalledTimes(2); expect(recalculateAffectedLeagues).toHaveBeenLastCalledWith( "season-1", expect.anything(), expect.objectContaining({ skipDiscord: true }) ); }); it("does not recalculate twice when the announcement already did", async () => { setSeason(["extra-1"]); await run(generateRequest()); expect(recalculateAffectedLeagues).toHaveBeenCalledTimes(1); // The announcing call, not the floor fallback. expect(recalculateAffectedLeagues).toHaveBeenCalledWith( "season-1", expect.anything(), expect.objectContaining({ eliminatedParticipantIds: ["extra-1"] }) ); }); it("does not recalculate at all when no floors were banked", async () => { // A template that guarantees nothing at seeding: no floors, nobody to eliminate, // so there is nothing for a recalculation to pick up. vi.mocked(applyBracketEntryFloors).mockResolvedValue(0); setSeason([]); await run(generateRequest()); expect(recalculateAffectedLeagues).not.toHaveBeenCalled(); }); });