Files
nxdns/web/src/auth/store.test.tsx
T

90 lines
2.8 KiB
TypeScript

import { act } from "react";
import { renderHook, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { AuthProvider, resetAuthProbeForTests, useAuth } from "@/auth/store";
const STORAGE_KEY = "nxdns_auth_required";
function wrapper({ children }: { children: ReactNode }) {
return <AuthProvider>{children}</AuthProvider>;
}
function jsonResponse(payload: unknown, status = 200, headers: Record<string, string> = {}): Response {
return new Response(JSON.stringify(payload), {
status,
headers: { "content-type": "application/json", ...headers },
});
}
beforeEach(() => {
sessionStorage.clear();
resetAuthProbeForTests();
});
afterEach(() => {
vi.unstubAllGlobals();
});
test("mounting with nothing stored probes and settles authRequired true on 401", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
if (String(input) === "/api/auth/login") return jsonResponse({ error: "password required" }, 401);
return jsonResponse({ error: "not stubbed" }, 404);
});
vi.stubGlobal("fetch", fetchMock);
const { result } = renderHook(() => useAuth(), { wrapper });
expect(result.current.authRequired).toBeNull();
await waitFor(() => expect(result.current.authRequired).toBe(true));
expect(sessionStorage.getItem(STORAGE_KEY)).toBe("true");
});
test("mounting with nothing stored probes and settles authRequired false when auth is off", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => jsonResponse({ authenticated: true, auth_required: false })),
);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.authRequired).toBe(false));
expect(sessionStorage.getItem(STORAGE_KEY)).toBe("false");
});
test("a stored value is the fast path: no probe fires on mount", async () => {
sessionStorage.setItem(STORAGE_KEY, "true");
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const { result } = renderHook(() => useAuth(), { wrapper });
expect(result.current.authRequired).toBe(true);
await act(async () => {});
expect(fetchMock).not.toHaveBeenCalled();
});
test("logout swallows a 401 from an already-dead session", async () => {
sessionStorage.setItem(STORAGE_KEY, "true");
vi.stubGlobal(
"fetch",
vi.fn(async () => jsonResponse({ error: "unauthorized" }, 401)),
);
const { result } = renderHook(() => useAuth(), { wrapper });
await expect(result.current.logout()).resolves.toBeUndefined();
});
test("logout rethrows non-401 errors such as 429", async () => {
sessionStorage.setItem(STORAGE_KEY, "true");
vi.stubGlobal(
"fetch",
vi.fn(async () => jsonResponse({ error: "rate limited" }, 429, { "retry-after": "7" })),
);
const { result } = renderHook(() => useAuth(), { wrapper });
await expect(result.current.logout()).rejects.toMatchObject({
name: "ApiError",
status: 429,
retryAfter: 7,
});
});