brackt/app/services/standings-sync/__tests__/sync.test.ts

191 lines
6.8 KiB
TypeScript
Raw Normal View History

import { describe, it, expect, vi, afterEach } from "vitest";
import { findMatchingTeamName } from "~/lib/normalize-team-name";
import { OcBlacktopIndyCarStandingsAdapter } from "../f1";
import type { FetchedStandingsRecord, StandingsSyncAdapter } from "../types";
// Pure unit tests for the name-matching logic used by syncStandings.
// The full orchestrator requires DB context; those are covered by integration tests.
describe("findMatchingTeamName (sync name-matching logic)", () => {
const participants = [
"Boston Celtics",
"Los Angeles Lakers",
"Golden State Warriors",
"Oklahoma City Thunder",
"San Antonio Spurs",
"New York Knicks",
];
it("matches exact names", () => {
expect(findMatchingTeamName("Boston Celtics", participants)).toBe("Boston Celtics");
expect(findMatchingTeamName("New York Knicks", participants)).toBe("New York Knicks");
});
it("is case-insensitive", () => {
expect(findMatchingTeamName("golden state warriors", participants)).toBe(
"Golden State Warriors"
);
expect(findMatchingTeamName("SAN ANTONIO SPURS", participants)).toBe("San Antonio Spurs");
});
it("matches partial city+team via substring", () => {
expect(findMatchingTeamName("Golden State", participants)).toBe("Golden State Warriors");
expect(findMatchingTeamName("Oklahoma City", participants)).toBe("Oklahoma City Thunder");
// "New York" is a substring of "New York Knicks"
expect(findMatchingTeamName("New York", participants)).toBe("New York Knicks");
});
it("returns null for truly unmatched names", () => {
expect(findMatchingTeamName("Phoenix Suns", participants)).toBeNull();
expect(findMatchingTeamName("Charlotte Hornets", participants)).toBeNull();
});
it("returns null for empty string", () => {
expect(findMatchingTeamName("", participants)).toBeNull();
});
it("handles extra whitespace gracefully", () => {
expect(findMatchingTeamName(" Boston Celtics ", participants)).toBe("Boston Celtics");
});
});
describe("OcBlacktopIndyCarStandingsAdapter", () => {
afterEach(() => {
vi.restoreAllMocks();
});
// A fallback that records whether it was invoked and returns a sentinel result.
function makeFallback(): StandingsSyncAdapter & { called: boolean } {
const stub = {
called: false,
async fetchStandings(): Promise<FetchedStandingsRecord[]> {
stub.called = true;
return [
{
teamName: "ESPN Fallback Driver",
externalTeamId: "espn-1",
wins: 0,
losses: 0,
winPct: 0,
gamesPlayed: 0,
leagueRank: 1,
currentPoints: 1,
},
];
},
};
return stub;
}
it("maps OC Blacktop driver standings to FetchedStandingsRecord", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(
JSON.stringify([
{ id: "ocb-palou", position: 1, points: "409.00", firstName: "Alex", lastName: "Palou" },
{ id: "ocb-kirkwood", position: 2, points: "348.00", firstName: "Kyle", lastName: "Kirkwood" },
]),
{ status: 200, headers: { "Content-Type": "application/json" } }
)
);
const fallback = makeFallback();
const adapter = new OcBlacktopIndyCarStandingsAdapter("test-key", fallback);
const records = await adapter.fetchStandings();
expect(fallback.called).toBe(false);
expect(records).toEqual([
expect.objectContaining({
teamName: "Alex Palou",
externalTeamId: "ocb-palou",
leagueRank: 1,
currentPoints: 409,
}),
expect.objectContaining({
teamName: "Kyle Kirkwood",
externalTeamId: "ocb-kirkwood",
leagueRank: 2,
currentPoints: 348,
}),
]);
});
it("falls back to ESPN when the API responds non-OK", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("nope", { status: 500, statusText: "Internal Server Error" })
);
const fallback = makeFallback();
const adapter = new OcBlacktopIndyCarStandingsAdapter("test-key", fallback);
const records = await adapter.fetchStandings();
expect(fallback.called).toBe(true);
expect(records[0].teamName).toBe("ESPN Fallback Driver");
});
it("falls back to ESPN when the payload is empty", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("[]", { status: 200, headers: { "Content-Type": "application/json" } })
);
const fallback = makeFallback();
const adapter = new OcBlacktopIndyCarStandingsAdapter("test-key", fallback);
await adapter.fetchStandings();
expect(fallback.called).toBe(true);
});
it("falls back to ESPN when the payload is missing expected fields (schema drift)", async () => {
// Non-empty array, but `points` was renamed upstream — must fail over, not
// silently sync zeros over real standings.
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(
JSON.stringify([
{ id: "ocb-palou", position: 1, championshipPoints: "409.00", firstName: "Alex", lastName: "Palou" },
]),
{ status: 200, headers: { "Content-Type": "application/json" } }
)
);
const fallback = makeFallback();
const adapter = new OcBlacktopIndyCarStandingsAdapter("test-key", fallback);
const records = await adapter.fetchStandings();
expect(fallback.called).toBe(true);
expect(records[0].teamName).toBe("ESPN Fallback Driver");
});
it("keeps legitimate zero-point drivers (0 points is valid, not a failure)", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(
JSON.stringify([
{ id: "ocb-1", position: 1, points: "409.00", firstName: "Alex", lastName: "Palou" },
{ id: "ocb-2", position: 33, points: "0.00", firstName: "Rookie", lastName: "Driver" },
]),
{ status: 200, headers: { "Content-Type": "application/json" } }
)
);
const fallback = makeFallback();
const adapter = new OcBlacktopIndyCarStandingsAdapter("test-key", fallback);
const records = await adapter.fetchStandings();
expect(fallback.called).toBe(false);
expect(records[1]).toEqual(
expect.objectContaining({ teamName: "Rookie Driver", leagueRank: 33, currentPoints: 0 })
);
});
it("falls back to ESPN when no API key is configured", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");
const fallback = makeFallback();
// Empty string is falsy but (unlike undefined) does not trigger the env default.
const adapter = new OcBlacktopIndyCarStandingsAdapter("", fallback);
const records = await adapter.fetchStandings();
expect(fetchSpy).not.toHaveBeenCalled();
expect(fallback.called).toBe(true);
expect(records[0].teamName).toBe("ESPN Fallback Driver");
});
});