brackt/app/models/__tests__/tournament.test.ts
Claude 7f7ea4e29d
Improve shared-tournament/event management UX
Deleting an event from a season only affects that season's scoring
event; the shared canonical tournament and the other linked seasons
survive. The old flow never communicated this and offered no way to
manage the relationship, so it felt like deleting an event might wipe
the whole shared tournament.

- deleteScoringEvent is now shared-tournament-aware: it auto-heals the
  primary window (promotes the earliest remaining window when the
  primary is removed) and optionally deletes the canonical tournament
  when the last linked window is removed. Returns a result describing
  what happened.
- Add deleteTournament model helper.
- Events page: delete confirmation now explains exactly what a delete
  does (removed from this season only vs. last window), with an opt-in
  checkbox to also remove the orphaned shared tournament.
- Tournament page: add a per-window "Remove" control on the Linked
  Sports Seasons card to unlink a season, reusing the auto-heal path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBzjCLVkVaQMj1MF3K54t2
2026-07-02 05:17:37 +00:00

198 lines
5.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("~/database/context", () => ({
database: vi.fn(),
}));
import {
createTournament,
getTournamentById,
findTournamentsBySport,
findTournamentBySportNameYear,
upsertTournament,
updateTournamentStatus,
deleteTournament,
} from "../tournament";
import { database } from "~/database/context";
const SPORT_ID = "sport-1";
const TOURNAMENT_ID = "tournament-1";
const SAMPLE_TOURNAMENT = {
id: TOURNAMENT_ID,
sportId: SPORT_ID,
name: "Australian Open",
year: 2026,
startsAt: new Date("2026-01-15T00:00:00Z"),
endsAt: new Date("2026-01-28T00:00:00Z"),
surface: "hard",
location: "Melbourne",
status: "scheduled" as const,
externalKey: "ao-2026",
createdAt: new Date("2026-01-01T00:00:00Z"),
updatedAt: new Date("2026-01-01T00:00:00Z"),
};
function makeInsertDb(returnValue: object) {
return {
insert: vi.fn().mockReturnValue({
values: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([returnValue]),
}),
}),
};
}
function makeSelectDb(rows: object[]) {
return {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue(rows),
orderBy: vi.fn().mockResolvedValue(rows),
}),
}),
}),
};
}
function makeUpdateDb(returnValue: object) {
return {
update: vi.fn().mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([returnValue]),
}),
}),
}),
};
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("createTournament", () => {
it("inserts a tournament and returns it", async () => {
vi.mocked(database).mockReturnValue(makeInsertDb(SAMPLE_TOURNAMENT) as never);
const result = await createTournament({
sportId: SPORT_ID,
name: "Australian Open",
year: 2026,
});
expect(result).toEqual(SAMPLE_TOURNAMENT);
});
});
describe("getTournamentById", () => {
it("returns the tournament when found", async () => {
vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_TOURNAMENT]) as never);
const result = await getTournamentById(TOURNAMENT_ID);
expect(result).toEqual(SAMPLE_TOURNAMENT);
});
it("returns null when not found", async () => {
vi.mocked(database).mockReturnValue(makeSelectDb([]) as never);
const result = await getTournamentById("nonexistent");
expect(result).toBeNull();
});
});
describe("findTournamentsBySport", () => {
it("returns tournaments ordered by year and date", async () => {
const tournaments = [SAMPLE_TOURNAMENT];
vi.mocked(database).mockReturnValue(makeSelectDb(tournaments) as never);
const result = await findTournamentsBySport(SPORT_ID);
expect(result).toEqual(tournaments);
});
});
describe("findTournamentBySportNameYear", () => {
it("returns the tournament when found", async () => {
vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_TOURNAMENT]) as never);
const result = await findTournamentBySportNameYear(SPORT_ID, "Australian Open", 2026);
expect(result).toEqual(SAMPLE_TOURNAMENT);
});
it("returns null when not found", async () => {
vi.mocked(database).mockReturnValue(makeSelectDb([]) as never);
const result = await findTournamentBySportNameYear(SPORT_ID, "Wimbledon", 2026);
expect(result).toBeNull();
});
});
describe("upsertTournament", () => {
it("returns existing tournament if found", async () => {
vi.mocked(database).mockReturnValue(makeSelectDb([SAMPLE_TOURNAMENT]) as never);
const result = await upsertTournament({
sportId: SPORT_ID,
name: "Australian Open",
year: 2026,
});
expect(result).toEqual(SAMPLE_TOURNAMENT);
});
it("creates new tournament if not found", async () => {
let callCount = 0;
vi.mocked(database).mockImplementation(() => {
callCount++;
return (callCount === 1 ? makeSelectDb([]) : makeInsertDb(SAMPLE_TOURNAMENT)) as never;
});
const result = await upsertTournament({
sportId: SPORT_ID,
name: "Australian Open",
year: 2026,
});
expect(result).toEqual(SAMPLE_TOURNAMENT);
});
});
describe("updateTournamentStatus", () => {
it("updates status and returns the tournament", async () => {
const updated = { ...SAMPLE_TOURNAMENT, status: "in_progress" as const };
vi.mocked(database).mockReturnValue(makeUpdateDb(updated) as never);
const result = await updateTournamentStatus(TOURNAMENT_ID, "in_progress");
expect(result.status).toBe("in_progress");
});
});
describe("deleteTournament", () => {
it("deletes the tournament row by id", async () => {
const where = vi.fn().mockResolvedValue(undefined);
const db = { delete: vi.fn().mockReturnValue({ where }) };
vi.mocked(database).mockReturnValue(db as never);
await deleteTournament(TOURNAMENT_ID);
expect(db.delete).toHaveBeenCalledTimes(1);
expect(where).toHaveBeenCalledTimes(1);
});
it("uses a provided db when passed (transaction)", async () => {
const where = vi.fn().mockResolvedValue(undefined);
const providedDb = { delete: vi.fn().mockReturnValue({ where }) };
await deleteTournament(TOURNAMENT_ID, providedDb as never);
expect(providedDb.delete).toHaveBeenCalledTimes(1);
expect(vi.mocked(database)).not.toHaveBeenCalled();
});
});