admin: live ring capacity is injectable, eviction test no longer timing-bound
Gates / test-aarch64 (push) Failing after 3h1m47s
Gates / package (push) Successful in 4m17s
Gates / container (push) Successful in 15s
CI / gates (push) Failing after 4h45m14s
Gates / frontend (push) Successful in 1m21s
Gates / test (push) Successful in 1m42s

This commit is contained in:
2026-08-23 09:04:18 +02:00
parent 409384ee9e
commit c5875af8c8
3 changed files with 82 additions and 19 deletions
@@ -8,7 +8,7 @@
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
import { RouterContextProvider, RouterProvider, createMemoryHistory } from "@tanstack/react-router";
import { AuthProvider } from "@/auth/store";
import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";
@@ -16,6 +16,17 @@ import type { Client } from "@/lib/types";
import { provenance, queryRow } from "@/features/provenance/provenanceFixture";
import { health } from "@/lib/healthFixture";
import { FakeEventSource } from "./fakeEventSource";
import LiveActivity from "./LiveActivity";
import type { ActivitySearch } from "./search";
const LIVE_ORIGIN: ActivitySearch = {
mode: "live",
since: undefined,
until: undefined,
domain: undefined,
client: undefined,
blocked: undefined,
};
function client(ip: string, name: string, learnedName: string): Client {
return {
@@ -327,23 +338,55 @@ test("opening a second row moves the expanded state and the focus with it", asyn
expect(document.activeElement).toBe(second);
});
test("an open streamed detail survives the row being evicted from the ring buffer", async () => {
await openLive();
/**
* The one render that bypasses the route, because the ring capacity is a
* parameter of the component and the route deliberately never passes it:
* evicting a row at the real 500 means pushing 500 frames through React state,
* which proves nothing the fifth frame does not. The real route tree still
* backs the links inside the detail.
*/
function renderLiveWithCapacity(capacity: number) {
const queryClient = createQueryClient();
const router = createAppRouter(createMemoryHistory({ initialEntries: ["/activity?mode=live"] }), queryClient);
render(
<AuthProvider>
<QueryClientProvider client={queryClient}>
<RouterContextProvider router={router}>
<LiveActivity origin={LIVE_ORIGIN} capacity={capacity} />
</RouterContextProvider>
</QueryClientProvider>
</AuthProvider>,
);
}
test("an open streamed detail survives the row being evicted from the ring buffer", () => {
renderLiveWithCapacity(5);
act(() => sources[0]!.emit("open"));
act(() => sources[0]!.emit("query", frame(1000, "evicted.example")));
fireEvent.click(screen.getByRole("button", { name: "evicted.example" }));
expect(screen.getByRole("heading", { level: 1, name: "evicted.example" })).toBeTruthy();
// 500 more queries: the ring keeps the newest 500, so the selected row is
// gone from the table. The detail is a snapshot, not a lookup into the ring.
// One ringful more: the ring keeps the newest 5, so the selected row is gone
// from the table. The detail is a snapshot, not a lookup into the ring.
act(() => {
for (let index = 0; index < 500; index += 1) {
for (let index = 0; index < 5; index += 1) {
sources[0]!.emit("query", frame(2000 + index, `filler${index}.example`));
}
});
expect(screen.getAllByRole("row")).toHaveLength(6);
expect(screen.queryByRole("button", { name: "evicted.example" })).toBeNull();
expect(screen.getByRole("heading", { level: 1, name: "evicted.example" })).toBeTruthy();
});
test("the route renders the live ring at its production capacity", async () => {
await openLive();
act(() => sources[0]!.emit("query", frame(1000, "pinned.example")));
expect(screen.getByText(/last 500 kept/)).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Freeze" }));
expect(screen.getByText(/newest 500 kept/)).toBeTruthy();
});
test("an open streamed detail survives Freeze and Resume", async () => {
await openLive();
act(() => sources[0]!.emit("query", frame(1000, "held.example")));