178 lines
6.1 KiB
TypeScript
178 lines
6.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("../scoring-calculator", () => ({
|
|
recalculateAffectedLeagues: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../qualifying-points", async (importOriginal) => {
|
|
const actual = await importOriginal();
|
|
return {
|
|
...(actual as Record<string, unknown>),
|
|
recalculateParticipantQP: vi.fn(),
|
|
};
|
|
});
|
|
|
|
import { deleteScoringEvent } from "../scoring-event";
|
|
|
|
describe("deleteScoringEvent", () => {
|
|
it("does not write majorsCompleted on delete (it is derived on read)", async () => {
|
|
// majorsCompleted is no longer a stored counter — it is computed via
|
|
// getMajorsCompleted from completed qualifying events. Deleting a qualifying
|
|
// event must therefore never issue a sportsSeasons.majorsCompleted update.
|
|
const setCalls: unknown[] = [];
|
|
const deleteChain = { where: vi.fn().mockResolvedValue(undefined) };
|
|
const updateChain = {
|
|
set: vi.fn((values: unknown) => {
|
|
setCalls.push(values);
|
|
return { where: vi.fn().mockResolvedValue(undefined) };
|
|
}),
|
|
};
|
|
const db = {
|
|
query: {
|
|
scoringEvents: {
|
|
findFirst: vi.fn().mockResolvedValue({
|
|
id: "event-1",
|
|
sportsSeasonId: "sports-season-1",
|
|
eventType: "major_tournament",
|
|
isQualifyingEvent: true,
|
|
}),
|
|
},
|
|
eventResults: {
|
|
findMany: vi.fn().mockResolvedValue([
|
|
{
|
|
seasonParticipantId: "participant-1",
|
|
placement: 20,
|
|
qualifyingPointsAwarded: "0.00",
|
|
},
|
|
]),
|
|
},
|
|
},
|
|
transaction: vi.fn(async (callback) => callback(db)),
|
|
delete: vi.fn(() => deleteChain),
|
|
update: vi.fn(() => updateChain),
|
|
} as any;
|
|
|
|
await deleteScoringEvent("event-1", db);
|
|
|
|
expect(setCalls).not.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ majorsCompleted: expect.anything() }),
|
|
])
|
|
);
|
|
});
|
|
|
|
describe("shared tournament handling", () => {
|
|
// Builds a mock db for a non-qualifying schedule_event linked to a tournament,
|
|
// so the delete skips the QP/league branches and exercises only the
|
|
// shared-tournament bookkeeping.
|
|
function makeSharedDb({
|
|
deletedEvent,
|
|
remaining,
|
|
siblingTournamentId = "tournament-1",
|
|
}: {
|
|
deletedEvent: { tournamentId: string | null; isPrimary: boolean };
|
|
remaining: Array<{ id: string; isPrimary: boolean }>;
|
|
siblingTournamentId?: string;
|
|
}) {
|
|
const findFirst = vi
|
|
.fn()
|
|
// 1st call: the event being deleted
|
|
.mockResolvedValueOnce({
|
|
id: "event-1",
|
|
sportsSeasonId: "sports-season-1",
|
|
eventType: "schedule_event",
|
|
isQualifyingEvent: false,
|
|
...deletedEvent,
|
|
})
|
|
// subsequent calls: setPrimaryEvent looking up the promoted event
|
|
.mockResolvedValue({ id: "promoted", tournamentId: siblingTournamentId });
|
|
|
|
const deleteWhere = vi.fn().mockResolvedValue(undefined);
|
|
const updateWhere = vi.fn().mockResolvedValue(undefined);
|
|
const db = {
|
|
query: {
|
|
scoringEvents: {
|
|
findFirst,
|
|
findMany: vi.fn().mockResolvedValue(remaining),
|
|
},
|
|
},
|
|
transaction: vi.fn(async (callback) => callback(db)),
|
|
delete: vi.fn(() => ({ where: deleteWhere })),
|
|
update: vi.fn(() => ({ set: vi.fn(() => ({ where: updateWhere })) })),
|
|
} as any;
|
|
return { db, deleteWhere };
|
|
}
|
|
|
|
it("keeps the tournament and promotes nothing when a non-primary window is deleted", async () => {
|
|
const { db } = makeSharedDb({
|
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: false },
|
|
remaining: [{ id: "event-2", isPrimary: true }],
|
|
});
|
|
|
|
const result = await deleteScoringEvent("event-1", db);
|
|
|
|
expect(result.remainingWindows).toBe(1);
|
|
expect(result.promotedPrimaryId).toBeNull();
|
|
expect(result.deletedTournament).toBe(false);
|
|
});
|
|
|
|
it("does not promote a primary when a golf-style tournament (no primary) loses a window", async () => {
|
|
// Golf-style shared majors intentionally have no primary window — every
|
|
// linked event is isPrimary=false. Deleting one must not flip a sibling
|
|
// into a primary, which would change its scoring/guard behavior.
|
|
const { db } = makeSharedDb({
|
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: false },
|
|
remaining: [
|
|
{ id: "event-2", isPrimary: false },
|
|
{ id: "event-3", isPrimary: false },
|
|
],
|
|
});
|
|
|
|
const result = await deleteScoringEvent("event-1", db);
|
|
|
|
expect(result.promotedPrimaryId).toBeNull();
|
|
expect(result.remainingWindows).toBe(2);
|
|
});
|
|
|
|
it("promotes the earliest remaining window when the primary window is deleted", async () => {
|
|
const { db } = makeSharedDb({
|
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
|
// findMany is ordered asc(createdAt); first is the earliest.
|
|
remaining: [
|
|
{ id: "event-2", isPrimary: false },
|
|
{ id: "event-3", isPrimary: false },
|
|
],
|
|
});
|
|
|
|
const result = await deleteScoringEvent("event-1", db);
|
|
|
|
expect(result.promotedPrimaryId).toBe("event-2");
|
|
expect(result.remainingWindows).toBe(2);
|
|
});
|
|
|
|
it("leaves the orphaned tournament intact when the last window is deleted without the flag", async () => {
|
|
const { db } = makeSharedDb({
|
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
|
remaining: [],
|
|
});
|
|
|
|
const result = await deleteScoringEvent("event-1", db);
|
|
|
|
expect(result.remainingWindows).toBe(0);
|
|
expect(result.deletedTournament).toBe(false);
|
|
});
|
|
|
|
it("deletes the orphaned tournament when the last window is deleted with the flag", async () => {
|
|
const { db } = makeSharedDb({
|
|
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
|
remaining: [],
|
|
});
|
|
|
|
const result = await deleteScoringEvent("event-1", db, {
|
|
deleteOrphanTournament: true,
|
|
});
|
|
|
|
expect(result.deletedTournament).toBe(true);
|
|
});
|
|
});
|
|
});
|