260 lines
7.8 KiB
TypeScript
260 lines
7.8 KiB
TypeScript
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: "points", value: 437 },
|
|
{ name: "wins", value: 3 },
|
|
{ name: "starts", value: 10 },
|
|
],
|
|
},
|
|
{
|
|
athlete: { id: "3098", displayName: "Alex Palou" },
|
|
stats: [
|
|
{ name: "rank", value: 2 },
|
|
{ name: "points", 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
|
|
);
|
|
});
|
|
});
|