import { beforeEach, describe, expect, it } from "vitest"; import { clearToken, getAuth, setToken } from "../auth/token.ts"; import { json, mockFetch } from "../test/fetchMock.ts"; import { api, ApiError, AuthError, checkToken, dropTokenOn, HubDownError } from "./client.ts"; describe("api", () => { beforeEach(() => setToken("tok")); it("шлёт токен и параметры, пустые параметры выбрасывает", async () => { const fetch = mockFetch(() => json(200, { issues: [] })); await api.get("/issues", { sprint: "S 2", q: "", epic: null, limit: 50 }); const [url, init] = fetch.mock.calls[0]; expect(url).toBe("/issues?sprint=S+2&limit=50"); expect((init!.headers as Record).Authorization).toBe("Bearer tok"); expect(init!.cache).toBe("no-store"); }); it("PUT отправляет JSON", async () => { const fetch = mockFetch((_m, _u, body) => json(200, body)); expect(await api.put("/issues/A-1/note", { rest_text: "x" })).toEqual({ rest_text: "x" }); expect((fetch.mock.calls[0][1]!.headers as Record)["Content-Type"]).toBe("application/json; charset=utf-8"); }); it("AuthError хранит токен запроса; dropTokenOn не трогает новый токен (T3.5)", async () => { mockFetch(() => json(401, { error: "unauthorized" })); const e = await api.get("/board").catch((err: unknown) => err) as AuthError; expect(e).toBeInstanceOf(AuthError); expect(e.token).toBe("tok"); setToken("new-tok"); dropTokenOn(e); expect(getAuth()).toEqual({ token: "new-tok", reason: "none" }); setToken("tok"); dropTokenOn(e); expect(getAuth()).toEqual({ token: "", reason: "bad" }); }); it("401 — AuthError, 5xx и сеть — HubDownError, прочие 4xx — ApiError с detail", async () => { mockFetch(() => json(401, { error: "unauthorized" })); await expect(api.get("/board")).rejects.toBeInstanceOf(AuthError); mockFetch(() => json(503, {})); await expect(api.get("/board")).rejects.toBeInstanceOf(HubDownError); mockFetch(() => { throw new TypeError("Failed to fetch"); }); await expect(api.get("/board")).rejects.toBeInstanceOf(HubDownError); mockFetch(() => json(422, { error: "bad_note", detail: "неизвестные поля заметки: x" })); const e = await api.put("/issues/A-1/note", { x: 1 }).catch((err: unknown) => err); expect(e).toBeInstanceOf(ApiError); expect(e).toMatchObject({ status: 422, error: "bad_note", detail: "неизвестные поля заметки: x" }); }); it("без токена — AuthError без запроса", async () => { clearToken(); const fetch = mockFetch(() => json(200, {})); await expect(api.get("/board")).rejects.toBeInstanceOf(AuthError); expect(fetch).not.toHaveBeenCalled(); }); it("токен с недопустимыми символами — AuthError без запроса", async () => { setToken("ток ен"); const fetch = mockFetch(() => json(200, {})); await expect(api.get("/board")).rejects.toBeInstanceOf(AuthError); expect(fetch).not.toHaveBeenCalled(); }); it("checkToken проверяет переданный токен, не сохранённый", async () => { const fetch = mockFetch(() => json(200, { kind_labels: {} })); await checkToken("candidate"); expect((fetch.mock.calls[0][1]!.headers as Record).Authorization).toBe("Bearer candidate"); expect(fetch.mock.calls[0][0]).toBe("/settings"); }); });