import { createHash } from "node:crypto"; import { once } from "node:events"; import { Buffer } from "node:buffer"; import { expect, test, vi } from "vitest"; import yazl from "yazl"; import { buildApp } from "../src/app.js"; import { loadConfig } from "../src/config.js"; import { WorkspaceRegistryError } from "../src/workspaces/git-repository.js"; import type { WorkspaceRegistry, WorkspaceRevision } from "../src/workspaces/registry.js"; import { renderWorkspaceDocs, serializeWorkspaceYaml, type CanonicalWorkspace } from "../src/workspaces/schema.js"; const workspace: CanonicalWorkspace = { workspace: { schema_version: 2, id: "psd-clinical", name: "Policlinico San Donato", description: "Clinical analytics workspace", language: "it", }, dwh: { engine: "postgres", database: "warehouse", schema: "datawarehouse", supported_transports: ["postgres_direct"], }, semantic_index: { vector_store: { engine: "pgvector", database: "warehouse", schema: "vectors", collection: "clinical_documents", dimensions: 768, distance: "cosine", supported_transports: ["pgvector_direct"], }, embedding: { provider: "ollama_compatible", model: "nomic-embed-text-v2-moe", dimensions: 768, }, }, llm_policy: { allowed: ["zai/glm-5.2"] }, }; const revision: WorkspaceRevision = { id: workspace.workspace.id, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/registry/snapshots/psd-clinical.yaml", state: "operational", }; 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]), read: vi.fn(async () => ({ workspace, revision })), publish: vi.fn(async () => revision), ...overrides, }; } function appFor(registry: RegistryFake, diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] }))) { return buildApp(loadConfig({ THT_HARNESS_DIR: "/missing-harness", THT_WORKSPACE_REGISTRY_ROOT: "/tmp/thoth-route-test-registry", }), { thtRunner: {} as any, workspaceRegistry: registry as WorkspaceRegistry, workspaceDiagnoser: diagnose, } as any); } function sha256(value: string): string { return createHash("sha256").update(value).digest("hex"); } async function zip(files: Record): Promise { const archive = new yazl.ZipFile(); const chunks: Buffer[] = []; archive.outputStream.on("data", (chunk: Buffer) => chunks.push(chunk)); for (const [name, contents] of Object.entries(files)) archive.addBuffer(Buffer.from(contents), name); archive.end(); await once(archive.outputStream, "end"); return Buffer.concat(chunks); } async function validBundle(): Promise { const workspaceYaml = serializeWorkspaceYaml(workspace); const docs = renderWorkspaceDocs(workspace); const contractEnv = docs.envExample; const readme = docs.markdown; return await zip({ "manifest.json": JSON.stringify({ schema_version: 1, workspace_id: workspace.workspace.id, files: { "workspace.yaml": sha256(workspaceYaml), "contract.env.example": sha256(contractEnv), "README.md": sha256(readme), }, }), "workspace.yaml": workspaceYaml, "contract.env.example": contractEnv, "README.md": readme, }); } function zipWithZipSlipEntry(): Promise { return zip({ "aa/escape.yaml": "bad" }).then((archive) => { const safeName = Buffer.from("aa/escape.yaml"); const unsafeName = Buffer.from("../escape.yaml"); for (let offset = archive.indexOf(safeName); offset !== -1; offset = archive.indexOf(safeName, offset + safeName.length)) { unsafeName.copy(archive, offset); } return archive; }); } async function importBundle(app: ReturnType, archive: Buffer) { const boundary = "----thoth-workspace-test-boundary"; const payload = Buffer.concat([ Buffer.from(`--${boundary}\r\ncontent-disposition: form-data; name="bundle"; filename="workspace.zip"\r\ncontent-type: application/zip\r\n\r\n`), archive, Buffer.from(`\r\n--${boundary}--\r\n`), ]); return await app.inject({ method: "POST", url: "/workspaces/import", headers: { "content-type": `multipart/form-data; boundary=${boundary}` }, payload, }); } 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()).toEqual({ branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: true, lastError: "git_auth_failed", }); expect(pull.statusCode).toBe(200); expect(JSON.stringify([status.json(), pull.json()])).not.toMatch(/token|password|ssh:\/\//i); }); test("lists compatible workspace summaries and reads a validated workspace", async () => { const app = appFor(registryFake()); const list = await app.inject({ method: "GET", url: "/workspaces" }); const detail = await app.inject({ method: "GET", url: "/workspaces/psd-clinical" }); expect(list.statusCode).toBe(200); expect(list.json()).toEqual([expect.objectContaining({ id: "psd-clinical", name: "psd-clinical", file: "psd-clinical.yaml", displayName: "Policlinico San Donato", })]); expect(detail.statusCode).toBe(200); expect(detail.json()).toMatchObject({ workspace, revision }); }); test("validates a canonical workspace and runs the injected installation diagnostic", async () => { const diagnose = vi.fn(async () => ({ activatable: false, diagnostics: [{ level: "error" as const, code: "binding_missing" as const, field: "THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE", message: "Installation binding is missing or invalid." }], })); const app = appFor(registryFake(), diagnose); const validate = await app.inject({ method: "POST", url: "/workspaces/validate", payload: { workspace } }); const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} }); expect(validate.statusCode).toBe(200); expect(validate.json()).toMatchObject({ workspace }); expect(testResult.statusCode).toBe(200); expect(testResult.json()).toMatchObject({ activatable: false, diagnostics: [{ code: "binding_missing" }] }); expect(diagnose).toHaveBeenCalledWith(workspace, expect.any(Object), { writeProbe: false }); }); test("returns a 409 field conflict instead of overwriting a changed workspace", async () => { const conflict = Object.assign( new WorkspaceRegistryError("workspace_conflict", "Workspace has changed"), { fields: ["semantic_index.embedding.model"], expected: { commit: "c".repeat(40), blob: "d".repeat(40) }, actual: { commit: revision.commit, blob: revision.blob }, base: workspace, local: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "local/model" } } }, remote: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "remote/model" } } }, }, ); const registry = registryFake({ publish: vi.fn(async () => { throw conflict; }) }); const app = appFor(registry); const staleUpdate = { action: "update", workspace, baseCommit: "c".repeat(40), baseBlob: "d".repeat(40), }; const res = await app.inject({ method: "POST", url: "/workspaces/publish", payload: staleUpdate }); expect(res.statusCode).toBe(409); expect(res.json()).toMatchObject({ code: "workspace_conflict", fields: ["semantic_index.embedding.model"], expected: { commit: "c".repeat(40), blob: "d".repeat(40) }, actual: { commit: revision.commit, blob: revision.blob }, base: workspace, remote: expect.objectContaining({ semantic_index: expect.objectContaining({ embedding: expect.objectContaining({ model: "remote/model" }), }), }), }); }); test("exports generated public artifacts without secret values", async () => { const app = appFor(registryFake()); const res = await app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" }); expect(res.statusCode).toBe(200); expect(res.headers["content-disposition"]).toMatch(/attachment; filename="psd-clinical\.zip"/); expect(res.headers["content-type"]).toMatch(/application\/zip/); expect(res.rawPayload.toString("utf8")).toContain("contract.env.example"); expect(res.rawPayload.toString("utf8")).not.toContain("secret-value"); }); test("rejects a zip-slip import without publishing or writing a checkout file", async () => { const registry = registryFake(); const app = appFor(registry); const res = await importBundle(app, await zipWithZipSlipEntry()); expect(res.statusCode).toBe(400); expect(res.json()).toMatchObject({ code: "workspace_invalid" }); expect(registry.publish).not.toHaveBeenCalled(); }); test("imports an exact generated bundle only as a browser draft", async () => { const registry = registryFake(); const app = appFor(registry); const res = await importBundle(app, await validBundle()); expect(res.statusCode).toBe(200); expect(res.json()).toMatchObject({ draft: { workspace } }); expect(registry.publish).not.toHaveBeenCalled(); });