/** * clear-bracket is the only path that can tear down a bracket, so the guard around it * matters: it discards recorded results and the placements derived from them. */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { findPlayoffMatchesByEventId, deletePlayoffMatchesByEventId, } from "~/models/playoff-match"; import { deleteParticipantResultsBySportsSeasonId } from "~/models/participant-result"; import { recalculateAffectedLeagues } from "~/models/scoring-calculator"; import { getScoringEventById } from "~/models/scoring-event"; import { action } from "../admin.sports-seasons.$id.events.$eventId.bracket.server"; 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()), findPlayoffMatchesByEventId: vi.fn(), deletePlayoffMatchesByEventId: vi.fn(), })); vi.mock("~/models/participant-result", async (importOriginal) => ({ ...(await importOriginal()), deleteParticipantResultsBySportsSeasonId: vi.fn(), })); vi.mock("~/models/scoring-calculator", async (importOriginal) => ({ ...(await importOriginal()), recalculateAffectedLeagues: vi.fn(), })); const EVENT = { id: "event-1", sportsSeasonId: "season-1" }; const params = { id: "season-1", eventId: "event-1" }; function clearRequest(confirm?: string): Request { const body = new FormData(); body.set("intent", "clear-bracket"); if (confirm !== undefined) body.set("confirm", confirm); return new Request("http://localhost/clear", { method: "POST", body }); } function match(isComplete: boolean) { return { id: `m-${Math.random()}`, isComplete }; } // The action's real signature carries React Router's generated types; the clear path // only reads request and params. const run = (request: Request) => (action as unknown as (args: { request: Request; params: typeof params }) => Promise<{ error?: string; success?: string; }>)({ request, params }); describe("clear-bracket", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(getScoringEventById).mockResolvedValue( EVENT as unknown as Awaited> ); vi.mocked(deletePlayoffMatchesByEventId).mockResolvedValue(undefined); vi.mocked(deleteParticipantResultsBySportsSeasonId).mockResolvedValue(undefined); vi.mocked(recalculateAffectedLeagues).mockResolvedValue( undefined as unknown as Awaited> ); }); it("deletes the matches and the placements derived from them", async () => { vi.mocked(findPlayoffMatchesByEventId).mockResolvedValue([ match(false), match(false), ] as unknown as Awaited>); const result = await run(clearRequest()); expect(result.success).toContain("2 match(es) removed"); expect(deletePlayoffMatchesByEventId).toHaveBeenCalledWith("event-1"); expect(deleteParticipantResultsBySportsSeasonId).toHaveBeenCalledWith("season-1"); }); it("refuses to discard completed matches without confirmation", async () => { vi.mocked(findPlayoffMatchesByEventId).mockResolvedValue([ match(true), match(false), ] as unknown as Awaited>); const result = await run(clearRequest()); expect(result.error).toContain("1 completed match(es)"); expect(deletePlayoffMatchesByEventId).not.toHaveBeenCalled(); expect(deleteParticipantResultsBySportsSeasonId).not.toHaveBeenCalled(); }); it("discards completed matches once confirmed", async () => { vi.mocked(findPlayoffMatchesByEventId).mockResolvedValue([ match(true), ] as unknown as Awaited>); const result = await run(clearRequest("true")); expect(result.success).toBeDefined(); expect(deletePlayoffMatchesByEventId).toHaveBeenCalledWith("event-1"); }); it("rejects an event with no bracket rather than reporting a no-op success", async () => { vi.mocked(findPlayoffMatchesByEventId).mockResolvedValue( [] as unknown as Awaited> ); const result = await run(clearRequest("true")); expect(result.error).toContain("no bracket to clear"); expect(deletePlayoffMatchesByEventId).not.toHaveBeenCalled(); }); });