import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { http, HttpResponse } from "msw"; import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { server } from "../test/msw"; import { WorkspaceManager } from "./WorkspaceManager"; const workspace = { workspace: { schema_version: 3, id: "psd-clinical", name: "PSD Clinical", language: "en" }, dwh: { engine: "postgres", database: "clinical", schema: "datawarehouse", port: 5432, supported_transports: ["postgres_direct"] }, semantic_index: { vector_store: { engine: "qdrant", collection: "clinical", dimensions: 1024, distance: "cosine" }, embedding: { provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024 }, }, llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] }, } as const; function renderManager() { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( undefined} />); } beforeEach(() => { localStorage.clear(); server.use( http.get("/api/workspace-registry/status", () => HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false })), http.get("/api/workspaces", () => HttpResponse.json([{ id: "psd-clinical", name: "PSD Clinical", displayName: "PSD Clinical", description: "Clinical data", language: "en", file: "workspaces/psd-clinical.yaml", revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "operational" }, }])), http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace, revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "operational" }, })), ); }); afterEach(() => vi.unstubAllGlobals()); test("lists registry workspaces and saves a new workspace only as a browser draft", async () => { const user = userEvent.setup(); renderManager(); expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible(); expect(await screen.findByRole("button", { name: "PSD Clinical" })).toBeVisible(); expect(screen.getByText(`Commit ${"a".repeat(12)}`)).toBeVisible(); await user.click(screen.getByRole("button", { name: "New workspace" })); await user.clear(screen.getByLabelText("Workspace ID")); await user.type(screen.getByLabelText("Workspace ID"), "trial-registry"); await user.click(screen.getByRole("button", { name: "Save draft" })); await waitFor(() => expect(screen.getByText("Draft saved in this browser.")).toBeVisible()); expect(localStorage.getItem("thothii.workspace-registry.v1.draft.trial-registry")).not.toBeNull(); }); test("imports a bundle as a local draft and never publishes it automatically", async () => { const user = userEvent.setup(); const publishSpy = vi.fn(); server.use( http.post("/api/workspaces/import", () => HttpResponse.json({ draft: { workspace, contract: {} } })), http.post("/api/workspaces/publish", () => { publishSpy(); return HttpResponse.json({}); }), ); renderManager(); await user.upload(screen.getByLabelText("Import workspace bundle"), new File(["bundle"], "workspace.zip", { type: "application/zip" })); expect(await screen.findByText("Imported draft saved in this browser. Validate it before publishing.")).toBeVisible(); expect(publishSpy).not.toHaveBeenCalled(); expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).not.toBeNull(); }); test("pulls and exports only when the manager explicitly requests each action", async () => { const user = userEvent.setup(); const publishSpy = vi.fn(); const createObjectURL = vi.fn(() => "blob:workspace-bundle"); const revokeObjectURL = vi.fn(); class DownloadUrl extends URL { static createObjectURL = createObjectURL; static revokeObjectURL = revokeObjectURL; } vi.stubGlobal("URL", DownloadUrl); vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => undefined); server.use( http.post("/api/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "c".repeat(40), ahead: 0, behind: 0, degraded: false })), http.get("/api/workspaces/psd-clinical/export", () => new HttpResponse(new Blob(["bundle"], { type: "application/zip" }))), http.post("/api/workspaces/publish", () => { publishSpy(); return HttpResponse.json({}); }), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Pull latest registry" })); expect(await screen.findByText("Registry updated. Reload a workspace to review its latest revision.")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Export workspace bundle" })); await waitFor(() => expect(createObjectURL).toHaveBeenCalledTimes(1)); expect(revokeObjectURL).toHaveBeenCalledWith("blob:workspace-bundle"); expect(publishSpy).not.toHaveBeenCalled(); }); test("stages duplicate and delete operations without publishing", async () => { const user = userEvent.setup(); let published = false; server.use(http.post("/api/workspaces/publish", () => { published = true; return HttpResponse.json({}); })); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Duplicate workspace" })); expect(screen.getByLabelText("Workspace ID")).not.toBeDisabled(); await user.click(screen.getByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Delete workspace" })); expect(screen.getByText("Deletion draft staged locally.")).toBeVisible(); expect(published).toBe(false); }); test("saves resolved conflict choices as a rebased browser draft without publishing again", async () => { const user = userEvent.setup(); let publishCalls = 0; const local = { ...workspace, semantic_index: { ...workspace.semantic_index, vector_store: { ...workspace.semantic_index.vector_store, collection: "local_collection" } } }; const remote = { ...workspace, semantic_index: { ...workspace.semantic_index, vector_store: { ...workspace.semantic_index.vector_store, collection: "remote_collection" } } }; server.use( http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace: local, contract: {} })), http.post("/api/workspaces/publish", () => { publishCalls += 1; return HttpResponse.json({ code: "workspace_conflict", message: "Workspace changed in the registry.", fields: ["semantic_index.vector_store.collection"], expected: { commit: "a".repeat(40), blob: "b".repeat(40) }, actual: { commit: "c".repeat(40), blob: "d".repeat(40) }, base: workspace, local, remote, }, { status: 409 }); }), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Publish draft" })); await user.click(screen.getByRole("button", { name: "Validate draft" })); await user.click(await screen.findByRole("button", { name: "Publish" })); await user.click(screen.getByRole("button", { name: "Confirm publish" })); await user.click(await screen.findByRole("radio", { name: "Use your draft for semantic_index.vector_store.collection" })); await user.click(screen.getByRole("button", { name: "Save revised draft" })); expect(await screen.findByText("Revised draft saved with registry revision cccccccccccc. Validate it before publishing.")).toBeVisible(); expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).toContain('"baseCommit":"cccccccccccccccccccccccccccccccccccccccc"'); expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).toContain('"baseBlob":"dddddddddddddddddddddddddddddddddddddddd"'); expect(publishCalls).toBe(1); }); test("proposes a different valid ID when duplicating a 63-character workspace ID", async () => { const user = userEvent.setup(); const maxId = `w${"a".repeat(62)}`; const maxWorkspace = { ...workspace, workspace: { ...workspace.workspace, id: maxId } }; server.use( http.get("/api/workspaces", () => HttpResponse.json([{ id: maxId, name: "Maximum", displayName: "Maximum", language: "en", file: `workspaces/${maxId}.yaml`, revision: { id: maxId, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/maximum", state: "operational" }, }])), http.get(`/api/workspaces/${maxId}`, () => HttpResponse.json({ workspace: maxWorkspace, revision: { id: maxId, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/maximum", state: "operational" }, })), ); renderManager(); await user.click(await screen.findByRole("button", { name: "Maximum" })); await user.click(screen.getByRole("button", { name: "Duplicate workspace" })); const proposed = screen.getByLabelText("Workspace ID") as HTMLInputElement; expect(proposed.value).toMatch(/^[a-z][a-z0-9-]{2,62}$/); expect(proposed).not.toHaveValue(maxId); }); test("does not show a saved-draft toast when manager validation rejects a DWH timeout", async () => { const user = userEvent.setup(); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.clear(screen.getByLabelText("DWH timeout (ms)")); await user.type(screen.getByLabelText("DWH timeout (ms)"), "0"); await user.click(screen.getByRole("button", { name: "Save draft" })); expect(screen.getByRole("alert")).toHaveTextContent("DWH timeout must be a positive whole number"); expect(screen.queryByText("Draft saved in this browser.")).not.toBeInTheDocument(); }); test("runs validation and installation test with only sanitized messages", async () => { const user = userEvent.setup(); server.use( http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {} })), http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({ activatable: false, diagnostics: [{ level: "warning", code: "binding_missing", field: "dwh", message: "DWH binding is not configured" }], })), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace" })); expect(await screen.findByText("Workspace definition is valid.")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Test on this installation" })); expect(await screen.findByText("binding_missing: DWH binding is not configured")).toBeVisible(); expect(within(screen.getByTestId("workspace-diagnostics")).queryByText(/password|token|secret/i)).not.toBeInTheDocument(); }); test("shows a migration banner for legacy descriptors and does not load editor details for them", async () => { const user = userEvent.setup(); server.use( http.get("/api/workspaces", () => HttpResponse.json([ { id: "psd-clinical", name: "PSD Clinical", displayName: "PSD Clinical", description: "Clinical data", language: "en", file: "workspaces/psd-clinical.yaml", revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "migration_required" }, }, ])), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); expect(await screen.findByText("This workspace uses a legacy descriptor and must be migrated to schema v3 before new sessions or publication.")).toBeVisible(); expect(screen.queryByLabelText("Vector collection")).not.toBeInTheDocument(); }); test("shows an actionable unavailable message when a workspace summary omits its canonical revision", async () => { const user = userEvent.setup(); server.use( http.get("/api/workspaces", () => HttpResponse.json([ { id: "broken-workspace", name: "Broken workspace", displayName: "Broken workspace", description: "Broken data", language: "en", file: "workspaces/broken-workspace.yaml", }, ])), ); renderManager(); await user.click(await screen.findByRole("button", { name: "Broken workspace" })); expect(await screen.findByText("This workspace summary is incomplete. Refresh the registry or choose another workspace before creating sessions or editing drafts.")).toBeVisible(); expect(screen.queryByLabelText("Vector collection")).not.toBeInTheDocument(); }); test("shows an accessible retry instead of a loading status when the registry status query fails", async () => { const user = userEvent.setup(); let calls = 0; server.use(http.get("/api/workspace-registry/status", () => { calls += 1; return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json({ branch: "main", ahead: 0, behind: 0, degraded: false }); })); renderManager(); expect(await screen.findByRole("alert", { name: "Workspace registry status failed" })).toHaveTextContent("Could not load registry status."); await user.click(screen.getByRole("button", { name: "Retry registry status" })); expect(await screen.findByText("main")).toBeVisible(); expect(calls).toBe(2); }); test("shows an accessible retry instead of an empty list when the workspace list query fails", async () => { const user = userEvent.setup(); let calls = 0; server.use(http.get("/api/workspaces", () => { calls += 1; return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json([]); })); renderManager(); expect(await screen.findByRole("alert", { name: "Workspace list failed" })).toHaveTextContent("Could not load workspaces."); expect(screen.queryByText("No published workspaces.")).not.toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Retry workspace list" })); expect(await screen.findByText("No published workspaces.")).toBeVisible(); expect(calls).toBe(2); }); test("shows an accessible retry when the selected workspace detail query fails", async () => { const user = userEvent.setup(); let calls = 0; server.use(http.get("/api/workspaces/psd-clinical", () => { calls += 1; return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json({ workspace, revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "operational" }, }); })); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("alert", { name: "Workspace details failed" })).toHaveTextContent("Could not load workspace details."); await user.click(screen.getByRole("button", { name: "Retry workspace details" })); expect(await screen.findByRole("heading", { name: "PSD Clinical" })).toBeVisible(); expect(calls).toBe(2); });