import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { act, render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { http, HttpResponse } from "msw"; import { beforeEach, expect, test, vi } from "vitest"; import { server } from "../test/msw"; import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures"; import { WorkspaceManager } from "./WorkspaceManager"; import { setAuthState } from "../auth/authState"; import { queryClient } from "../app/queryClient"; const workspace = canonicalWorkspaceFixture("psd-clinical"); const revision = workspaceRevisionFixture("psd-clinical"); const authenticatedWorkspaceUser = { issuer: "local", subject: "workspace-user", roles: ["user"] as const, permissions: ["workspace.manage", "workspace.secrets.manage"], isAdmin: false, csrfToken: "w".repeat(43), session: null, }; const requirement = { id: "dwh.password", connector: "dwh", label: "Data warehouse password", description: "Password used by the selected data warehouse connection.", input: "password", required: true, configured: false, }; const readyAuthentication = { ready: true, mode: "none", checks: [{ level: "info", code: "auth_ready", message: "Authentication is ready." }], }; function runtimeConfiguration(configured = false) { return { workspaceId: "psd-clinical", revision, configurationState: configured ? "ready" : "configuration_required", requirements: [{ ...requirement, configured }], }; } function renderManager(onClose = vi.fn()) { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return { onClose, ...render( , ), }; } function renderDeniedManager() { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( , ); } type ManagerSettings = { open: boolean; canManageWorkspace: boolean; canManageSecrets: boolean; }; function renderManagerWithSettings(initial: Partial = {}) { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const onClose = vi.fn(); let settings: ManagerSettings = { open: true, canManageWorkspace: true, canManageSecrets: true, ...initial, }; const tree = () => ( ); const view = render(tree()); return { ...view, client, onClose, rerender(next: Partial) { settings = { ...settings, ...next }; view.rerender(tree()); }, }; } beforeEach(() => { localStorage.clear(); setAuthState(authenticatedWorkspaceUser); server.use( http.get("/api/workspace-registry/status", () => HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false, repository: { host: "git.example.test", repository: "analytics/thoth-workspaces", transport: "ssh", }, })), http.get("/api/workspaces", () => HttpResponse.json([ workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, }), ])), http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace, revision })), http.get("/api/workspaces/psd-clinical/runtime-configuration", () => ( HttpResponse.json(runtimeConfiguration()) )), ); }); test("defaults workspace mutations and secrets to denied", async () => { renderDeniedManager(); expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible(); expect(screen.queryByRole("button", { name: "Update workspace repository" })).not.toBeInTheDocument(); await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible(); expect(screen.queryByRole("button", { name: "Validate workspace source" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Test workspace connections" })).not.toBeInTheDocument(); expect(screen.queryByRole("heading", { name: "Runtime secrets" })).not.toBeInTheDocument(); }); test("uses the shared modeless work-area panel and keeps workspace content scrollable", () => { renderManager(); const dialog = screen.getByRole("dialog", { name: "Workspace management" }); expect(dialog).toHaveAttribute("aria-modal", "false"); expect(dialog).toHaveAttribute("data-work-area-panel"); expect(dialog).toHaveAttribute("data-width", "expanded"); expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="header"]')).toHaveLength(1); expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="body"]')).toHaveLength(1); expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="footer"]')).toHaveLength(0); expect(screen.getByRole("region", { name: "Workspace configuration" })).toHaveClass("overflow-y-auto"); }); test("offers a button above the workspace list that returns to Level 1", async () => { const user = userEvent.setup(); const onClose = vi.fn(); renderManager(onClose); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible(); const navigation = screen.getByRole("navigation", { name: "Workspaces" }); const backButton = within(navigation).getByRole("button", { name: "Back to Level 1" }); expect(backButton).toHaveClass("border-border", "bg-card", "w-full"); expect(backButton.compareDocumentPosition(within(navigation).getByText("Available workspaces")) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); await user.click(backButton); expect(onClose).not.toHaveBeenCalled(); expect(screen.getByTestId("workspace-overview")).toBeVisible(); expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument(); }); test("level one explains workspace files, shared repositories, and the read-only Git flow", async () => { const user = userEvent.setup(); server.use(http.post("/api/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false, }))); renderManager(); expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible(); const overview = screen.getByTestId("workspace-overview"); expect(within(overview).getByRole("heading", { name: "What workspace files tell ThothII" })).toBeVisible(); expect(within(overview).getByText(/A workspace file describes one data environment/i)).toBeVisible(); expect(within(overview).getByText(/one Git repository can contain many workspaces/i)).toBeVisible(); expect(within(overview).getByText(/each workspace has its own directory/i)).not.toHaveTextContent(/database target/i); expect(within(overview).getByText(/each workspace has its own directory/i)).toHaveTextContent(/Evidence/i); const repositorySteps = within(overview).getByRole("list"); expect(repositorySteps).toHaveClass("gap-0"); expect(within(repositorySteps).getAllByRole("listitem")).toHaveLength(3); expect(within(overview).getByRole("link", { name: /workspace authoring instructions on GitHub/i })).toHaveAttribute( "href", "https://github.com/mptyl/ThothII/blob/main/docs/install/local-workspace-registry.md#prepare-and-publish-a-workspace-source", ); const repositoryLink = await within(overview).findByRole("link", { name: "git.example.test/analytics/thoth-workspaces", }); expect(repositoryLink).toHaveAttribute("href", "https://git.example.test/analytics/thoth-workspaces"); expect(repositoryLink).toHaveAttribute("target", "_blank"); expect(repositoryLink).toHaveAttribute("rel", "noreferrer"); expect(within(overview).getByText(/configured during ThothII installation/i)).toBeVisible(); expect(within(overview).getAllByText(/managed read-only checkout/i)).toHaveLength(2); expect(within(overview).getByText(/current active revision remains unchanged/i)).toBeVisible(); expect(within(overview).getByText(/No workspace selection is required/i)).toBeVisible(); await user.click(screen.getByRole("button", { name: "Update workspace repository" })); expect(await screen.findByText("Workspace repository updated and validated.")).toBeVisible(); expect(screen.getByText(/A workspace file describes one data environment/i)).toBeInTheDocument(); expect(screen.queryByText(/create a workspace repository/i)).not.toBeInTheDocument(); expect(screen.queryByText(/import|export|bundle/i)).not.toBeInTheDocument(); }); test("shows a local workspace repository as text instead of an invalid web link", async () => { server.use(http.get("/api/workspace-registry/status", () => HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false, repository: { host: "local", repository: "configured-repository", transport: "local", }, }))); renderManager(); const overview = await screen.findByTestId("workspace-overview"); expect(await within(overview).findByText("local/configured-repository")).toBeVisible(); expect(within(overview).queryByRole("link", { name: "local/configured-repository" })).not.toBeInTheDocument(); }); test("workspace-specific commands remain isolated until a workspace is selected", async () => { const user = userEvent.setup(); renderManager(); expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("heading", { name: "About this workspace" })).toBeVisible(); expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible(); expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/workspace.yaml/i); expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/Evidence/i); expect(screen.getByText(/exact version currently activated by ThothII/i)).toBeVisible(); expect(screen.getByText(/does not modify the repository/i)).toBeVisible(); expect(screen.getByText(/confirms that workspace.yaml and the directories/i)).toBeVisible(); expect(screen.getByText(/database configured in Database Management/i)).toBeVisible(); expect(screen.getByText(/Evidence source and the installation semantic services/i)).toBeVisible(); expect(screen.getByText(/result is informational/i)).toBeVisible(); const languageField = screen.getByText("Workspace language").parentElement; expect(languageField).not.toBeNull(); expect(languageField).toHaveTextContent("en"); expect(screen.getByRole("button", { name: "Validate workspace source" })).toBeVisible(); expect(screen.getByRole("button", { name: "Test workspace connections" })).toBeVisible(); }); test("keeps validation and connection results inside their respective action cards", async () => { const user = userEvent.setup(); server.use( http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, })), http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({ activatable: false, diagnostics: [{ level: "error", code: "connector_unavailable", message: "Connector diagnostic failed." }], authentication: readyAuthentication, })), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); const validationCard = screen.getByTestId("workspace-validation-card"); const connectionCard = screen.getByTestId("workspace-connection-card"); await user.click(within(validationCard).getByRole("button", { name: "Validate workspace source" })); const validationStatus = await within(validationCard).findByRole("status"); expect(validationStatus).toHaveTextContent("Workspace source and authentication are valid."); expect(validationStatus).toHaveClass("text-emerald-700"); expect(within(connectionCard).queryByText("Workspace source and authentication are valid.")).not.toBeInTheDocument(); await user.click(within(connectionCard).getByRole("button", { name: "Test workspace connections" })); expect(await within(connectionCard).findByRole("alert")).toHaveTextContent( "connector_unavailable: Connector diagnostic failed.", ); expect(within(validationCard).queryByText("connector_unavailable: Connector diagnostic failed.")).not.toBeInTheDocument(); }); test("renders one authentication section with configured-group errors and no unmapped-group list", async () => { const user = userEvent.setup(); server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: false, diagnostics: [], authentication: { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_mapped_group_missing", field: "Thoth Administrators", message: "A configured authorization group does not exist.", }], }, }))); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); const section = await screen.findByTestId("workspace-authentication"); expect(within(section).getByRole("heading", { name: "Authentication" })).toBeVisible(); expect(within(section).getByText("Failed")).toBeVisible(); expect(within(section).getByText("oidc_mapped_group_missing: Thoth Administrators — A configured authorization group does not exist.")).toBeVisible(); expect(within(section).queryByText(/unmapped/i)).not.toBeInTheDocument(); }); test("clears a previous authentication result as soon as validation is retried", async () => { const user = userEvent.setup(); let calls = 0; let releaseRetry!: () => void; const retryStarted = new Promise((resolve) => { releaseRetry = resolve; }); server.use(http.post("/api/workspaces/validate", async () => { calls += 1; if (calls > 1) await retryStarted; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, }); })); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); const button = screen.getByRole("button", { name: "Validate workspace source" }); await user.click(button); expect(await screen.findByTestId("workspace-authentication")).toBeVisible(); await user.click(button); await waitFor(() => expect(calls).toBe(2)); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); releaseRetry(); expect(await screen.findByTestId("workspace-authentication")).toBeVisible(); }); test("ignores an older validation response that completes after a newer connection test", async () => { const user = userEvent.setup(); let releaseValidation!: () => void; let releaseTest!: () => void; let validationStarted!: () => void; let testStarted!: () => void; let validationSettled!: () => void; const heldValidation = new Promise((resolve) => { releaseValidation = resolve; }); const heldTest = new Promise((resolve) => { releaseTest = resolve; }); const validationRequestStarted = new Promise((resolve) => { validationStarted = resolve; }); const testRequestStarted = new Promise((resolve) => { testStarted = resolve; }); const validationRequestSettled = new Promise((resolve) => { validationSettled = resolve; }); server.use( http.post("/api/workspaces/validate", async () => { validationStarted(); try { await heldValidation; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, }); } finally { validationSettled(); } }), http.post("/api/workspaces/psd-clinical/test", async () => { testStarted(); await heldTest; return HttpResponse.json({ activatable: false, diagnostics: [], authentication: { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "Newer connection result." }], }, }); }), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); await validationRequestStarted; await user.click(screen.getByRole("button", { name: "Test workspace connections" })); await testRequestStarted; expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); act(() => releaseTest()); const authentication = await screen.findByTestId("workspace-authentication"); expect(within(authentication).getByText(/Newer connection result/)).toBeVisible(); expect(screen.getByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); act(() => releaseValidation()); await act(async () => { await validationRequestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(within(authentication).getByText(/Newer connection result/)).toBeVisible(); expect(within(authentication).queryByText("Passed")).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); }); test("does not apply a diagnostic that completes after its dialog is closed and reopened", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.post("/api/workspaces/validate", async () => { started(); try { await held; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, }); } finally { settled(); } })); const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const onClose = vi.fn(); const view = render( , ); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Close workspace management" })); expect(onClose).toHaveBeenCalledTimes(1); view.rerender( , ); view.rerender( , ); expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); }); test("does not apply a diagnostic that completes after returning through Level 1", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.post("/api/workspaces/validate", async () => { started(); try { await held; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, }); } finally { settled(); } })); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); await requestStarted; await user.click(within(screen.getByRole("navigation", { name: "Workspaces" })) .getByRole("button", { name: "Back to Level 1" })); expect(screen.getByTestId("workspace-overview")).toBeVisible(); await user.click(screen.getByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); }); test("does not apply a diagnostic after changing workspaces and returning", async () => { const user = userEvent.setup(); const alternateId = "other-clinical"; const alternateWorkspace = canonicalWorkspaceFixture(alternateId); const alternateRevision = workspaceRevisionFixture(alternateId); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use( http.get("/api/workspaces", () => HttpResponse.json([ workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, }), workspaceSummaryFixture(alternateId, { displayName: "Other Clinical", description: "Other data", configurationState: "configuration_required", revision: alternateRevision, }), ])), http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({ workspace: alternateWorkspace, revision: alternateRevision, })), http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({ workspaceId: alternateId, revision: alternateRevision, configurationState: "configuration_required", requirements: [{ ...requirement }], })), http.post("/api/workspaces/validate", async () => { started(); try { await held; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication, }); } finally { settled(); } }), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Other Clinical" })); expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible(); await user.click(screen.getByRole("button", { name: "PSD Clinical" })); expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); }); test("never renders a hostile authentication field rejected by the API decoder", async () => { const user = userEvent.setup(); const attacker = "attacker-field-SENTINEL"; server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: false, diagnostics: [], authentication: { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", field: attacker }], }, }))); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); expect(await screen.findByRole("alert")).toBeVisible(); expect(screen.queryByText(new RegExp(attacker))).not.toBeInTheDocument(); }); test("renders binding_ok as a green connection success", async () => { const user = userEvent.setup(); server.use( http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({ activatable: true, diagnostics: [{ level: "info", code: "binding_ok", message: "Installation bindings and diagnostics succeeded." }], authentication: readyAuthentication, })), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); const connectionCard = screen.getByTestId("workspace-connection-card"); await user.click(within(connectionCard).getByRole("button", { name: "Test workspace connections" })); const connectionStatus = await within(connectionCard).findByRole("status"); expect(connectionStatus).toHaveTextContent("binding_ok: Installation bindings and diagnostics succeeded."); expect(connectionStatus).toHaveClass("text-emerald-700"); expect(within(connectionCard).queryByRole("alert")).not.toBeInTheDocument(); }); test("secret fields are write-only, clear after blind save, and may be forgotten", async () => { const user = userEvent.setup(); let savedBody: unknown; server.use( http.put("/api/workspaces/psd-clinical/secrets", async ({ request }) => { savedBody = await request.json(); return HttpResponse.json(runtimeConfiguration(true)); }), http.delete("/api/workspaces/psd-clinical/secrets/dwh.password", () => ( HttpResponse.json(runtimeConfiguration(false)) )), ); renderManager(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); const input = await screen.findByLabelText("Data warehouse password"); expect(input).toHaveValue(""); expect(input).toHaveAttribute("type", "password"); expect(screen.getByText("Not configured")).toBeVisible(); await user.type(input, "one-time-password"); await user.click(screen.getByRole("button", { name: "Save entered secrets" })); await waitFor(() => expect(savedBody).toEqual({ values: { "dwh.password": "one-time-password" }, })); expect(input).toHaveValue(""); expect(await screen.findByText("Configured")).toBeVisible(); expect(screen.queryByDisplayValue("one-time-password")).not.toBeInTheDocument(); expect(localStorage.length).toBe(0); await user.click(screen.getByRole("button", { name: "Forget stored Data warehouse password" })); expect(await screen.findByText("Not configured")).toBeVisible(); }); test("a delayed runtime-secret save from user A cannot repopulate user B's cache or notice", async () => { let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => { started(); try { await held; return HttpResponse.json(runtimeConfiguration(true)); } finally { settled(); } })); queryClient.clear(); setAuthState({ issuer: "local", subject: "user-a", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "a".repeat(43), session: null }); render( , ); await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" })); const input = await screen.findByLabelText("Data warehouse password"); await userEvent.type(input, "a-secret"); await userEvent.click(screen.getByRole("button", { name: "Save entered secrets" })); await requestStarted; act(() => setAuthState({ issuer: "local", subject: "user-b", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "b".repeat(43), session: null })); expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined(); release(); await act(async () => { await requestSettled; }); expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined(); expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument(); }); test("a deferred secret save cannot update a reopened dialog", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => { started(); try { await held; return HttpResponse.json(runtimeConfiguration(true)); } finally { settled(); } })); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password"); await user.click(screen.getByRole("button", { name: "Save entered secrets" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Close workspace management" })); manager.rerender({ open: false }); manager.rerender({ open: true }); await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret"); expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled(); const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease); expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument(); }); test("a deferred secret save cannot update a workspace selected again after a cross-workspace transition", async () => { const user = userEvent.setup(); const alternateId = "other-clinical"; const alternateWorkspace = canonicalWorkspaceFixture(alternateId); const alternateRevision = workspaceRevisionFixture(alternateId); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use( http.get("/api/workspaces", () => HttpResponse.json([ workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, }), workspaceSummaryFixture(alternateId, { displayName: "Other Clinical", description: "Other data", configurationState: "configuration_required", revision: alternateRevision, }), ])), http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({ workspace: alternateWorkspace, revision: alternateRevision })), http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({ workspaceId: alternateId, revision: alternateRevision, configurationState: "configuration_required", requirements: [{ ...requirement }], })), http.put("/api/workspaces/psd-clinical/secrets", async () => { started(); try { await held; return HttpResponse.json(runtimeConfiguration(true)); } finally { settled(); } }), ); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password"); await user.click(screen.getByRole("button", { name: "Save entered secrets" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Other Clinical" })); expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible(); await user.click(screen.getByRole("button", { name: "PSD Clinical" })); await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret"); expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled(); const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease); expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument(); }); test("a deferred secret save cannot update a context after its secret permission changes", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => { started(); try { await held; return HttpResponse.json(runtimeConfiguration(true)); } finally { settled(); } })); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password"); await user.click(screen.getByRole("button", { name: "Save entered secrets" })); await requestStarted; manager.rerender({ canManageSecrets: false }); manager.rerender({ canManageSecrets: true }); await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret"); expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled(); const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease); expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument(); }); test("a deferred repository update cannot update a reopened dialog or refetch its cache", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; let statusRequests = 0; let workspaceRequests = 0; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use( http.get("/api/workspace-registry/status", () => { statusRequests++; return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false }); }), http.get("/api/workspaces", () => { workspaceRequests++; return HttpResponse.json([workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, })]); }), http.post("/api/workspace-registry/pull", async () => { started(); try { await held; return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false }); } finally { settled(); } }), ); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "Update workspace repository" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Close workspace management" })); manager.rerender({ open: false }); manager.rerender({ open: true }); expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled(); const requestsBeforeRelease = { statusRequests, workspaceRequests }; act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease); expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument(); }); test("a deferred repository update cannot update a context after a cross-workspace transition", async () => { const user = userEvent.setup(); const alternateId = "other-clinical"; const alternateWorkspace = canonicalWorkspaceFixture(alternateId); const alternateRevision = workspaceRevisionFixture(alternateId); let release!: () => void; let started!: () => void; let settled!: () => void; let statusRequests = 0; let workspaceRequests = 0; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use( http.get("/api/workspace-registry/status", () => { statusRequests++; return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false }); }), http.get("/api/workspaces", () => { workspaceRequests++; return HttpResponse.json([ workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, }), workspaceSummaryFixture(alternateId, { displayName: "Other Clinical", description: "Other data", configurationState: "configuration_required", revision: alternateRevision, }), ]); }), http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({ workspace: alternateWorkspace, revision: alternateRevision })), http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({ workspaceId: alternateId, revision: alternateRevision, configurationState: "configuration_required", requirements: [{ ...requirement }], })), http.post("/api/workspace-registry/pull", async () => { started(); try { await held; return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false }); } finally { settled(); } }), ); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "Update workspace repository" })); await requestStarted; await user.click(screen.getByRole("button", { name: "Other Clinical" })); expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible(); await user.click(within(screen.getByRole("navigation", { name: "Workspaces" })) .getByRole("button", { name: "Back to Level 1" })); expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled(); const requestsBeforeRelease = { statusRequests, workspaceRequests }; act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease); expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument(); }); test("a deferred repository update cannot update a context after workspace permission changes", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; let statusRequests = 0; let workspaceRequests = 0; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use( http.get("/api/workspace-registry/status", () => { statusRequests++; return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false }); }), http.get("/api/workspaces", () => { workspaceRequests++; return HttpResponse.json([workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision, })]); }), http.post("/api/workspace-registry/pull", async () => { started(); try { await held; return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false }); } finally { settled(); } }), ); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "Update workspace repository" })); await requestStarted; manager.rerender({ canManageWorkspace: false }); manager.rerender({ canManageWorkspace: true }); expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled(); const requestsBeforeRelease = { statusRequests, workspaceRequests }; act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease); expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument(); }); test("a deferred validation cannot update a context after workspace permission changes", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.post("/api/workspaces/validate", async () => { started(); try { await held; return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication }); } finally { settled(); } })); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Validate workspace source" })); await requestStarted; manager.rerender({ canManageWorkspace: false }); manager.rerender({ canManageWorkspace: true }); expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled(); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); }); test("a deferred connection test cannot update a context after workspace permission changes", async () => { const user = userEvent.setup(); let release!: () => void; let started!: () => void; let settled!: () => void; const held = new Promise((resolve) => { release = resolve; }); const requestStarted = new Promise((resolve) => { started = resolve; }); const requestSettled = new Promise((resolve) => { settled = resolve; }); server.use(http.post("/api/workspaces/psd-clinical/test", async () => { started(); try { await held; return HttpResponse.json({ activatable: true, diagnostics: [], authentication: readyAuthentication }); } finally { settled(); } })); const manager = renderManagerWithSettings(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.click(screen.getByRole("button", { name: "Test workspace connections" })); await requestStarted; manager.rerender({ canManageWorkspace: false }); manager.rerender({ canManageWorkspace: true }); expect(await screen.findByRole("button", { name: "Test workspace connections" })).not.toBeDisabled(); act(() => release()); await act(async () => { await requestSettled; await new Promise((resolve) => { setTimeout(resolve, 50); }); }); expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument(); }); test("closing clears unsaved secret fields", async () => { const user = userEvent.setup(); const onClose = vi.fn(); renderManager(onClose); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); await user.type(await screen.findByLabelText("Data warehouse password"), "unsaved-value"); await user.click(screen.getByRole("button", { name: "Close workspace management" })); expect(onClose).toHaveBeenCalledTimes(1); expect(screen.queryByDisplayValue("unsaved-value")).not.toBeInTheDocument(); });