import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { ExternalApiError, externalRequest, readExternalCsrfToken, rememberExternalCsrfToken, setExternalUnauthorizedHandler } from "./client.js"; describe("externalRequest", () => { beforeEach(() => { rememberExternalCsrfToken(""); document.cookie = "hyapp_external_csrf=; Max-Age=0; Path=/"; }); afterEach(() => { setExternalUnauthorizedHandler(null); vi.unstubAllGlobals(); }); test("uses only the isolated cookie session and external API base", async () => { const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: { items: [] } })); vi.stubGlobal("fetch", fetchMock); await externalRequest("/app/users", { query: { keyword: "163000", page: 1 } }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe("/api/v1/external/app/users?keyword=163000&page=1"); expect(init.credentials).toBe("include"); expect(init.headers.get("Authorization")).toBeNull(); expect(init.headers.get("X-App-Code")).toBeNull(); expect(init.headers.get("X-CSRF-Token")).toBeNull(); }); test("adds CSRF to writes from the dedicated cookie", async () => { document.cookie = "hyapp_external_csrf=csrf%20token; Path=/"; const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: { ok: true } })); vi.stubGlobal("fetch", fetchMock); await externalRequest("/auth/change-password", { body: { currentPassword: "old", newPassword: "new" }, method: "POST" }); const [, init] = fetchMock.mock.calls[0]; expect(init.headers.get("X-CSRF-Token")).toBe("csrf token"); expect(JSON.parse(init.body)).toEqual({ currentPassword: "old", newPassword: "new" }); }); test("keeps csrfToken in memory when the API-scoped cookie is unreadable", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(jsonResponse({ code: 0, data: { csrfToken: "memory-csrf", user: { username: "operator" } } })) .mockResolvedValueOnce(jsonResponse({ code: 0, data: {} })); vi.stubGlobal("fetch", fetchMock); await externalRequest("/auth/me"); await externalRequest("/auth/logout", { method: "POST" }); expect(fetchMock.mock.calls[1][1].headers.get("X-CSRF-Token")).toBe("memory-csrf"); expect(readExternalCsrfToken("")).toBe("memory-csrf"); }); test("invalidates the external session on protected 401", async () => { const handler = vi.fn(); setExternalUnauthorizedHandler(handler); vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ code: "UNAUTHORIZED", message: "请登录" }, 401))); await expect(externalRequest("/admin/rooms")).rejects.toEqual(expect.objectContaining({ status: 401 })); expect(handler).toHaveBeenCalledTimes(1); expect(new ExternalApiError("x")).toBeInstanceOf(Error); }); }); function jsonResponse(body, status = 200) { return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, status }); }