| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- /* @vitest-environment node */
- import { describe, it, expect, vi, beforeEach } from "vitest";
- import { apiFetch, ApiClientError, getFiles, login } from "./apiClient.js";
- beforeEach(() => {
- vi.restoreAllMocks();
- global.fetch = vi.fn();
- });
- describe("lib/frontend/apiClient", () => {
- it("apiFetch uses credentials=include and cache=no-store", async () => {
- fetch.mockResolvedValue(
- new Response(JSON.stringify({ ok: true }), {
- status: 200,
- headers: { "Content-Type": "application/json" },
- })
- );
- await apiFetch("/api/health");
- expect(fetch).toHaveBeenCalledTimes(1);
- const [url, init] = fetch.mock.calls[0];
- expect(url).toBe("/api/health");
- expect(init.credentials).toBe("include");
- expect(init.cache).toBe("no-store");
- });
- it("apiFetch serializes JSON bodies and sets Content-Type", async () => {
- fetch.mockResolvedValue(
- new Response(JSON.stringify({ ok: true }), {
- status: 200,
- headers: { "Content-Type": "application/json" },
- })
- );
- await login({ username: "u", password: "p" });
- const [, init] = fetch.mock.calls[0];
- expect(init.method).toBe("POST");
- expect(init.headers.Accept).toBe("application/json");
- expect(init.headers["Content-Type"]).toBe("application/json");
- expect(init.body).toBe(JSON.stringify({ username: "u", password: "p" }));
- });
- it("apiFetch throws ApiClientError for standardized backend error payloads", async () => {
- fetch.mockResolvedValue(
- new Response(
- JSON.stringify({
- error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
- }),
- { status: 401, headers: { "Content-Type": "application/json" } }
- )
- );
- await expect(apiFetch("/api/branches")).rejects.toMatchObject({
- name: "ApiClientError",
- status: 401,
- code: "AUTH_UNAUTHENTICATED",
- message: "Unauthorized",
- });
- });
- it("apiFetch maps network failures to CLIENT_NETWORK_ERROR", async () => {
- fetch.mockRejectedValue(new Error("connection refused"));
- try {
- await apiFetch("/api/branches");
- throw new Error("Expected apiFetch to throw");
- } catch (err) {
- expect(err).toBeInstanceOf(ApiClientError);
- expect(err.code).toBe("CLIENT_NETWORK_ERROR");
- expect(err.status).toBe(0);
- }
- });
- it("getFiles builds the expected query string", async () => {
- fetch.mockResolvedValue(
- new Response(
- JSON.stringify({
- branch: "NL01",
- year: "2024",
- month: "10",
- day: "23",
- files: [],
- }),
- { status: 200, headers: { "Content-Type": "application/json" } }
- )
- );
- await getFiles("NL01", "2024", "10", "23");
- const [url] = fetch.mock.calls[0];
- // We do not rely on param ordering beyond URLSearchParams defaults.
- expect(url).toContain("/api/files?");
- expect(url).toContain("branch=NL01");
- expect(url).toContain("year=2024");
- expect(url).toContain("month=10");
- expect(url).toContain("day=23");
- });
- });
|