183 lines
5.4 KiB
TypeScript
183 lines
5.4 KiB
TypeScript
|
|
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<string, string>) {
|
||
|
|
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<typeof action>[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,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|