import { expect, test } from "vitest"; import { http, HttpResponse } from "msw"; import { server } from "../test/msw"; import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures"; import { forgetWorkspaceSecret, getWorkspace, getWorkspaceRuntimeConfiguration, listWorkspaces, saveWorkspaceSecrets, testWorkspace, validateWorkspace, } from "./workspaces"; const workspace = canonicalWorkspaceFixture("psd-clinical"); const revision = workspaceRevisionFixture("psd-clinical"); const runtimeConfiguration = { workspaceId: "psd-clinical", revision, configurationState: "configuration_required", requirements: [{ id: "dwh.password", connector: "dwh", label: "Data warehouse password", description: "Password used by the selected data warehouse connection.", input: "password", required: true, configured: false, }], } as const; test("decodes read-only workspace summaries with a revision in every readiness state", async () => { const ready = workspaceSummaryFixture("psd-clinical", { displayName: "PSD Clinical", revision, }); const needsSecrets = { ...ready, configurationState: "configuration_required" as const, }; server.use(http.get("/api/workspaces", () => HttpResponse.json([ready, needsSecrets]))); await expect(listWorkspaces()).resolves.toEqual([ready, needsSecrets]); }); test("accepts the immutable workspace revision contract", async () => { server.use(http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace, revision }))); await expect(getWorkspace("psd-clinical")).resolves.toEqual({ workspace, revision }); }); test("accepts the evidence schema version materialized by the backend", async () => { const persistedWorkspace = { ...workspace, evidence: { schema_version: 1, source: { type: "filesystem", uri: "psd-clinical/evidence", patterns: ["**/*.md"], max_bytes: 10 * 1024 * 1024, }, policy: { max_chunk_chars: 5000, retain_published_generations: 3 }, }, }; server.use(http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace: persistedWorkspace, revision, }))); await expect(getWorkspace("psd-clinical")).resolves.toEqual({ workspace: persistedWorkspace, revision, }); }); test("validates a workspace read from the backend without losing its Evidence schema version", async () => { const persistedWorkspace = { ...workspace, evidence: { schema_version: 1, source: { type: "filesystem", uri: "psd-clinical/evidence", patterns: ["curated/**/*.md"], max_bytes: 10 * 1024 * 1024, }, policy: { max_chunk_chars: 5000, retain_published_generations: 3 }, }, }; let validationRequest: unknown; server.use( http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace: persistedWorkspace, revision, })), http.post("/api/workspaces/validate", async ({ request }) => { validationRequest = await request.json(); return HttpResponse.json({ workspace: persistedWorkspace, contract: {}, activatable: true, diagnostics: [], authentication: { ready: true, mode: "local", checks: [{ level: "info", code: "auth_ready", message: "Authentication is ready." }], }, }); }), ); const loaded = await getWorkspace("psd-clinical"); await expect(validateWorkspace(loaded.workspace)).resolves.toMatchObject({ activatable: true }); expect(validationRequest).toEqual({ workspace: persistedWorkspace }); }); test("decodes runtime requirements but rejects any secret value returned by the server", async () => { server.use(http.get( "/api/workspaces/psd-clinical/runtime-configuration", () => HttpResponse.json(runtimeConfiguration), )); await expect(getWorkspaceRuntimeConfiguration("psd-clinical")) .resolves.toEqual(runtimeConfiguration); server.use(http.get( "/api/workspaces/psd-clinical/runtime-configuration", () => HttpResponse.json({ ...runtimeConfiguration, requirements: [{ ...runtimeConfiguration.requirements[0], value: "leaked-secret" }], }), )); await expect(getWorkspaceRuntimeConfiguration("psd-clinical")) .rejects.toThrow("invalid runtime configuration"); }); test("blind secret replacement sends values once and returns status only", async () => { let requestBody: unknown; server.use(http.put("/api/workspaces/psd-clinical/secrets", async ({ request }) => { requestBody = await request.json(); return HttpResponse.json({ ...runtimeConfiguration, configurationState: "ready", requirements: [{ ...runtimeConfiguration.requirements[0], configured: true }], }); })); const response = await saveWorkspaceSecrets("psd-clinical", { "dwh.password": "one-time-value", }); expect(requestBody).toEqual({ values: { "dwh.password": "one-time-value" } }); expect(response.configurationState).toBe("ready"); expect(JSON.stringify(response)).not.toContain("one-time-value"); }); test("forget targets one declared requirement", async () => { let called = false; server.use(http.delete("/api/workspaces/psd-clinical/secrets/dwh.password", () => { called = true; return HttpResponse.json(runtimeConfiguration); })); await expect(forgetWorkspaceSecret("psd-clinical", "dwh.password")) .resolves.toEqual(runtimeConfiguration); expect(called).toBe(true); }); test("decodes the shared authentication diagnostics on static validation and live connection tests", async () => { const authentication = { ready: false, mode: "oidc" as const, checks: [{ level: "error" as const, code: "oidc_mapped_group_missing" as const, field: "Thoth Administrators", message: "A configured authorization group does not exist.", }], }; server.use( http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: false, diagnostics: [], authentication, })), http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({ activatable: false, diagnostics: [], authentication, })), ); await expect(validateWorkspace(workspace)).resolves.toMatchObject({ activatable: false, authentication, }); await expect(testWorkspace("psd-clinical")).resolves.toMatchObject({ activatable: false, authentication, }); }); test.each([ ["ready with error", { ready: true, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure" }] }], ["failed with ready", { ready: false, mode: "oidc", checks: [{ level: "info", code: "auth_ready", message: "ready" }] }], ["failed without error", { ready: false, mode: "oidc", checks: [{ level: "info", code: "auth_config_invalid", message: "info" }] }], ["duplicate", { ready: false, mode: "oidc", checks: [ { level: "error", code: "oidc_secret_missing", message: "one" }, { level: "error", code: "oidc_secret_missing", message: "two" }, ] }], ["attacker field", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", field: "attacker-field-SENTINEL" }] }], ["control", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_mapped_group_missing", message: "failure", field: "bad\u0085field" }] }], ["unexpected property", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", attacker: "field" }] }], ])("rejects hostile authentication diagnostics: %s", async (_name, authentication) => { server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: false, diagnostics: [], authentication, }))); await expect(validateWorkspace(workspace)).rejects.toThrow("invalid authentication diagnostics"); }); test("rejects a workspace claimed activatable when authentication is not ready", async () => { server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "Authentication is unavailable." }], }, }))); await expect(validateWorkspace(workspace)).rejects.toThrow("invalid diagnostic result"); });