import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import SettingsPage from "@/app/(app)/settings/page"; import { renderWithQueryClient } from "./test-utils"; const replaceMock = vi.fn(); vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), replace: replaceMock }), })); const REGULAR_USER = { id: "user-1", email: "user@example.com", display_name: "Regular User", timezone: "America/New_York", is_active: true, is_admin: false, auth_mode: "jwt", must_change_password: false, }; const LOCAL_DEV_USER = { id: "00000000-0000-0000-0000-000000000001", email: "local@ci-agent.local", display_name: "Local Developer", timezone: "America/New_York", is_active: true, is_admin: true, auth_mode: "local", must_change_password: false, }; function systemStatusBody(isLocalhost: boolean, authMode: "local" | "jwt") { return { app_env: "development", auth_mode: authMode, llm_provider: "mock", search_provider: "mock", sms_enabled: false, sms_provider: "twilio", ninjapear_configured: false, ninjapear_credit_balance: null, ninjapear_estimated_credits_per_company: null, is_localhost: isLocalhost, turnstile_site_key: null, components: [], }; } function mockFetchImplementation( isLocalhost: boolean, authMode: "local" | "jwt", meUser: typeof REGULAR_USER, ) { return vi.fn().mockImplementation((url: string, init?: RequestInit) => { const path = url.replace("http://localhost:8000", ""); const ok = (json: unknown, status = 200) => Promise.resolve({ ok: true, status, json: async () => json }); if (path === "/api/v1/auth/me") return ok(meUser); if (path === "/api/v1/system/status") return ok(systemStatusBody(isLocalhost, authMode)); if (path === "/api/v1/notification-destinations") return ok([]); if (path === "/api/v1/companies") return ok([]); if (path === "/api/v1/system/logs") return ok([]); if (path === "/api/v1/user-api-keys") return ok([]); if (path === "/api/v1/system/secrets") return ok([]); if (path === "/api/v1/auth/security-events") return ok([]); if (path === "/api/v1/admin/ip-bans") return ok([]); if (path === "/api/v1/admin/unban-requests") return ok([]); if (path === "/api/v1/auth/me" && init?.method === "DELETE") return ok(undefined, 204); return Promise.resolve({ ok: false, status: 404, json: async () => ({ detail: "not found" }) }); }); } beforeEach(() => { replaceMock.mockClear(); }); describe("Settings - Delete account box", () => { it("renders for a regular (non-local-dev) user, disabled until a password is typed", async () => { vi.stubGlobal("fetch", mockFetchImplementation(false, "jwt", REGULAR_USER)); renderWithQueryClient(); const button = await screen.findByRole("button", { name: /delete account/i }); expect(button).toBeDisabled(); const user = userEvent.setup(); await user.type(screen.getByLabelText(/confirm your password/i), "correct-horse-1"); expect(button).not.toBeDisabled(); }); it("is hidden entirely for the local-dev bypass account", async () => { vi.stubGlobal("fetch", mockFetchImplementation(true, "local", LOCAL_DEV_USER)); renderWithQueryClient(); await screen.findByText(LOCAL_DEV_USER.email); expect(screen.queryByRole("button", { name: /delete account/i })).not.toBeInTheDocument(); }); it("submits the password, then clears tokens and redirects home on success", async () => { const fetchMock = vi.fn().mockImplementation((url: string, init?: RequestInit) => { const path = url.replace("http://localhost:8000", ""); if (path === "/api/v1/auth/me" && init?.method === "DELETE") { expect(JSON.parse(init.body as string)).toEqual({ password: "correct-horse-1" }); return Promise.resolve({ ok: true, status: 204, json: async () => undefined }); } return mockFetchImplementation(false, "jwt", REGULAR_USER)(url, init); }); vi.stubGlobal("fetch", fetchMock); const user = userEvent.setup(); renderWithQueryClient(); const button = await screen.findByRole("button", { name: /delete account/i }); await user.type(screen.getByLabelText(/confirm your password/i), "correct-horse-1"); await user.click(button); await waitFor(() => { expect(replaceMock).toHaveBeenCalledWith("/"); }); }); it("shows an error message when the password is wrong", async () => { const fetchMock = vi.fn().mockImplementation((url: string, init?: RequestInit) => { const path = url.replace("http://localhost:8000", ""); if (path === "/api/v1/auth/me" && init?.method === "DELETE") { return Promise.resolve({ ok: false, status: 401, json: async () => ({ detail: "Incorrect password" }), }); } return mockFetchImplementation(false, "jwt", REGULAR_USER)(url, init); }); vi.stubGlobal("fetch", fetchMock); const user = userEvent.setup(); renderWithQueryClient(); const button = await screen.findByRole("button", { name: /delete account/i }); await user.type(screen.getByLabelText(/confirm your password/i), "wrong-password"); await user.click(button); expect(await screen.findByText(/incorrect password/i)).toBeInTheDocument(); }); });