import { act, render, screen, waitFor } from "@testing-library/react"; import { setLocale } from "../i18n"; 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"; afterEach(() => act(() => setLocale("en"))); test("translates a live readiness diagnostic while preserving its catalog revision", () => { renderControl({ schemaVersion: 1, workspaceId: "catalog-workspace", state: "ready", actionable: true, clearable: true, detail: "Catalog revision 18 is indexed." }); act(() => setLocale("it")); expect(screen.getByText("La revisione 18 del Catalogo รจ indicizzata.")).toBeVisible(); expect(screen.getByRole("button", { name: "Esegui di nuovo" })).toBeEnabled(); }); function renderControl(status: WorkspacePreprocessingStatus) { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( , ); } 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)); });