Gates / frontend (push) Successful in 1m34s
Gates / test (push) Successful in 2m3s
Gates / test-aarch64 (push) Failing after 3h13m33s
Gates / package (push) Successful in 5m20s
Gates / container (push) Successful in 15s
CI / gates (push) Failing after 6h30m45s
115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import type { QueryRow } from "@/lib/types";
|
|
import type { ClientNames } from "@/features/clients/clientNames";
|
|
import { queryRow } from "@/features/provenance/provenanceFixture";
|
|
import { summarizeRow, type QuerySummary } from "@/features/provenance/querySummary";
|
|
import { ACTIVITY_COLUMNS, ActivityCells, ActivityTableHead, resultLabel, routeLabel } from "./cells";
|
|
|
|
const noNames: ClientNames = new Map();
|
|
|
|
function renderRow(overrides: Partial<QueryRow> = {}): HTMLTableRowElement {
|
|
const row: QuerySummary = summarizeRow(queryRow(1, overrides));
|
|
render(
|
|
<table>
|
|
<ActivityTableHead />
|
|
<tbody>
|
|
<tr data-testid="row">
|
|
<ActivityCells
|
|
row={row}
|
|
clientNames={noNames}
|
|
renderDomain={(id, children) => <a href={`/activity/queries/${id}`}>{children}</a>}
|
|
/>
|
|
</tr>
|
|
</tbody>
|
|
</table>,
|
|
);
|
|
return screen.getByTestId("row") as HTMLTableRowElement;
|
|
}
|
|
|
|
/** The cell under a header, read by its column name rather than its index. */
|
|
function cell(row: HTMLTableRowElement, column: (typeof ACTIVITY_COLUMNS)[number]): string {
|
|
const index = ACTIVITY_COLUMNS.indexOf(column);
|
|
return row.cells[index]?.textContent ?? "";
|
|
}
|
|
|
|
test("the head names the seven columns in order", () => {
|
|
render(
|
|
<table>
|
|
<ActivityTableHead />
|
|
</table>,
|
|
);
|
|
const headers = screen.getAllByRole("columnheader").map((header) => header.textContent);
|
|
expect(headers).toEqual(["Time", "Domain", "Client", "Type", "Result", "Route", "Duration"]);
|
|
});
|
|
|
|
test("an allowed NOERROR row reads as the answer it got, with no badge", () => {
|
|
const row = renderRow({ blocked: false, rcode: 0, route_kind: "upstream", response_time_us: 1234 });
|
|
expect(cell(row, "Result")).toBe("NOERROR");
|
|
expect(cell(row, "Route")).toBe("Upstream");
|
|
expect(cell(row, "Duration")).toBe("1.2 ms");
|
|
expect(cell(row, "Type")).toBe("A");
|
|
});
|
|
|
|
test("a blocked row reads Blocked even though the client got NOERROR", () => {
|
|
const row = renderRow({ blocked: true, rcode: 0, route_kind: "blocked", policy_reason: "blocklist_domain" });
|
|
expect(cell(row, "Result")).toBe("Blocked");
|
|
expect(cell(row, "Route")).toBe("Blocked");
|
|
});
|
|
|
|
test("a SERVFAIL row names the code", () => {
|
|
const row = renderRow({ blocked: false, rcode: 2, route_kind: "upstream", response_time_us: null });
|
|
expect(cell(row, "Result")).toBe("SERVFAIL");
|
|
expect(cell(row, "Duration")).toBe("—");
|
|
});
|
|
|
|
test("a cache hit names the cache as the route", () => {
|
|
const row = renderRow({ blocked: false, rcode: 0, route_kind: "cache", cache_hit: true, upstream: "" });
|
|
expect(cell(row, "Route")).toBe("Cache");
|
|
expect(cell(row, "Result")).toBe("NOERROR");
|
|
});
|
|
|
|
test("an unassigned extended rcode keeps the numeric fallback, without the long form's parentheses", () => {
|
|
const row = renderRow({ blocked: false, rcode: 3841 });
|
|
expect(cell(row, "Result")).toBe("RCODE 3841");
|
|
});
|
|
|
|
test("a persisted row links its domain to the detail the caller chose", () => {
|
|
renderRow({ domain: "ads.example" });
|
|
expect(screen.getByRole("link", { name: "ads.example" }).getAttribute("href")).toBe("/activity/queries/1");
|
|
});
|
|
|
|
test("a streamed row reaches the renderer with a null id, and can render as plain text", () => {
|
|
render(
|
|
<table>
|
|
<tbody>
|
|
<tr data-testid="row">
|
|
<ActivityCells
|
|
row={{ ...summarizeRow(queryRow(1)), id: null }}
|
|
clientNames={noNames}
|
|
renderDomain={(id, children) => {
|
|
expect(id).toBeNull();
|
|
return children;
|
|
}}
|
|
/>
|
|
</tr>
|
|
</tbody>
|
|
</table>,
|
|
);
|
|
expect(screen.queryByRole("link")).toBeNull();
|
|
expect(screen.getByTestId("row").textContent).toContain("example.com");
|
|
});
|
|
|
|
test("every route kind has a compact label", () => {
|
|
expect(routeLabel("blocked")).toBe("Blocked");
|
|
expect(routeLabel("local")).toBe("Local");
|
|
expect(routeLabel("forward_zone")).toBe("Forward zone");
|
|
expect(routeLabel("upstream")).toBe("Upstream");
|
|
expect(routeLabel("cache")).toBe("Cache");
|
|
expect(routeLabel("rejected")).toBe("Rejected");
|
|
});
|
|
|
|
test("resultLabel is the pure form of the Result cell", () => {
|
|
expect(resultLabel({ blocked: true, rcode: 2 })).toBe("Blocked");
|
|
expect(resultLabel({ blocked: false, rcode: 5 })).toBe("REFUSED");
|
|
});
|