import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("~/lib/auth.server", () => ({ auth: { api: { getSession: vi.fn() } }, })); vi.mock("~/models/user", () => ({ isUserAdmin: vi.fn(), })); vi.mock("~/models/tournament", () => ({ getTournamentById: vi.fn(), updateTournamentStatus: vi.fn(), })); vi.mock("~/models/tournament-result", () => ({ getTournamentResults: vi.fn(), upsertTournamentResult: vi.fn(), })); vi.mock("~/models/participant", () => ({ findCanonicalParticipantsBySport: vi.fn(), })); vi.mock("~/services/sync-tournament-results", () => ({ syncTournamentResults: vi.fn(), })); vi.mock("~/lib/logger", () => ({ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, })); import { action } from "~/routes/admin.tournaments.$id"; import { auth } from "~/lib/auth.server"; import { isUserAdmin } from "~/models/user"; import { getTournamentById, updateTournamentStatus, } from "~/models/tournament"; import { upsertTournamentResult } from "~/models/tournament-result"; import { syncTournamentResults } from "~/services/sync-tournament-results"; const TOURNAMENT_ID = "tournament-1"; const ADMIN_USER_ID = "admin-1"; const NON_ADMIN_USER_ID = "user-1"; function makeRequest(body: Record) { const formData = new FormData(); for (const [k, v] of Object.entries(body)) { formData.append(k, v); } return new Request(`http://localhost/admin/tournaments/${TOURNAMENT_ID}`, { method: "POST", body: formData, }); } function makeActionArgs(request: Request) { return { request, params: { id: TOURNAMENT_ID }, context: {} as never, } as unknown as Parameters[0]; } describe("admin.tournaments.$id action", () => { beforeEach(() => { vi.clearAllMocks(); }); it("batch-upsert-results: upserts each row, marks tournament completed, and fans out", async () => { vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: ADMIN_USER_ID }, } as never); vi.mocked(isUserAdmin).mockResolvedValue(true); vi.mocked(getTournamentById).mockResolvedValue({ id: TOURNAMENT_ID, sportId: "sport-1", name: "US Open", year: 2026, status: "scheduled", } as never); vi.mocked(upsertTournamentResult).mockResolvedValue({} as never); vi.mocked(updateTournamentStatus).mockResolvedValue({} as never); const syncReport = { tournamentId: TOURNAMENT_ID, windowsSynced: 2, windowsFailed: 0, failures: [], }; vi.mocked(syncTournamentResults).mockResolvedValue(syncReport); const results = [ { participantId: "p-1", placement: 1 }, { participantId: "p-2", placement: 2 }, { participantId: "p-3", placement: 3 }, ]; const request = makeRequest({ intent: "batch-upsert-results", results: JSON.stringify(results), }); const response = await action(makeActionArgs(request)); expect(upsertTournamentResult).toHaveBeenCalledTimes(3); expect(upsertTournamentResult).toHaveBeenCalledWith({ tournamentId: TOURNAMENT_ID, participantId: "p-1", placement: 1, }); expect(updateTournamentStatus).toHaveBeenCalledWith( TOURNAMENT_ID, "completed" ); expect(syncTournamentResults).toHaveBeenCalledWith(TOURNAMENT_ID); expect(response).toMatchObject({ success: true, syncReport, }); }); it("does not re-update status when tournament is already completed", async () => { vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: ADMIN_USER_ID }, } as never); vi.mocked(isUserAdmin).mockResolvedValue(true); vi.mocked(getTournamentById).mockResolvedValue({ id: TOURNAMENT_ID, sportId: "sport-1", name: "US Open", year: 2026, status: "completed", } as never); vi.mocked(upsertTournamentResult).mockResolvedValue({} as never); vi.mocked(syncTournamentResults).mockResolvedValue({ tournamentId: TOURNAMENT_ID, windowsSynced: 1, windowsFailed: 0, failures: [], }); const request = makeRequest({ intent: "batch-upsert-results", results: JSON.stringify([{ participantId: "p-1", placement: 1 }]), }); await action(makeActionArgs(request)); expect(updateTournamentStatus).not.toHaveBeenCalled(); expect(syncTournamentResults).toHaveBeenCalledWith(TOURNAMENT_ID); }); it("non-admin user is forbidden (403)", async () => { vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: NON_ADMIN_USER_ID }, } as never); vi.mocked(isUserAdmin).mockResolvedValue(false); const request = makeRequest({ intent: "batch-upsert-results", results: JSON.stringify([{ participantId: "p-1", placement: 1 }]), }); await expect(action(makeActionArgs(request))).rejects.toMatchObject({ status: 403, }); expect(upsertTournamentResult).not.toHaveBeenCalled(); expect(syncTournamentResults).not.toHaveBeenCalled(); }); it("missing tournament returns 404", async () => { vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: ADMIN_USER_ID }, } as never); vi.mocked(isUserAdmin).mockResolvedValue(true); vi.mocked(getTournamentById).mockResolvedValue(null); const request = makeRequest({ intent: "batch-upsert-results", results: JSON.stringify([{ participantId: "p-1", placement: 1 }]), }); await expect(action(makeActionArgs(request))).rejects.toMatchObject({ status: 404, }); }); });