import { describe, it, expect, vi, beforeEach } from "vitest"; import { NbaStandingsAdapter } from "../nba"; function makeStat(name: string, value: number, displayValue?: string) { return { name, value, displayValue: displayValue ?? String(value) }; } const SAMPLE_NBA_RESPONSE = { children: [ { name: "Eastern Conference", children: [ { name: "Atlantic Division", standings: { entries: [ { team: { id: "2", displayName: "Boston Celtics", abbreviation: "BOS" }, stats: [ makeStat("wins", 52), makeStat("losses", 16), makeStat("winPercent", 0.765), makeStat("gamesBehind", 0), makeStat("playoffSeed", 1), { name: "streak", value: 5, displayValue: "W5" }, makeStat("homeWins", 28), makeStat("homeLosses", 7), makeStat("awayWins", 24), makeStat("awayLosses", 9), ], }, { team: { id: "7", displayName: "Toronto Raptors", abbreviation: "TOR" }, stats: [ makeStat("wins", 22), makeStat("losses", 46), makeStat("winPercent", 0.324), makeStat("gamesBehind", 30), makeStat("playoffSeed", 12), { name: "streak", value: 2, displayValue: "L2" }, makeStat("homeWins", 12), makeStat("homeLosses", 22), makeStat("awayWins", 10), makeStat("awayLosses", 24), ], }, ], }, }, ], }, { name: "Western Conference", children: [ { name: "Northwest Division", standings: { entries: [ { team: { id: "21", displayName: "Oklahoma City Thunder", abbreviation: "OKC" }, stats: [ makeStat("wins", 58), makeStat("losses", 10), makeStat("winPercent", 0.853), makeStat("gamesBehind", 0), makeStat("playoffSeed", 1), { name: "streak", value: 4, displayValue: "W4" }, makeStat("homeWins", 30), makeStat("homeLosses", 4), makeStat("awayWins", 28), makeStat("awayLosses", 6), ], }, ], }, }, ], }, ], }; describe("NbaStandingsAdapter", () => { beforeEach(() => { vi.stubGlobal("fetch", vi.fn()); }); it("maps ESPN API response to FetchedStandingsRecord[]", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_NBA_RESPONSE, } as Response); const adapter = new NbaStandingsAdapter(); const records = await adapter.fetchStandings(); expect(records).toHaveLength(3); const okc = records.find((r) => r.teamName === "Oklahoma City Thunder"); if (!okc) throw new Error("Oklahoma City Thunder record not found"); expect(okc).toBeDefined(); expect(okc.wins).toBe(58); expect(okc.losses).toBe(10); expect(okc.conference).toBe("Western Conference"); expect(okc.division).toBe("Northwest Division"); expect(okc.otLosses).toBeUndefined(); }); it("assigns conference correctly", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_NBA_RESPONSE, } as Response); const adapter = new NbaStandingsAdapter(); const records = await adapter.fetchStandings(); const bos = records.find((r) => r.teamName === "Boston Celtics"); if (!bos) throw new Error("Boston Celtics record not found"); expect(bos.conference).toBe("Eastern Conference"); expect(bos.division).toBe("Atlantic Division"); }); it("extracts streak from stats array", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_NBA_RESPONSE, } as Response); const adapter = new NbaStandingsAdapter(); const records = await adapter.fetchStandings(); const bos = records.find((r) => r.teamName === "Boston Celtics"); if (!bos) throw new Error("Boston Celtics record not found"); expect(bos.streak).toBe("W5"); const tor = records.find((r) => r.teamName === "Toronto Raptors"); if (!tor) throw new Error("Toronto Raptors record not found"); expect(tor.streak).toBe("L2"); }); it("does not set otLosses (NBA has no OT losses)", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_NBA_RESPONSE, } as Response); const adapter = new NbaStandingsAdapter(); const records = await adapter.fetchStandings(); for (const record of records) { expect(record.otLosses).toBeUndefined(); } }); it("throws on non-ok response", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, status: 429, statusText: "Too Many Requests", } as Response); const adapter = new NbaStandingsAdapter(); await expect(adapter.fetchStandings()).rejects.toThrow("429"); }); it("throws when no entries returned", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ children: [] }), } as Response); const adapter = new NbaStandingsAdapter(); await expect(adapter.fetchStandings()).rejects.toThrow("no entries"); }); });