milestone 9: react spa admin ui, frontend ci and embedded dist
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user