Files
nxdns/admin/src/auth/LoginPage.test.tsx
T
mokhtar fa323c7ed4
Gates / test (push) Successful in 1m40s
Gates / package (push) Successful in 3m58s
Gates / container (push) Successful in 14s
CI / gates (push) Successful in 12m51s
Gates / frontend (push) Successful in 1m18s
Gates / test-aarch64 (push) Successful in 6m57s
milestone 29: activity — history, live and policy simulation on one surface
query log, live and lookup merge into /activity. history filters live
in the url, so a pasted link or back/forward reproduces the exact
view; the result column separates servfail and nxdomain from success
in the list. live is follow-by-default with freeze, and a streamed
row opens its in-memory provenance detail — no correlation invented
for rows sqlite has not written. lookup survives as the current
policy simulation under /activity/test. investigation links carry
absolute bounds, and the diagnostics page now honors since/until
instead of ignoring them. the old routes are gone without aliases.
2026-08-22 10:52:56 +02:00

98 lines
3.3 KiB
TypeScript

import { act } from "react";
import { QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
import { safeRedirect } from "@/auth/LoginPage";
import { AuthProvider, resetAuthProbeForTests } from "@/auth/store";
import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";
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();
vi.useRealTimers();
});
async function flushAll() {
for (let i = 0; i < 20; i++) {
await act(async () => {
vi.advanceTimersByTime(0);
await Promise.resolve();
});
}
}
function renderLoginRoute() {
const queryClient = createQueryClient();
const router = createAppRouter(createMemoryHistory({ initialEntries: ["/login"] }), queryClient);
render(
<AuthProvider>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</AuthProvider>,
);
}
test("429 login shows a ticking countdown and keeps submit disabled until it ends", async () => {
vi.useFakeTimers();
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) !== "/api/auth/login") return jsonResponse({ error: "not stubbed" }, 404);
const body = JSON.parse(String(init?.body)) as { password: string };
if (body.password === "") return jsonResponse({ error: "password required" }, 401);
return jsonResponse({ error: "rate limited" }, 429, { "retry-after": "3" });
});
vi.stubGlobal("fetch", fetchMock);
renderLoginRoute();
await flushAll();
const input = screen.getByLabelText("Password");
fireEvent.change(input, { target: { value: "wrong" } });
fireEvent.submit(input.closest("form") as HTMLFormElement);
await flushAll();
const button = screen.getByRole("button", { name: "Log in" }) as HTMLButtonElement;
expect(screen.getByRole("alert").textContent).toBe("Too many attempts. Try again in 3s.");
expect(button.disabled).toBe(true);
const callsAtLockout = fetchMock.mock.calls.length;
fireEvent.submit(input.closest("form") as HTMLFormElement);
await flushAll();
expect(fetchMock.mock.calls.length).toBe(callsAtLockout);
act(() => {
vi.advanceTimersByTime(1000);
});
expect(screen.getByRole("alert").textContent).toBe("Too many attempts. Try again in 2s.");
expect(button.disabled).toBe(true);
act(() => {
vi.advanceTimersByTime(2000);
});
expect(screen.getByRole("alert").textContent).toBe("Too many attempts. Try again shortly.");
expect(button.disabled).toBe(false);
});
test("safeRedirect only allows same-origin absolute paths", () => {
expect(safeRedirect(undefined)).toBe("/");
expect(safeRedirect("/activity")).toBe("/activity");
expect(safeRedirect("/activity?x=1")).toBe("/activity?x=1");
expect(safeRedirect("//evil.example")).toBe("/");
expect(safeRedirect("https://evil.example")).toBe("/");
expect(safeRedirect("/\\evil.example")).toBe("/");
expect(safeRedirect("/\\\\evil.example")).toBe("/");
expect(safeRedirect("\\evil")).toBe("/");
});