75 lines
2.9 KiB
JavaScript

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 });
}