334 lines
10 KiB
TypeScript
334 lines
10 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||
|
|
|
||
|
|
vi.mock("~/database/context", () => ({
|
||
|
|
database: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
import {
|
||
|
|
upsertRegularSeasonStandings,
|
||
|
|
upsertManualStanding,
|
||
|
|
getRegularSeasonStandings,
|
||
|
|
getLastSyncedAt,
|
||
|
|
deleteRegularSeasonStandings,
|
||
|
|
getParticipantStanding,
|
||
|
|
} from "../regular-season-standings";
|
||
|
|
import { database } from "~/database/context";
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Helpers
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
function makeUpsertDb(returnedRows: object[] = []) {
|
||
|
|
const returning = vi.fn().mockResolvedValue(returnedRows);
|
||
|
|
const onConflictDoUpdate = vi.fn().mockReturnValue({ returning });
|
||
|
|
const values = vi.fn().mockReturnValue({ onConflictDoUpdate });
|
||
|
|
const insert = vi.fn().mockReturnValue({ values });
|
||
|
|
const db: Record<string, unknown> = {
|
||
|
|
insert,
|
||
|
|
transaction: vi.fn().mockImplementation(async (fn: (tx: unknown) => unknown) => fn(db)),
|
||
|
|
};
|
||
|
|
return { db, insert, values, onConflictDoUpdate, returning };
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeRecord(overrides: Record<string, unknown> = {}) {
|
||
|
|
return {
|
||
|
|
participantId: "p-1",
|
||
|
|
sportsSeasonId: "ss-1",
|
||
|
|
wins: 10,
|
||
|
|
losses: 5,
|
||
|
|
gamesPlayed: 15,
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// upsertRegularSeasonStandings
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
describe("upsertRegularSeasonStandings", () => {
|
||
|
|
it("returns empty array immediately when given no records", async () => {
|
||
|
|
const { db } = makeUpsertDb();
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
const result = await upsertRegularSeasonStandings([]);
|
||
|
|
expect(result).toEqual([]);
|
||
|
|
expect(db.insert).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("maps winPct and gamesBack to strings", async () => {
|
||
|
|
const { db, values } = makeUpsertDb([]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await upsertRegularSeasonStandings([
|
||
|
|
makeRecord({ winPct: 0.667, gamesBack: 2.5 }),
|
||
|
|
]);
|
||
|
|
|
||
|
|
const inserted = values.mock.calls[0][0];
|
||
|
|
expect(inserted[0].winPct).toBe("0.667");
|
||
|
|
expect(inserted[0].gamesBack).toBe("2.5");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("maps null winPct and gamesBack to null", async () => {
|
||
|
|
const { db, values } = makeUpsertDb([]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await upsertRegularSeasonStandings([
|
||
|
|
makeRecord({ winPct: null, gamesBack: null }),
|
||
|
|
]);
|
||
|
|
|
||
|
|
const inserted = values.mock.calls[0][0];
|
||
|
|
expect(inserted[0].winPct).toBeNull();
|
||
|
|
expect(inserted[0].gamesBack).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("uses the provided db instead of calling database()", async () => {
|
||
|
|
const { db } = makeUpsertDb([]);
|
||
|
|
|
||
|
|
await upsertRegularSeasonStandings([makeRecord()], db as never);
|
||
|
|
|
||
|
|
expect(database).not.toHaveBeenCalled();
|
||
|
|
expect(db.transaction).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns the rows from the DB", async () => {
|
||
|
|
const row = { id: "row-1", wins: 10 };
|
||
|
|
const { db } = makeUpsertDb([row]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
const result = await upsertRegularSeasonStandings([makeRecord()]);
|
||
|
|
expect(result).toEqual([row]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("inserts multiple records in a single call", async () => {
|
||
|
|
const { db, values } = makeUpsertDb([]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await upsertRegularSeasonStandings([
|
||
|
|
makeRecord({ participantId: "p-1" }),
|
||
|
|
makeRecord({ participantId: "p-2" }),
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect(values.mock.calls[0][0]).toHaveLength(2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("sets syncedAt to now when not provided", async () => {
|
||
|
|
const before = new Date();
|
||
|
|
const { db, values } = makeUpsertDb([]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await upsertRegularSeasonStandings([makeRecord()]);
|
||
|
|
|
||
|
|
const inserted = values.mock.calls[0][0];
|
||
|
|
expect(inserted[0].syncedAt).toBeInstanceOf(Date);
|
||
|
|
expect(inserted[0].syncedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// upsertManualStanding
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
describe("upsertManualStanding", () => {
|
||
|
|
it("sets syncedAt to null", async () => {
|
||
|
|
const { db, values } = makeUpsertDb([{ id: "row-1" }]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await upsertManualStanding("p-1", "ss-1", {
|
||
|
|
wins: 10,
|
||
|
|
losses: 5,
|
||
|
|
gamesPlayed: 15,
|
||
|
|
});
|
||
|
|
|
||
|
|
const inserted = values.mock.calls[0][0];
|
||
|
|
expect(inserted[0].syncedAt).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns the first row", async () => {
|
||
|
|
const row = { id: "row-1" };
|
||
|
|
const { db } = makeUpsertDb([row]);
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
const result = await upsertManualStanding("p-1", "ss-1", {
|
||
|
|
wins: 10,
|
||
|
|
losses: 5,
|
||
|
|
gamesPlayed: 15,
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(result).toEqual(row);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// getRegularSeasonStandings — sorting logic
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
function makeRow(overrides: Record<string, unknown>) {
|
||
|
|
return {
|
||
|
|
conference: null,
|
||
|
|
division: null,
|
||
|
|
divisionRank: null,
|
||
|
|
leagueRank: null,
|
||
|
|
participant: {},
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function makeQueryDb(rows: object[]) {
|
||
|
|
return {
|
||
|
|
query: {
|
||
|
|
regularSeasonStandings: {
|
||
|
|
findMany: vi.fn().mockResolvedValue(rows),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("getRegularSeasonStandings", () => {
|
||
|
|
it("sorts by conference alphabetically", async () => {
|
||
|
|
const rows = [
|
||
|
|
makeRow({ conference: "Western", leagueRank: 1 }),
|
||
|
|
makeRow({ conference: "Eastern", leagueRank: 2 }),
|
||
|
|
];
|
||
|
|
vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);
|
||
|
|
|
||
|
|
const result = await getRegularSeasonStandings("ss-1");
|
||
|
|
expect(result[0].conference).toBe("Eastern");
|
||
|
|
expect(result[1].conference).toBe("Western");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("sorts by division within conference", async () => {
|
||
|
|
const rows = [
|
||
|
|
makeRow({ conference: "East", division: "Southeast", leagueRank: 1 }),
|
||
|
|
makeRow({ conference: "East", division: "Atlantic", leagueRank: 2 }),
|
||
|
|
];
|
||
|
|
vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);
|
||
|
|
|
||
|
|
const result = await getRegularSeasonStandings("ss-1");
|
||
|
|
expect(result[0].division).toBe("Atlantic");
|
||
|
|
expect(result[1].division).toBe("Southeast");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("sorts by divisionRank within division", async () => {
|
||
|
|
const rows = [
|
||
|
|
makeRow({ conference: "East", division: "Atlantic", divisionRank: 3 }),
|
||
|
|
makeRow({ conference: "East", division: "Atlantic", divisionRank: 1 }),
|
||
|
|
makeRow({ conference: "East", division: "Atlantic", divisionRank: 2 }),
|
||
|
|
];
|
||
|
|
vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);
|
||
|
|
|
||
|
|
const result = await getRegularSeasonStandings("ss-1");
|
||
|
|
expect(result.map((r) => r.divisionRank)).toEqual([1, 2, 3]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("falls back to leagueRank when divisionRank is null", async () => {
|
||
|
|
const rows = [
|
||
|
|
makeRow({ leagueRank: 3 }),
|
||
|
|
makeRow({ leagueRank: 1 }),
|
||
|
|
makeRow({ leagueRank: 2 }),
|
||
|
|
];
|
||
|
|
vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);
|
||
|
|
|
||
|
|
const result = await getRegularSeasonStandings("ss-1");
|
||
|
|
expect(result.map((r) => r.leagueRank)).toEqual([1, 2, 3]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("places null conference/division rows last", async () => {
|
||
|
|
const rows = [
|
||
|
|
makeRow({ conference: null, leagueRank: 1 }),
|
||
|
|
makeRow({ conference: "Eastern", leagueRank: 2 }),
|
||
|
|
];
|
||
|
|
vi.mocked(database).mockReturnValue(makeQueryDb(rows) as never);
|
||
|
|
|
||
|
|
const result = await getRegularSeasonStandings("ss-1");
|
||
|
|
expect(result[0].conference).toBe("Eastern");
|
||
|
|
expect(result[1].conference).toBeNull();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// getLastSyncedAt
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
describe("getLastSyncedAt", () => {
|
||
|
|
function makeSelectDb(lastSync: Date | null) {
|
||
|
|
return {
|
||
|
|
select: vi.fn().mockReturnValue({
|
||
|
|
from: vi.fn().mockReturnValue({
|
||
|
|
where: vi.fn().mockResolvedValue([{ lastSync }]),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
it("returns the max syncedAt date", async () => {
|
||
|
|
const date = new Date("2025-03-01T00:00:00Z");
|
||
|
|
vi.mocked(database).mockReturnValue(makeSelectDb(date) as never);
|
||
|
|
|
||
|
|
const result = await getLastSyncedAt("ss-1");
|
||
|
|
expect(result).toEqual(date);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns null when no synced records exist", async () => {
|
||
|
|
vi.mocked(database).mockReturnValue(makeSelectDb(null) as never);
|
||
|
|
|
||
|
|
const result = await getLastSyncedAt("ss-1");
|
||
|
|
expect(result).toBeNull();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// deleteRegularSeasonStandings
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
describe("deleteRegularSeasonStandings", () => {
|
||
|
|
it("calls delete on the DB", async () => {
|
||
|
|
const where = vi.fn().mockResolvedValue(undefined);
|
||
|
|
const del = vi.fn().mockReturnValue({ where });
|
||
|
|
const db = { delete: del };
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
await deleteRegularSeasonStandings("ss-1");
|
||
|
|
expect(del).toHaveBeenCalled();
|
||
|
|
expect(where).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// getParticipantStanding
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
describe("getParticipantStanding", () => {
|
||
|
|
it("returns the standing row when found", async () => {
|
||
|
|
const row = { id: "row-1", participantId: "p-1" };
|
||
|
|
const db = {
|
||
|
|
query: {
|
||
|
|
regularSeasonStandings: {
|
||
|
|
findFirst: vi.fn().mockResolvedValue(row),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
const result = await getParticipantStanding("p-1", "ss-1");
|
||
|
|
expect(result).toEqual(row);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns undefined when not found", async () => {
|
||
|
|
const db = {
|
||
|
|
query: {
|
||
|
|
regularSeasonStandings: {
|
||
|
|
findFirst: vi.fn().mockResolvedValue(undefined),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
vi.mocked(database).mockReturnValue(db as never);
|
||
|
|
|
||
|
|
const result = await getParticipantStanding("p-99", "ss-1");
|
||
|
|
expect(result).toBeUndefined();
|
||
|
|
});
|
||
|
|
});
|