75 lines
2.9 KiB
JavaScript
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 });
|
|
}
|