- Live stops polling on a failed read and marks the list as possibly stale (with Retry), instead of re-raising the error dialog every tick. - While live, a refetch reads the newest page only, whatever the cache holds (a late "Load older", a revisited filter set). - Copy is disabled and the list dimmed while a filter change loads. - Dates follow the UI language; the pivot chip uses the shared .chip. - Tests: query-string wire test, Copy, every facet's skip key.
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The log console's query strings. The console's DOM tests mock this slice, so nothing else in the
|
|
// suite looks at the URL that goes out — and a multi-level group is the case that breaks quietly:
|
|
// `levels` must repeat, or FastAPI's list reads only the last one.
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { adminApi } from "./admin";
|
|
|
|
const fetchMock = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
fetchMock.mockReset().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
headers: new Headers({ "content-type": "application/json" }),
|
|
json: async () => ({}),
|
|
text: async () => "{}",
|
|
});
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
});
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
const lastQuery = (): URLSearchParams => {
|
|
const calls = fetchMock.mock.calls;
|
|
return new URL(String(calls[calls.length - 1]?.[0]), "http://x").searchParams;
|
|
};
|
|
|
|
describe("adminApi log query strings", () => {
|
|
it("repeats the list filters and sends every single one it is given", async () => {
|
|
await adminApi.adminLogs(
|
|
{
|
|
levels: ["ERROR", "CRITICAL"],
|
|
sources: ["api", "worker"],
|
|
user: "none",
|
|
event: "sync.rss.failed",
|
|
request_id: "req-abcdef12",
|
|
job_id: 42,
|
|
q: "100% done",
|
|
since: "2026-09-19T11:00:00.000Z",
|
|
},
|
|
"cursor-1",
|
|
);
|
|
const p = lastQuery();
|
|
expect(p.getAll("levels")).toEqual(["ERROR", "CRITICAL"]);
|
|
expect(p.getAll("sources")).toEqual(["api", "worker"]);
|
|
expect(Object.fromEntries([...p].filter(([k]) => k !== "levels" && k !== "sources"))).toEqual({
|
|
user: "none",
|
|
event: "sync.rss.failed",
|
|
request_id: "req-abcdef12",
|
|
job_id: "42",
|
|
q: "100% done",
|
|
since: "2026-09-19T11:00:00.000Z",
|
|
before: "cursor-1",
|
|
});
|
|
});
|
|
|
|
it("leaves out what is not set, and the facets carry no cursor", async () => {
|
|
await adminApi.adminLogs({ q: "", user: undefined }, null);
|
|
expect(String(fetchMock.mock.calls[0]?.[0])).toBe("/api/admin/logs");
|
|
|
|
await adminApi.adminLogFacets({ levels: ["WARNING"], since: "2026-09-19T11:00:00.000Z" });
|
|
expect([...lastQuery().keys()]).toEqual(["levels", "since"]);
|
|
});
|
|
});
|