rename web/ to admin/, along with the web-named build and cli identifiers
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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("/queries")).toBe("/queries");
|
||||
expect(safeRedirect("/queries?x=1")).toBe("/queries?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("/");
|
||||
});
|
||||
Reference in New Issue
Block a user