rename web/ to admin/, along with the web-named build and cli identifiers
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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";
|
||||
|
||||
const RESPONSES: Record<string, unknown> = {
|
||||
"/api/stats?period=24h": {
|
||||
period: "24h",
|
||||
since: 0,
|
||||
until: 86400,
|
||||
queries: 1000,
|
||||
blocked: 250,
|
||||
cached: 100,
|
||||
clients: 7,
|
||||
avg_response_time_us: 2345,
|
||||
},
|
||||
"/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 },
|
||||
],
|
||||
},
|
||||
"/api/stats?period=1h": {
|
||||
period: "1h",
|
||||
since: 0,
|
||||
until: 3600,
|
||||
queries: 12,
|
||||
blocked: 3,
|
||||
cached: 0,
|
||||
clients: 2,
|
||||
avg_response_time_us: null,
|
||||
},
|
||||
"/api/stats/timeseries?period=1h": {
|
||||
period: "1h",
|
||||
since: 0,
|
||||
until: 3600,
|
||||
bucket_seconds: 60,
|
||||
buckets: [],
|
||||
},
|
||||
"/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,
|
||||
},
|
||||
"/api/upstream/health": {
|
||||
upstreams: [
|
||||
{
|
||||
url: "https://dns.example/dns-query",
|
||||
enabled: true,
|
||||
available: false,
|
||||
consecutive_failures: 4,
|
||||
total_successes: 90,
|
||||
total_failures: 10,
|
||||
success_rate: 0.9,
|
||||
last_error: "timeout",
|
||||
},
|
||||
{
|
||||
url: "udp://9.9.9.9:53",
|
||||
enabled: true,
|
||||
available: true,
|
||||
consecutive_failures: 0,
|
||||
total_successes: 100,
|
||||
total_failures: 0,
|
||||
success_rate: 1,
|
||||
last_error: "",
|
||||
},
|
||||
],
|
||||
available: 1,
|
||||
total: 2,
|
||||
},
|
||||
"/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<string>;
|
||||
|
||||
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(
|
||||
<AuthProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
</AuthProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
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("Disk")).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("timeout")).toBeTruthy();
|
||||
expect(screen.getByText("1/2 available")).toBeTruthy();
|
||||
});
|
||||
|
||||
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");
|
||||
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("Disk")).toBeTruthy();
|
||||
expect(screen.queryByText("https://dns.example/dns-query")).toBeNull();
|
||||
});
|
||||
Reference in New Issue
Block a user