The junction table was a redundant second source of truth: syncTournamentResults already fans out by querying scoring_events.tournamentId directly, so the table only served the admin UI and could drift out of sync (causing orphaned events). - Drop sports_season_tournaments table and all link/unlink admin actions - Add getTournamentsBySportsSeason / getSportsSeasonsByTournament helpers that derive the same information from scoring_events.tournamentId - Add "Add Existing Tournament" dropdown to the events admin page (qualifying_points seasons only) — selecting a tournament creates the scoring event in one step - Fix clone: scoring events now carry tournamentId, fixing a latent check-constraint violation when cloning qualifying_points seasons - Tournament admin page "Linked Sports Seasons" is now a read-only derived view Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const mockFindMany = vi.fn();
|
|
|
|
const mockDb = {
|
|
query: {
|
|
scoringEvents: { findMany: mockFindMany },
|
|
},
|
|
};
|
|
|
|
vi.mock("~/database/context", () => ({
|
|
database: () => mockDb,
|
|
}));
|
|
|
|
vi.mock("~/database/schema", () => ({
|
|
scoringEvents: {
|
|
sportsSeasonId: "se.sports_season_id",
|
|
tournamentId: "se.tournament_id",
|
|
},
|
|
}));
|
|
|
|
vi.mock("drizzle-orm", () => ({
|
|
eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }),
|
|
and: (...args: unknown[]) => ({ type: "and", args }),
|
|
isNotNull: (col: unknown) => ({ type: "isNotNull", col }),
|
|
}));
|
|
|
|
import {
|
|
getTournamentsBySportsSeason,
|
|
getSportsSeasonsByTournament,
|
|
} from "../scoring-event";
|
|
|
|
const SEASON_ID = "ss-1";
|
|
const TOURNAMENT_ID_A = "t-a";
|
|
const TOURNAMENT_ID_B = "t-b";
|
|
|
|
const mockTournamentA = { id: TOURNAMENT_ID_A, name: "Wimbledon", year: 2026 };
|
|
const mockTournamentB = { id: TOURNAMENT_ID_B, name: "US Open", year: 2026 };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("getTournamentsBySportsSeason", () => {
|
|
it("returns one row per unique tournament, deduplicating multiple events for the same tournament", async () => {
|
|
mockFindMany.mockResolvedValue([
|
|
{ id: "e1", sportsSeasonId: SEASON_ID, tournamentId: TOURNAMENT_ID_A, tournament: mockTournamentA },
|
|
{ id: "e2", sportsSeasonId: SEASON_ID, tournamentId: TOURNAMENT_ID_A, tournament: mockTournamentA },
|
|
{ id: "e3", sportsSeasonId: SEASON_ID, tournamentId: TOURNAMENT_ID_B, tournament: mockTournamentB },
|
|
]);
|
|
|
|
const result = await getTournamentsBySportsSeason(SEASON_ID);
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].tournamentId).toBe(TOURNAMENT_ID_A);
|
|
expect(result[1].tournamentId).toBe(TOURNAMENT_ID_B);
|
|
});
|
|
|
|
it("returns empty array when no events have a tournamentId", async () => {
|
|
mockFindMany.mockResolvedValue([
|
|
{ id: "e1", sportsSeasonId: SEASON_ID, tournamentId: null, tournament: null },
|
|
]);
|
|
|
|
const result = await getTournamentsBySportsSeason(SEASON_ID);
|
|
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it("returns empty array when season has no events", async () => {
|
|
mockFindMany.mockResolvedValue([]);
|
|
|
|
const result = await getTournamentsBySportsSeason(SEASON_ID);
|
|
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
const SEASON_A_ID = "ss-a";
|
|
const SEASON_B_ID = "ss-b";
|
|
|
|
const mockSeasonA = { id: SEASON_A_ID, name: "Golf June 2026", year: 2026, scoringType: "majors", status: "active", sport: { id: "sp-1", name: "Golf" } };
|
|
const mockSeasonB = { id: SEASON_B_ID, name: "Golf Sept 2026", year: 2026, scoringType: "majors", status: "upcoming", sport: { id: "sp-1", name: "Golf" } };
|
|
|
|
describe("getSportsSeasonsByTournament", () => {
|
|
it("returns one row per unique sports season, deduplicating multiple events in the same season", async () => {
|
|
mockFindMany.mockResolvedValue([
|
|
{ id: "e1", sportsSeasonId: SEASON_A_ID, tournamentId: TOURNAMENT_ID_A, sportsSeason: mockSeasonA },
|
|
{ id: "e2", sportsSeasonId: SEASON_A_ID, tournamentId: TOURNAMENT_ID_A, sportsSeason: mockSeasonA },
|
|
{ id: "e3", sportsSeasonId: SEASON_B_ID, tournamentId: TOURNAMENT_ID_A, sportsSeason: mockSeasonB },
|
|
]);
|
|
|
|
const result = await getSportsSeasonsByTournament(TOURNAMENT_ID_A);
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].sportsSeasonId).toBe(SEASON_A_ID);
|
|
expect(result[1].sportsSeasonId).toBe(SEASON_B_ID);
|
|
});
|
|
|
|
it("returns empty array when no sports seasons use this tournament", async () => {
|
|
mockFindMany.mockResolvedValue([]);
|
|
|
|
const result = await getSportsSeasonsByTournament(TOURNAMENT_ID_A);
|
|
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it("passes tournamentId to the query", async () => {
|
|
mockFindMany.mockResolvedValue([]);
|
|
|
|
await getSportsSeasonsByTournament(TOURNAMENT_ID_A);
|
|
|
|
expect(mockFindMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({ type: "eq" }),
|
|
})
|
|
);
|
|
});
|
|
});
|