import { afterEach, describe, expect, it, vi } from "vitest"; import { setPlatformApiSessionToken } from "../api/client"; import { rolesFromBackendRoles } from "../contracts/workspace"; import { currentUserFromResponse, loadCurrentUser, localFallbackUser } from "./session"; describe("session store helpers", () => { afterEach(() => { setPlatformApiSessionToken(null); vi.restoreAllMocks(); vi.unstubAllGlobals(); }); it("does not grant platform administrator access for local fallback", () => { expect(localFallbackUser.roles).toEqual(["serverAdmin"]); expect(localFallbackUser.capabilities).not.toContain("users.manage"); }); it("maps backend platform admin roles only from API responses", () => { const user = currentUserFromResponse({ id: "user-admin", displayName: "Operator", status: "active", roles: ["platform-admin"] }, "api"); expect(user.roles).toEqual(["platformAdmin"]); expect(user.capabilities).toContain("users.manage"); expect(rolesFromBackendRoles(["server-admin"])).toEqual(["serverAdmin"]); }); it("returns null without a stored API session token", async () => { stubWindowStorage(new Map()); vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ code: "unauthorized" }, 401))); await expect(loadCurrentUser()).resolves.toBeNull(); }); it("restores an HttpOnly cookie session without a script-readable token", async () => { const storage = new Map(); stubWindowStorage(storage); const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => { expect(new Headers(init?.headers).has("Authorization")).toBe(false); expect(init?.credentials).toBe("same-origin"); return jsonResponse({ id: "user-admin", displayName: "Operator", status: "active", roles: ["platform-admin"] }); }); vi.stubGlobal("fetch", fetchMock); await expect(loadCurrentUser()).resolves.toMatchObject({ id: "user-admin", source: "api" }); expect(storage.size).toBe(0); }); it("loads current user with a stored API bearer token", async () => { const storage = new Map([["platform-web.session.apiToken", "session-token"]]); stubWindowStorage(storage); const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => { expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer session-token"); return jsonResponse({ id: "user-admin", displayName: "Operator", status: "active", roles: ["platform-admin"] }); }); vi.stubGlobal("fetch", fetchMock); await expect(loadCurrentUser()).resolves.toMatchObject({ id: "user-admin", roles: ["platformAdmin"], source: "api" }); expect(storage.get("platform-web.session.apiToken")).toBe("session-token"); }); it("clears stale stored API session tokens", async () => { const storage = new Map([["platform-web.session.apiToken", "stale-token"]]); stubWindowStorage(storage); vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ message: "authentication required" }, 401))); await expect(loadCurrentUser()).resolves.toBeNull(); expect(storage.has("platform-web.session.apiToken")).toBe(false); }); }); function stubWindowStorage(values: Map) { vi.stubGlobal("window", { localStorage: { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => values.set(key, value), removeItem: (key: string) => values.delete(key) } }); } function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } }); }