Adds CRUD modules for the canonical tables created in commit 775b905.
Each module mirrors existing app/models conventions (database() from
~/database/context, schema from ~/database/schema, mock-based tests).
Key implementation notes:
- participant.ts exports use "Canonical" prefix (CanonicalParticipant,
createCanonicalParticipant, etc.) to avoid collision with existing
season-participant.ts exports
- All four models include comprehensive unit tests following the
audit-log.test.ts pattern
- Tests use mocked db responses (no real database access)
- Upsert functions use onConflictDoUpdate for appropriate unique constraints
Part of Phase 1b of canonical tournament layer migration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
174 lines
4.6 KiB
TypeScript
174 lines
4.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
vi.mock("~/database/context", () => ({
|
|
database: vi.fn(),
|
|
}));
|
|
|
|
import {
|
|
createTournament,
|
|
getTournamentById,
|
|
findTournamentsBySport,
|
|
findTournamentBySportNameYear,
|
|
upsertTournament,
|
|
updateTournamentStatus,
|
|
} 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");
|
|
});
|
|
});
|