import { execFile } from "node:child_process"; import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; import { afterEach, expect, test, vi } from "vitest"; import { buildApp } from "../src/app.js"; import { loadConfig } from "../src/config.js"; import { createProductionWorkspaceDiagnoser } from "../src/workspaces/diagnostics.js"; import { WorkspaceRegistry, type WorkspaceRevision } from "../src/workspaces/registry.js"; import { serializeWorkspaceYaml, type CanonicalWorkspace } from "../src/workspaces/schema.js"; import { WorkspaceSecretStore } from "../src/workspaces/secret-store.js"; import type { AuthDiagnoser, AuthDiagnostics } from "../src/auth/diagnostics.js"; import type { WorkspaceDatabase } from "../src/catalog/types.js"; import type { WorkspaceDatabaseTester } from "../src/routes/workspaces.js"; const workspace: CanonicalWorkspace = { workspace: { schema_version: 4, id: "psd-clinical", name: "Policlinico San Donato", description: "Clinical analytics workspace", language: "it", }, dwh: { engine: "postgres", database: "warehouse", schema: "datawarehouse", supported_transports: ["postgres_direct"], }, }; const revision: WorkspaceRevision = { id: workspace.workspace.id, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/registry/snapshots/psd-clinical.yaml", }; type RegistryFake = Pick; function registryFake(overrides: Partial = {}): RegistryFake { return { bootstrap: vi.fn(async () => ({ branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false, })), pull: vi.fn(async () => ({ branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false, })), list: vi.fn(async () => [revision]), listCatalog: vi.fn(async () => [{ id: "psd-clinical", name: "Policlinico San Donato", configurationState: "ready" as const, revision, }]), read: vi.fn(async () => ({ workspace, revision })), ...overrides, }; } const readyAuthentication: AuthDiagnostics = { ready: true, mode: "none", checks: [{ level: "info", code: "auth_ready", message: "Authentication is ready." }], }; const reachableWorkspaceDatabase: WorkspaceDatabase = { id: "db-psd-clinical", workspaceId: "psd-clinical", engine: "postgres", databaseName: "warehouse", schema: "datawarehouse", version: 1, createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", binding: { transport: "postgres_direct", host: "current-db.internal", port: 5432, username: "current-reader", }, connectionStatus: "reachable", testedVersion: 1, lastTestedAt: "2026-01-01T00:00:00.000Z", }; function appFor( registry: RegistryFake, diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] })), secretStore = testSecretStore(), env: Record = {}, authDiagnoser: AuthDiagnoser = { inspect: vi.fn(async () => readyAuthentication) }, workspaceDatabaseTester: WorkspaceDatabaseTester = vi.fn( async () => reachableWorkspaceDatabase, ), ) { return buildApp(loadConfig({ THT_HARNESS_DIR: "/missing-harness", THT_WORKSPACE_REGISTRY_ROOT: "/tmp/thoth-route-test-registry", ...env, }), { thtRunner: {} as any, workspaceRegistry: registry as WorkspaceRegistry, workspaceDiagnoser: diagnose, workspaceSecretStore: secretStore, authDiagnoser, workspaceDatabaseTester, } as any); } const userHeaders = { "x-thoth-principal-issuer": "portal", "x-thoth-principal-subject": "alice", "x-thoth-is-admin": "0", }; const adminHeaders = { ...userHeaders, "x-thoth-principal-subject": "admin", "x-thoth-is-admin": "1" }; const secretStoreRoots: string[] = []; function testSecretStore(): WorkspaceSecretStore { const root = mkdtempSync(join(tmpdir(), "thoth-route-secret-store-")); const runtimeRoot = mkdtempSync(join(tmpdir(), "thoth-route-secret-runtime-")); secretStoreRoots.push(root, runtimeRoot); return new WorkspaceSecretStore({ root, runtimeRoot, installationId: "route-test" }); } test("returns a redacted registry status and pulls without Git credential details", async () => { const registry = registryFake({ bootstrap: vi.fn(async () => ({ branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: true, lastError: "git_auth_failed" as const, })), }); const app = appFor(registry); const status = await app.inject({ method: "GET", url: "/workspace-registry/status" }); const pull = await app.inject({ method: "POST", url: "/workspace-registry/pull" }); expect(status.statusCode).toBe(200); expect(status.json()).toMatchObject({ branch: "main", degraded: true, lastError: "git_auth_failed" }); expect(status.body).not.toMatch(/token|credential|private.?key/i); expect(pull.statusCode).toBe(200); expect(registry.pull).toHaveBeenCalledTimes(1); }); test("workspace mutations and secret writes require their catalog permissions", async () => { const registry = registryFake(); const app = appFor(registry, undefined, testSecretStore(), { AUTH_MODE: "upstream" }); try { const deniedPull = await app.inject({ method: "POST", url: "/workspace-registry/pull", headers: userHeaders }); const allowedPull = await app.inject({ method: "POST", url: "/workspace-registry/pull", headers: adminHeaders }); const deniedBootstrap = await app.inject({ method: "GET", url: "/workspace-registry/status", headers: userHeaders }); const allowedBootstrap = await app.inject({ method: "GET", url: "/workspace-registry/status", headers: adminHeaders }); const deniedSecret = await app.inject({ method: "PUT", url: "/workspaces/psd-clinical/secrets", headers: userHeaders, payload: { values: { "dwh.password": "secret" } }, }); const allowedSecret = await app.inject({ method: "PUT", url: "/workspaces/psd-clinical/secrets", headers: adminHeaders, payload: { values: { "dwh.password": "secret" } }, }); expect(deniedPull.statusCode).toBe(403); expect(deniedPull.json()).toEqual({ code: "auth_forbidden", error: "This operation is not permitted" }); expect(allowedPull.statusCode).toBe(200); expect(deniedBootstrap.statusCode).toBe(403); expect(deniedBootstrap.json()).toEqual({ code: "auth_forbidden", error: "This operation is not permitted" }); expect(allowedBootstrap.statusCode).toBe(200); expect(deniedSecret.statusCode).toBe(403); expect(deniedSecret.json()).toEqual({ code: "auth_forbidden", error: "This operation is not permitted" }); expect(allowedSecret.statusCode).toBe(200); } finally { await app.close(); } }); test.each([ ["POST", "/workspaces/publish"], ["GET", "/workspaces/psd-clinical/export"], ["POST", "/workspaces/import"], ] as const)("does not register the removed %s %s mutation or bundle route", async (method, url) => { const response = await appFor(registryFake()).inject({ method, url }); expect(response.statusCode).toBe(404); }); test("lists workspace summaries and reads a validated immutable workspace", async () => { const app = appFor(registryFake()); const list = await app.inject({ method: "GET", url: "/workspaces" }); const read = await app.inject({ method: "GET", url: "/workspaces/psd-clinical" }); expect(list.statusCode).toBe(200); expect(list.json()).toEqual([expect.objectContaining({ id: "psd-clinical", displayName: "Policlinico San Donato", configurationState: "configuration_required", revision, })]); expect(read.statusCode).toBe(200); expect(read.json()).toEqual({ workspace, revision }); }); test("validates a schema v4 workspace without mutating the repository", async () => { const app = appFor(registryFake()); const response = await app.inject({ method: "POST", url: "/workspaces/validate", payload: { workspace }, }); expect(response.statusCode).toBe(200); expect(response.json()).toMatchObject({ workspace }); }); test("aggregates one static and one live authentication report without reordering connector diagnostics", async () => { const connectorDiagnostics = [{ level: "info" as const, code: "binding_ok" as const, message: "Installation bindings and diagnostics succeeded.", }]; const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: connectorDiagnostics })); const authentication: AuthDiagnostics = { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_mapped_group_missing", field: "Thoth Administrators", message: "A configured authorization group does not exist.", }], }; const authDiagnoser: AuthDiagnoser = { inspect: vi.fn(async () => authentication) }; const app = appFor(registryFake(), diagnose, testSecretStore(), {}, authDiagnoser); const validation = await app.inject({ method: "POST", url: "/workspaces/validate", payload: { workspace }, }); const connection = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {}, }); expect(validation.statusCode).toBe(200); expect(validation.json()).toMatchObject({ workspace, activatable: false, diagnostics: [], authentication, }); expect(connection.statusCode).toBe(200); expect(connection.json()).toEqual({ activatable: false, diagnostics: connectorDiagnostics, authentication, }); expect(diagnose).toHaveBeenCalledTimes(1); expect(authDiagnoser.inspect).toHaveBeenNthCalledWith(1, { live: false }); expect(authDiagnoser.inspect).toHaveBeenNthCalledWith(2, { live: true }); }); test("fails closed without reflecting a hostile authentication report", async () => { const attacker = "attacker-field-SENTINEL"; const authDiagnoser: AuthDiagnoser = { inspect: vi.fn(async () => ({ ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", field: attacker, }], } as AuthDiagnostics)) }; const app = appFor(registryFake(), undefined, testSecretStore(), {}, authDiagnoser); const response = await app.inject({ method: "POST", url: "/workspaces/validate", payload: { workspace }, }); expect(response.statusCode).toBe(400); expect(response.json()).toEqual({ code: "workspace_invalid", message: "Workspace request is invalid." }); expect(response.body).not.toContain(attacker); }); test.each([1, 2])("rejects schema v%s at the validation boundary with a sanitized error", async (version) => { const legacy = { ...workspace, workspace: { ...workspace.workspace, schema_version: version }, }; const response = await appFor(registryFake()).inject({ method: "POST", url: "/workspaces/validate", payload: { workspace: legacy }, }); expect(response.statusCode).toBe(400); expect(response.json()).toEqual({ code: "workspace_invalid", message: "Workspace request is invalid." }); expect(response.body).not.toMatch(/migration_required|schema version/i); }); test("runs diagnostics for a schema v4 workspace", async () => { const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] })); const app = appFor(registryFake(), diagnose); const response = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {}, }); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ activatable: true, diagnostics: [], authentication: readyAuthentication }); expect(diagnose).toHaveBeenCalledWith(workspace, { dwh: expect.objectContaining({ transport: "postgres_direct" }), evidence: { missing: [], values: {} }, }, { writeProbe: false, skipDwh: true }); }); test("reports a missing Database Management configuration without using the legacy DWH test", async () => { const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [{ level: "info" as const, code: "binding_ok" as const, message: "Installation bindings and diagnostics succeeded.", }], })); const workspaceDatabaseTester = vi.fn(async () => undefined); const app = appFor( registryFake(), diagnose, testSecretStore(), {}, { inspect: vi.fn(async () => readyAuthentication) }, workspaceDatabaseTester, ); const response = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {}, }); expect(response.statusCode).toBe(200); expect(response.json()).toMatchObject({ activatable: false, diagnostics: [{ level: "error", code: "binding_missing", field: "dwh", message: "Configure this workspace in Database Management before testing connections.", }], }); expect(diagnose).toHaveBeenCalledWith( workspace, expect.anything(), { writeProbe: false, skipDwh: true }, ); expect(workspaceDatabaseTester).toHaveBeenCalledWith("psd-clinical"); }); test("reports runtime secret requirements without returning stored values", async () => { const secretStore = testSecretStore(); const app = appFor(registryFake(), undefined, secretStore); const missing = await app.inject({ method: "GET", url: "/workspaces/psd-clinical/runtime-configuration", }); expect(missing.statusCode).toBe(200); expect(missing.json()).toMatchObject({ workspaceId: "psd-clinical", revision, configurationState: "configuration_required", requirements: [{ id: "dwh.password", connector: "dwh", label: "Data warehouse password", required: true, configured: false, }], }); const secret = "never-return-this-password"; const save = await app.inject({ method: "PUT", url: "/workspaces/psd-clinical/secrets", payload: { values: { "dwh.password": secret } }, }); expect(save.statusCode).toBe(200); expect(save.body).not.toContain(secret); expect(save.json()).toMatchObject({ configurationState: "ready", requirements: [{ id: "dwh.password", configured: true }], }); const configured = await app.inject({ method: "GET", url: "/workspaces/psd-clinical/runtime-configuration", }); expect(configured.body).not.toContain(secret); expect(configured.json()).toMatchObject({ configurationState: "ready" }); }); test("rejects undeclared secret identifiers and supports forgetting a configured secret", async () => { const secretStore = testSecretStore(); const app = appFor(registryFake(), undefined, secretStore); const unknown = await app.inject({ method: "PUT", url: "/workspaces/psd-clinical/secrets", payload: { values: { "evidence.secret_key": "not-applicable" } }, }); expect(unknown.statusCode).toBe(400); expect(secretStore.configured("psd-clinical")).toEqual([]); secretStore.put("psd-clinical", "dwh.password", "temporary-password"); const forget = await app.inject({ method: "DELETE", url: "/workspaces/psd-clinical/secrets/dwh.password", }); expect(forget.statusCode).toBe(200); expect(forget.json()).toMatchObject({ configurationState: "configuration_required" }); expect(secretStore.has("psd-clinical", "dwh.password")).toBe(false); }); test("materializes stored secrets only for the diagnostic lease", async () => { const secretStore = testSecretStore(); secretStore.put("psd-clinical", "dwh.password", "diagnostic-password"); let materializedPath = ""; const diagnose = vi.fn(async (_workspace, bindings) => { materializedPath = bindings.dwh.values.THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE; expect(readFileSync(materializedPath, "utf8")).toBe("diagnostic-password"); return { activatable: true, diagnostics: [] }; }); const app = appFor(registryFake(), diagnose, secretStore); const response = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {}, }); expect(response.statusCode).toBe(200); expect(materializedPath).not.toBe(""); expect(existsSync(materializedPath)).toBe(false); }); test("reports a missing Evidence credential without changing the registry revision", async () => { const evidenceWorkspace: CanonicalWorkspace = { ...workspace, evidence: { source: { type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", connect_timeout_ms: 5_000, read_timeout_ms: 30_000, max_bytes: 10 * 1024 * 1024, max_redirects: 5, allow_private_hosts: false, max_cache_bytes: 64 * 1024 * 1024, }, policy: { max_chunk_chars: 4_000, retain_published_generations: 3 }, }, }; const read = vi.fn(async () => ({ workspace: evidenceWorkspace, revision })); const app = appFor(registryFake({ read }), createProductionWorkspaceDiagnoser(100)); const variable = "THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE"; const previous = process.env[variable]; delete process.env[variable]; try { const response = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {}, }); expect(response.statusCode).toBe(200); expect(response.json()).toMatchObject({ activatable: false, diagnostics: expect.arrayContaining([expect.objectContaining({ code: "binding_missing", field: "evidence.source.authentication", variable, })]), }); expect(read).toHaveBeenCalledTimes(1); } finally { if (previous === undefined) delete process.env[variable]; else process.env[variable] = previous; } }); const runFile = promisify(execFile); const realRouteRoots: string[] = []; async function git(cwd: string, args: string[]): Promise { const { stdout } = await runFile("git", args, { cwd }); return stdout.trim(); } async function createRealRouteFixture() { const root = mkdtempSync(join(tmpdir(), "thoth-workspace-route-")); realRouteRoots.push(root); const remote = join(root, "remote.git"); const author = join(root, "author"); const registryRoot = join(root, "registry"); await git(root, ["init", "--bare", "--initial-branch=main", remote]); mkdirSync(author); await git(author, ["init", "--initial-branch=main"]); await git(author, ["config", "user.name", "Workspace Route Test"]); await git(author, ["config", "user.email", "workspace-route@example.invalid"]); const { dwh: _runtimeDwh, diagnostics: _runtimeDiagnostics, ...authoredWorkspace } = workspace; const descriptor: CanonicalWorkspace = { ...authoredWorkspace, evidence: { source: { type: "filesystem", uri: "psd-clinical/evidence", patterns: ["**/*.md"], max_bytes: 1024 * 1024, }, policy: { max_chunk_chars: 4_000, retain_published_generations: 3 }, }, }; writeFileSync(join(author, "thoth-workspaces.yaml"), [ "schema_version: 1", "workspaces:", " - id: psd-clinical", " name: Policlinico San Donato", " description: Clinical analytics workspace", "", ].join("\n")); mkdirSync(join(author, "psd-clinical", "evidence"), { recursive: true }); writeFileSync(join(author, "psd-clinical", "workspace.yaml"), serializeWorkspaceYaml(descriptor)); writeFileSync(join(author, "psd-clinical", "evidence", "guide.md"), "Evidence bytes\n"); await git(author, ["add", "."]); await git(author, ["commit", "-m", "Initial workspace"]); await git(author, ["remote", "add", "origin", remote]); await git(author, ["push", "origin", "main"]); const initialCommit = await git(author, ["rev-parse", "HEAD"]); const config = loadConfig({ THT_HARNESS_DIR: "/missing-harness", THT_WORKSPACE_REGISTRY_ROOT: registryRoot, THT_WORKSPACE_GIT_REMOTE: remote, }); const registry = new WorkspaceRegistry(config.workspaceRegistry); const app = buildApp(config, { thtRunner: {} as any, workspaceRegistry: registry, workspaceDiagnoser: vi.fn(async () => ({ activatable: true, diagnostics: [] })), }); return { author, initialCommit, app, registry }; } afterEach(() => { realRouteRoots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })); secretStoreRoots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })); }); test("a failed candidate pull keeps the last valid active workspace", async () => { const fixture = await createRealRouteFixture(); await fixture.registry.bootstrap(); rmSync(join(fixture.author, "psd-clinical", "evidence"), { recursive: true }); await git(fixture.author, ["add", "-A"]); await git(fixture.author, ["commit", "-m", "Remove required Evidence tree"]); await git(fixture.author, ["push", "origin", "main"]); const pull = await fixture.app.inject({ method: "POST", url: "/workspace-registry/pull" }); expect(pull.statusCode).toBe(400); expect(pull.json()).toEqual({ code: "workspace_invalid", message: "Workspace request is invalid." }); await expect(fixture.registry.read("psd-clinical")).resolves.toMatchObject({ revision: { commit: fixture.initialCommit }, }); });