73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
import { ApiError, ApiValidationError, createApiClient } from "../src/api/client";
|
|
|
|
function mockFetch(status: number, body: unknown) {
|
|
return vi.fn(
|
|
async (_url: RequestInfo | URL, _init?: RequestInit) =>
|
|
new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
}
|
|
|
|
describe("typed API client", () => {
|
|
it("returns a valid response, typed", async () => {
|
|
const fetch = mockFetch(200, { realTime: 1000, gameTime: 60_000 });
|
|
const api = createApiClient({ fetch });
|
|
const time = await api.call("getServerTime", {});
|
|
expect(time).toEqual({ realTime: 1000, gameTime: 60_000 });
|
|
expectTypeOf(time.realTime).toEqualTypeOf<number>();
|
|
expect(fetch).toHaveBeenCalledWith("/api/time", expect.objectContaining({ method: "GET" }));
|
|
});
|
|
|
|
it("raises a validation error when the response breaks its schema", async () => {
|
|
const api = createApiClient({ fetch: mockFetch(200, { realTime: "soon" }) });
|
|
await expect(api.call("getServerTime", {})).rejects.toBeInstanceOf(ApiValidationError);
|
|
});
|
|
|
|
it("fills path parameters and sends the rest as a JSON body", async () => {
|
|
const pilot = {
|
|
id: "p1",
|
|
name: "Rae Okafor",
|
|
isAvatar: false,
|
|
xp: 0,
|
|
level: 0,
|
|
skills: {},
|
|
skillPointsAvailable: 0,
|
|
assignedShipId: "s1",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
};
|
|
const fetch = mockFetch(200, { pilot });
|
|
const api = createApiClient({ fetch });
|
|
await api.call("assignPilot", { pilotId: "p1", shipId: "s1" });
|
|
|
|
const [url, init] = fetch.mock.calls[0]!;
|
|
expect(url).toBe("/api/pilots/p1/assign");
|
|
expect(init).toMatchObject({ method: "POST", credentials: "include" });
|
|
expect(JSON.parse(init!.body as string)).toEqual({ shipId: "s1" });
|
|
});
|
|
|
|
it("puts path parameters in GET URLs", async () => {
|
|
const fetch = mockFetch(200, { contracts: [], periodEndsAt: 7_200_000 });
|
|
await createApiClient({ fetch }).call("getContractBoard", { locationId: "moon" });
|
|
expect(fetch.mock.calls[0]![0]).toBe("/api/locations/moon/board");
|
|
});
|
|
|
|
it("throws the server's error code and message", async () => {
|
|
const fetch = mockFetch(409, {
|
|
error: { code: "insufficient-funds", message: "Not enough credits." },
|
|
});
|
|
const error = await createApiClient({ fetch })
|
|
.call("buyShip", { modelId: "courier" })
|
|
.catch((e: unknown) => e);
|
|
expect(error).toBeInstanceOf(ApiError);
|
|
expect(error).toMatchObject({
|
|
status: 409,
|
|
code: "insufficient-funds",
|
|
message: "Not enough credits.",
|
|
});
|
|
});
|
|
});
|