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"; vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), })); const ADMIN_USER = { id: "user-1", email: "admin@example.com", display_name: "Admin", timezone: "America/New_York", is_active: true, is_admin: true, auth_mode: "jwt", 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: [], }; } /** Covers every endpoint SettingsPage's admin-gated boxes touch on mount - * empty-list/no-op responses for everything except /system/status, which * each test configures to drive the local-vs-remote branch under test. */ function mockFetchImplementation(isLocalhost: boolean, authMode: "local" | "jwt" = "jwt") { 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(ADMIN_USER); 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/db-viewer/session" && init?.method === "POST") { return ok({ token: "mock-bootstrap-token" }); } return Promise.resolve({ ok: false, status: 404, json: async () => ({ detail: "not found" }) }); }); } beforeEach(() => { vi.stubEnv("NEXT_PUBLIC_DB_VIEWER_URL", "https://db.ciagent.org"); }); describe("Settings - Database viewer box", () => { it("links straight to the local Adminer instance for the local-dev bypass account", async () => { vi.stubGlobal("fetch", mockFetchImplementation(true, "local")); renderWithQueryClient(); const link = await screen.findByRole("link", { name: /open database viewer/i }); expect(link).toHaveAttribute("href", "http://localhost:8081/?pgsql=postgres&username=ciagent"); expect(link).toHaveAttribute("target", "_blank"); }); it("mints a session token and opens the configured remote viewer URL for a real admin", async () => { vi.stubGlobal("fetch", mockFetchImplementation(false, "jwt")); const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); const user = userEvent.setup(); renderWithQueryClient(); const button = await screen.findByRole("button", { name: /open database viewer/i }); await user.click(button); await waitFor(() => { expect(openSpy).toHaveBeenCalledWith( "https://db.ciagent.org/_auth?token=mock-bootstrap-token", "_blank", "noopener,noreferrer", ); }); }); it("shows a not-configured message when NEXT_PUBLIC_DB_VIEWER_URL is unset for a remote admin", async () => { vi.stubEnv("NEXT_PUBLIC_DB_VIEWER_URL", ""); vi.stubGlobal("fetch", mockFetchImplementation(false, "jwt")); renderWithQueryClient(); expect(await screen.findByText(/not configured for this deployment/i)).toBeInTheDocument(); expect(screen.queryByRole("button", { name: /open database viewer/i })).not.toBeInTheDocument(); }); });