import { describe, it, expect, vi, beforeEach } from "vitest"; import { F1StandingsAdapter, IndyCarStandingsAdapter } from "../f1"; // Minimal Jolpica driver standings response const SAMPLE_JOLPICA_STANDINGS = { MRData: { StandingsTable: { StandingsLists: [ { DriverStandings: [ { position: "1", points: "195", wins: "5", Driver: { driverId: "max_verstappen", givenName: "Max", familyName: "Verstappen" }, }, { position: "2", points: "152", wins: "2", Driver: { driverId: "norris", givenName: "Lando", familyName: "Norris" }, }, { position: "3", points: "133", wins: "1", Driver: { driverId: "leclerc", givenName: "Charles", familyName: "Leclerc" }, }, ], }, ], }, }, }; // Minimal ESPN IndyCar standings response (flat, single standings list) const SAMPLE_ESPN_INDYCAR = { standings: { entries: [ { athlete: { id: "3097", displayName: "Scott Dixon" }, stats: [ { name: "rank", value: 1 }, { name: "championshipPts", value: 437 }, { name: "wins", value: 3 }, { name: "starts", value: 10 }, ], }, { athlete: { id: "3098", displayName: "Alex Palou" }, stats: [ { name: "rank", value: 2 }, { name: "championshipPts", value: 398 }, { name: "wins", value: 2 }, { name: "starts", value: 10 }, ], }, ], }, }; // IndyCar response nested under children (alternate ESPN shape) const SAMPLE_ESPN_INDYCAR_NESTED = { children: [ { standings: { entries: SAMPLE_ESPN_INDYCAR.standings.entries, }, }, ], }; describe("F1StandingsAdapter", () => { beforeEach(() => { vi.stubGlobal("fetch", vi.fn()); }); it("returns one record per driver with correct names", async () => { vi.mocked(fetch) .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS } as Response); const adapter = new F1StandingsAdapter(); const records = await adapter.fetchStandings(); expect(records).toHaveLength(3); expect(records[0].teamName).toBe("Max Verstappen"); expect(records[1].teamName).toBe("Lando Norris"); }); it("sets externalTeamId to driverId", async () => { vi.mocked(fetch) .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS } as Response); const records = await new F1StandingsAdapter().fetchStandings(); expect(records[0].externalTeamId).toBe("max_verstappen"); expect(records[2].externalTeamId).toBe("leclerc"); }); it("maps championship points to currentPoints", async () => { vi.mocked(fetch) .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS } as Response); const records = await new F1StandingsAdapter().fetchStandings(); expect(records[0].currentPoints).toBe(195); expect(records[1].currentPoints).toBe(152); }); it("sets leagueRank to championship position", async () => { vi.mocked(fetch) .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS } as Response); const records = await new F1StandingsAdapter().fetchStandings(); expect(records[0].leagueRank).toBe(1); expect(records[2].leagueRank).toBe(3); }); it("sets gamesPlayed to 0 (not stored for season_standings sports)", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS, } as Response); const records = await new F1StandingsAdapter().fetchStandings(); expect(records[0].gamesPlayed).toBe(0); expect(records[1].gamesPlayed).toBe(0); }); it("sets winPct to 0 (not stored for season_standings sports)", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_JOLPICA_STANDINGS, } as Response); const records = await new F1StandingsAdapter().fetchStandings(); expect(records[0].winPct).toBe(0); }); it("throws when standings fetch fails", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, status: 500, statusText: "Internal Server Error", } as Response); await expect(new F1StandingsAdapter().fetchStandings()).rejects.toThrow("500"); }); it("throws when API returns empty standings", async () => { const empty = { MRData: { StandingsTable: { StandingsLists: [] } } }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => empty, } as Response); await expect(new F1StandingsAdapter().fetchStandings()).rejects.toThrow(/no entries/i); }); }); describe("IndyCarStandingsAdapter", () => { beforeEach(() => { vi.stubGlobal("fetch", vi.fn()); }); it("parses flat ESPN standings", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records).toHaveLength(2); expect(records[0].teamName).toBe("Scott Dixon"); expect(records[1].teamName).toBe("Alex Palou"); }); it("parses nested ESPN standings (children[0].standings)", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR_NESTED, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records).toHaveLength(2); expect(records[0].teamName).toBe("Scott Dixon"); }); it("sets externalTeamId to ESPN athlete id", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records[0].externalTeamId).toBe("3097"); }); it("maps championship points to currentPoints", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records[0].currentPoints).toBe(437); expect(records[1].currentPoints).toBe(398); }); it("sets leagueRank from rank stat", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records[0].leagueRank).toBe(1); expect(records[1].leagueRank).toBe(2); }); it("sets gamesPlayed from starts stat", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records[0].gamesPlayed).toBe(10); }); it("computes winPct as wins / starts", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_ESPN_INDYCAR, } as Response); const records = await new IndyCarStandingsAdapter().fetchStandings(); expect(records[0].winPct).toBeCloseTo(3 / 10, 5); }); it("throws when API returns HTTP error", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, status: 404, statusText: "Not Found", } as Response); await expect(new IndyCarStandingsAdapter().fetchStandings()).rejects.toThrow("404"); }); it("throws when API returns no entries", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ standings: { entries: [] } }), } as Response); await expect(new IndyCarStandingsAdapter().fetchStandings()).rejects.toThrow( /no entries/i ); }); });