Files
xiaobaifupan/next/frontend/src/shared/api/client.test.ts
T

68 lines
2.0 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { api, ApiError } from "./client";
afterEach(() => {
vi.unstubAllGlobals();
});
describe("api client", () => {
it("uses the single API prefix", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ status: "ok" }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
await expect(api.get<{ status: string }>("/health")).resolves.toEqual({ status: "ok" });
expect(fetchMock).toHaveBeenCalledWith(
"/api/health",
expect.objectContaining({ credentials: "same-origin" }),
);
});
it("normalizes safe server errors", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(
JSON.stringify({
error: { code: "unavailable", message: "暂不可用", request_id: "abc" },
}),
{ status: 503, headers: { "Content-Type": "application/json" } },
),
),
);
const promise = api.get("/health");
await expect(promise).rejects.toBeInstanceOf(ApiError);
await expect(promise).rejects.toMatchObject({
message: "暂不可用",
status: 503,
code: "unavailable",
requestId: "abc",
});
});
it("adds the readable CSRF cookie to write requests", async () => {
vi.stubGlobal("document", { cookie: "xiaobai_csrf=csrf-token-123" });
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ message: "ok" }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
await api.patch("/account/password", { value: "changed" });
const request = fetchMock.mock.calls[0]?.[1] as RequestInit;
const headers = request.headers as Headers;
expect(request.method).toBe("PATCH");
expect(headers.get("X-CSRF-Token")).toBe("csrf-token-123");
expect(headers.get("Content-Type")).toBe("application/json");
});
});