import { fireEvent, render, screen } from "@testing-library/react"; import { QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; import { AuthProvider } from "@/auth/store"; import { createQueryClient } from "@/lib/queryClient"; import { createAppRouter } from "@/routes"; /** Wall clock at import; the upstream fixtures date their failures against it. */ const NOW_S = Math.floor(Date.now() / 1000); const RESPONSES: Record = { "/api/stats?period=24h": { period: "24h", since: 0, until: 86400, queries: 1000, blocked: 250, cached: 100, clients: 7, avg_response_time_us: 2345, coverage: { complete: true, available_since: 0 }, }, "/api/stats/timeseries?period=24h": { period: "24h", since: 0, until: 86400, bucket_seconds: 1800, buckets: [ { ts: 0, queries: 60, blocked: 20, cached: 10 }, { ts: 1800, queries: 40, blocked: 0, cached: 0 }, { ts: 3600, queries: 0, blocked: 0, cached: 0 }, ], coverage: { complete: true, available_since: 0 }, }, "/api/stats?period=1h": { period: "1h", since: 0, until: 3600, queries: 12, blocked: 3, cached: 0, clients: 2, avg_response_time_us: null, coverage: { complete: true, available_since: 0 }, }, "/api/stats/timeseries?period=1h": { period: "1h", since: 0, until: 3600, bucket_seconds: 60, buckets: [], coverage: { complete: true, available_since: 0 }, }, "/api/health": { status: "degraded", disk: { state: "warn", free_bytes: 400 * 1024 * 1024, db_bytes: 12 * 1024 * 1024, log_bytes: 2048, sample_failures: 0, }, upstreams: { available: 1, total: 2 }, queries_dropped: 5, writer_failed: false, refreshes_gated: 0, snapshot_generation: 3, diagnostics: { state: "recording", active_warnings: 1, active_errors: 0 }, }, "/api/upstream/health?period=24h": { period: "24h", since: NOW_S - 86_400, until: NOW_S, available: 1, total: 2, complete: true, upstreams: [ { url: "https://dns.example/dns-query", enabled: true, available: false, period: { attempts: 100, successes: 90, failures: 10, success_rate: 0.9, // 3h30m before the fixture's now, far from a unit boundary. last_failure_at: NOW_S - 12_600, last_failure_error: "timeout", }, }, { url: "udp://9.9.9.9:53", enabled: true, available: true, period: { attempts: 100, successes: 100, failures: 0, success_rate: 1, last_failure_at: null, last_failure_error: null, }, }, ], }, "/api/upstream/health?period=1h": { period: "1h", since: NOW_S - 3600, until: NOW_S, available: 1, total: 1, complete: true, upstreams: [ { url: "https://dns.example/dns-query", enabled: true, available: true, period: { attempts: 7, successes: 6, failures: 1, success_rate: 6 / 7, last_failure_at: NOW_S - 300, last_failure_error: "timeout", }, }, ], }, "/api/version": { version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 }, }; // Endpoints forced to fail with a 4xx, which the query client does not retry. let failing: Set; beforeEach(() => { failing = new Set(); vi.stubGlobal( "fetch", vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (failing.has(url)) { return new Response(JSON.stringify({ error: "upstream health unavailable" }), { status: 400, headers: { "content-type": "application/json" }, }); } const payload = RESPONSES[url]; if (payload === undefined) return new Response(JSON.stringify({ error: "not stubbed" }), { status: 404 }); return new Response(JSON.stringify(payload), { status: 200, headers: { "content-type": "application/json" }, }); }), ); }); afterEach(() => { vi.unstubAllGlobals(); }); function renderDashboard() { const queryClient = createQueryClient(); const router = createAppRouter(createMemoryHistory({ initialEntries: ["/"] }), queryClient); render( , ); } test("dashboard renders stats, chart, disk card, upstream table and health banners", async () => { renderDashboard(); await screen.findByRole("heading", { name: "Dashboard" }); expect(screen.getByText("1,000")).toBeTruthy(); expect(screen.getByText("250")).toBeTruthy(); expect(screen.getByText("25.0%")).toBeTruthy(); expect(screen.getByText("7")).toBeTruthy(); expect(screen.getByText("2.3 ms")).toBeTruthy(); expect(screen.getByRole("img", { name: /queries over time/i })).toBeTruthy(); expect(screen.getByText("Blocked", { selector: "li" })).toBeTruthy(); expect(screen.getByText("Storage now")).toBeTruthy(); expect(screen.getByText("warn")).toBeTruthy(); expect(screen.getAllByText("400.0 MiB").length).toBeGreaterThan(0); expect(screen.getByText("12.0 MiB")).toBeTruthy(); expect(screen.getByText("2.0 KiB")).toBeTruthy(); const alerts = screen.getAllByRole("alert"); expect(alerts.some((alert) => /disk space low/i.test(alert.textContent ?? ""))).toBe(true); expect(alerts.some((alert) => /5 queries dropped/i.test(alert.textContent ?? ""))).toBe(true); expect(screen.getByText("https://dns.example/dns-query")).toBeTruthy(); expect(screen.getByText("90.0%")).toBeTruthy(); expect(screen.getByText("100.0%")).toBeTruthy(); expect(screen.getByText("1/2 available")).toBeTruthy(); }); test("live state is labeled on its own card, not by a section that disowns the picker", async () => { renderDashboard(); await screen.findByRole("heading", { name: "Dashboard" }); expect(screen.getByText("Storage now")).toBeTruthy(); expect(screen.queryByRole("region", { name: "Right now" })).toBeNull(); expect(screen.queryByText("Right now")).toBeNull(); expect(screen.queryByText("Snapshot state; the period above does not apply.")).toBeNull(); }); test("the period picker rescopes the upstream table", async () => { renderDashboard(); await screen.findByRole("heading", { name: "Dashboard" }); await screen.findByText("90.0%"); fireEvent.click(screen.getByRole("button", { name: "1h" })); await screen.findByText("85.7%"); expect(screen.getByRole("columnheader", { name: "Selected period · 1h" })).toBeTruthy(); expect(screen.queryByText("90.0%")).toBeNull(); }); test("period picker refetches stats and shows the empty chart state", async () => { renderDashboard(); await screen.findByRole("heading", { name: "Dashboard" }); fireEvent.click(screen.getByRole("button", { name: "1h" })); await screen.findByText("12"); expect(screen.getByRole("button", { name: "1h" }).getAttribute("aria-pressed")).toBe("true"); expect(screen.getByRole("button", { name: "24h" }).getAttribute("aria-pressed")).toBe("false"); await screen.findByText("No queries in this period."); expect(screen.getByText("—", { selector: "span" })).toBeTruthy(); }); test("one failing endpoint degrades its own widget on cold navigation", async () => { failing.add("/api/upstream/health?period=24h"); renderDashboard(); await screen.findByRole("heading", { name: "Dashboard" }); // The page renders; only the upstream widget carries the error. await screen.findByText("upstream health unavailable"); expect(screen.queryByText("Something went wrong")).toBeNull(); expect(screen.queryByText("Request failed (400)")).toBeNull(); expect(screen.getByText("1,000")).toBeTruthy(); expect(screen.getByRole("img", { name: /queries over time/i })).toBeTruthy(); expect(screen.getByText("Storage now")).toBeTruthy(); expect(screen.queryByText("https://dns.example/dns-query")).toBeNull(); });