feat: complete catalog-driven preprocessing
Publish documentation / publish (push) Successful in 2m12s

This commit is contained in:
Codex
2026-09-06 17:49:35 +02:00
parent 8707ae1d46
commit cffa60772e
141 changed files with 5898 additions and 3015 deletions
@@ -0,0 +1,174 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import type { WorkspacePreprocessingStatus } from "../api/workspace-preprocessing";
import { WorkspacePreprocessingControl } from "./WorkspacePreprocessingControl";
function renderControl(status: WorkspacePreprocessingStatus) {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(
<QueryClientProvider client={client}>
<WorkspacePreprocessingControl
workspaceId="catalog-workspace"
status={status}
loading={false}
unavailable={false}
canManage
/>
</QueryClientProvider>,
);
}
test("failed state shows the reason and only the latest run diagnostic", async () => {
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "failed",
actionable: true,
clearable: true,
detail: "The schema index could not be rebuilt.",
reason: "The schema indexing worker stopped before publication.",
nextStep: "Check the core service log, then retry.",
lastFailure: {
stage: "schema_index",
errorCode: "schema_index_failed",
finishedAt: "2026-09-05T10:04:00.000Z",
},
});
expect(screen.getByText("The schema indexing worker stopped before publication.")).toBeVisible();
await userEvent.click(screen.getByText("Last run details"));
expect(screen.getByText("schema_index", { selector: "dd" })).toBeVisible();
expect(screen.getByText("schema_index_failed", { selector: "dd" })).toBeVisible();
expect(screen.getByText(/docker compose logs core/)).toBeVisible();
expect(screen.queryByText(/history/i)).not.toBeInTheDocument();
});
test("blocked state gives the current prerequisite and no run details", () => {
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "blocked",
actionable: false,
clearable: true,
detail: "Catalog synchronization is required.",
reason: "Database configuration v4 is newer than Catalog synchronization v3.",
nextStep: "Open Database management and run Synchronize schema.",
});
expect(screen.getByText("Database configuration v4 is newer than Catalog synchronization v3.")).toBeVisible();
expect(screen.getByText("No preprocessing log is available because a run did not start.")).toBeVisible();
expect(screen.queryByText("Last run details")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Run" })).toBeDisabled();
});
test("running state shows the current durable phase instead of only a spinner", () => {
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "running",
actionable: false,
clearable: false,
detail: "Indexing Evidence.",
progress: { stage: "evidence", step: 3, totalSteps: 4 },
});
expect(screen.getByText("Indexing Evidence")).toBeVisible();
expect(screen.getByText("3 / 4")).toBeVisible();
expect(screen.getByRole("progressbar", { name: "Preprocessing progress" })).toHaveAttribute(
"aria-valuetext",
"Indexing Evidence, stage 3 of 4",
);
expect(screen.getByRole("button", { name: "Running" })).toBeDisabled();
});
test("ready state offers an explicit rerun from the GUI", async () => {
let calls = 0;
server.use(http.post("/api/workspaces/catalog-workspace/preprocessing", () => {
calls += 1;
return HttpResponse.json({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "ready",
actionable: true,
clearable: true,
detail: "Catalog revision 18 is indexed.",
});
}));
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "ready",
actionable: true,
clearable: true,
detail: "Catalog revision 18 is indexed.",
});
await userEvent.click(screen.getByRole("button", { name: "Run again" }));
await waitFor(() => expect(calls).toBe(1));
});
test("Retry invokes the single preprocessing endpoint", async () => {
let calls = 0;
server.use(http.post("/api/workspaces/catalog-workspace/preprocessing", () => {
calls += 1;
return HttpResponse.json({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "ready",
actionable: true,
clearable: true,
detail: "Catalog revision 18 is indexed.",
});
}));
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "failed",
actionable: true,
clearable: true,
detail: "Preprocessing did not complete.",
reason: "The worker stopped.",
nextStep: "Retry.",
lastFailure: {
stage: "preprocessing",
errorCode: "preprocessing_failed",
finishedAt: "2026-09-05T10:04:00.000Z",
},
});
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
await waitFor(() => expect(calls).toBe(1));
});
test("Clear uses the approved inline review and preserves memory in its scope copy", async () => {
let calls = 0;
server.use(http.delete("/api/workspaces/catalog-workspace/preprocessing", () => {
calls += 1;
return HttpResponse.json({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "required",
actionable: true,
clearable: false,
detail: "Reference vectors and LSH are empty. Memory is preserved.",
});
}));
renderControl({
schemaVersion: 1,
workspaceId: "catalog-workspace",
state: "ready",
actionable: true,
clearable: true,
detail: "Catalog revision 18 is indexed.",
});
await userEvent.click(screen.getByRole("button", { name: "Clear" }));
expect(screen.getByText("Clear derived data?")).toBeVisible();
expect(screen.getByText("Schema/Evidence vectors, LSH and derived checkpoints")).toBeVisible();
expect(screen.getByText("Memory and solved questions")).toBeVisible();
await userEvent.click(screen.getByRole("button", { name: "Clear derived data" }));
await waitFor(() => expect(calls).toBe(1));
});