Files

85 lines
3.6 KiB
TypeScript

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<string, string>();
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<string, string>) {
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" }
});
}