import { cleanup, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { http, HttpResponse, delay } from "msw"; import { beforeEach, afterEach, describe, expect, test, vi } from "vitest"; import { StrictMode } from "react"; import { AuthGate } from "./AuthGate"; import { clearAuthState, getAuthGeneration, getAuthState, setAuthState } from "./authState"; import { server } from "../test/msw"; vi.mock("../shell/AppShell", () => ({ AppShell: () => (
Authenticated shell
), })); const user = { issuer: "local", subject: "user-1", displayName: "Analyst", roles: ["user"] as const, permissions: ["session.use"], isAdmin: false, csrfToken: "c".repeat(43), session: { method: "local" as const, remembered: false, idleExpiresAt: "2026-08-17T10:00:00.000Z", absoluteExpiresAt: "2026-08-17T20:00:00.000Z", }, }; const localConfig = { mode: "local", localLogin: true, oidcLogin: false }; beforeEach(() => { clearAuthState(); server.use( http.get("/api/auth/config", () => HttpResponse.json(localConfig)), http.get("/api/me", () => HttpResponse.json(user)), ); }); afterEach(() => { cleanup(); clearAuthState(); }); describe("AuthGate", () => { test("shows a loading state while /me is unresolved", async () => { server.use(http.get("/api/me", async () => { await delay(100); return HttpResponse.json(user); })); render(); expect(screen.getByRole("status", { name: /checking access/i })).toBeInTheDocument(); expect(screen.queryByTestId("authenticated-shell")).not.toBeInTheDocument(); }); test("renders the authenticated shell from the safe /me DTO", async () => { render(); expect(await screen.findByTestId("authenticated-shell")).toBeInTheDocument(); expect(screen.queryByRole("heading", { name: /sign in/i })).not.toBeInTheDocument(); }); test("returns to local login after an expired session 401", async () => { server.use(http.get("/api/me", () => new HttpResponse(null, { status: 401 }))); render(); expect(await screen.findByRole("heading", { name: /sign in to thothii/i })).toBeInTheDocument(); expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); }); test("presents a forbidden /me response explicitly", async () => { setAuthState(user); const generation = getAuthGeneration(); server.use(http.get("/api/me", () => HttpResponse.json( { code: "auth_not_authorized", error: "This operation is not permitted" }, { status: 403 }, ))); render(); expect(await screen.findByRole("heading", { name: /access not permitted/i })).toBeInTheDocument(); expect(screen.getByText(/signed in without permission/i)).toBeInTheDocument(); expect(getAuthState()).toMatchObject({ subject: "user-1", csrfToken: "c".repeat(43) }); expect(getAuthGeneration()).toBe(generation); }); test("offers retry when the authentication provider is unavailable", async () => { let attempts = 0; server.use( http.get("/api/me", () => { attempts += 1; return attempts === 1 ? HttpResponse.json({ code: "auth_unavailable" }, { status: 503 }) : HttpResponse.json(user); }), ); render(); expect(await screen.findByRole("heading", { name: /authentication unavailable/i })).toBeInTheDocument(); await userEvent.click(screen.getByRole("button", { name: /retry/i })); expect(await screen.findByTestId("authenticated-shell")).toBeInTheDocument(); }); test("accepts the nullable legacy /me session shape without inventing a token", async () => { server.use(http.get("/api/me", () => HttpResponse.json({ issuer: "portal", subject: "legacy-user", displayName: "Legacy user", roles: ["user"], permissions: ["session.use"], isAdmin: false, csrfToken: null, session: null, }))); render(); expect(await screen.findByTestId("authenticated-shell")).toBeInTheDocument(); }); test("does not write credentials or tokens to browser storage", async () => { const storageWrites = (["setItem", "removeItem", "clear"] as const).map((method) => vi.spyOn(Storage.prototype, method)); render(); await screen.findByTestId("authenticated-shell"); for (const write of storageWrites) expect(write).not.toHaveBeenCalled(); for (const write of storageWrites) write.mockRestore(); }); test("does not emit act warnings while StrictMode authenticates", async () => { const errors = vi.spyOn(console, "error").mockImplementation(() => undefined); try { render(); expect(await screen.findByTestId("authenticated-shell")).toBeInTheDocument(); expect(errors.mock.calls.flat().join(" ")).not.toMatch(/not wrapped in act/i); } finally { errors.mockRestore(); } }); });