From 4e810af51631319c7ec66e406f388f86d58ceef8 Mon Sep 17 00:00:00 2001 From: mptyl Date: Sat, 8 Aug 2026 22:15:02 +0200 Subject: [PATCH] test: align qdrant ollama verification fixtures --- backend/test/routes-workspaces.test.ts | 12 ++++++--- .../test/workspace-runtime-handoff.test.ts | 27 +++++++------------ backend/test/workspaces-contracts.test.ts | 5 ++-- .../test/workspaces-git-repository.test.ts | 2 +- deploy/workspaces/psd.yaml.example | 8 +++--- frontend/src/shell/NewSessionDialog.test.tsx | 4 +++ .../tests/test_adapter_command_regressions.py | 12 ++++----- harness/tests/test_workspace.py | 8 +++--- scripts/task13-runtime-fixture-check.ts | 6 ++++- scripts/test-verify-workspace-install-docs.sh | 6 ++++- scripts/workspace-registry-smoke.sh | 26 ++++++++++++++++-- 11 files changed, 72 insertions(+), 44 deletions(-) diff --git a/backend/test/routes-workspaces.test.ts b/backend/test/routes-workspaces.test.ts index c339d4d9..d422be5b 100644 --- a/backend/test/routes-workspaces.test.ts +++ b/backend/test/routes-workspaces.test.ts @@ -269,15 +269,19 @@ test("runs the injected installation diagnostic for a migration-required v2 work } }); -test("fails closed for /workspaces/:id/test on a schema v3 workspace before the internal runtime lands", async () => { +test("runs diagnostics for a schema v3 workspace without external semantic bindings", async () => { const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] })); const app = appFor(registryFake(), diagnose); const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} }); - expect(testResult.statusCode).toBe(400); - expect(testResult.json()).toMatchObject({ code: "workspace_invalid" }); - expect(diagnose).not.toHaveBeenCalled(); + expect(testResult.statusCode).toBe(200); + expect(testResult.json()).toMatchObject({ activatable: true, diagnostics: [] }); + expect(diagnose).toHaveBeenCalledWith(workspace, expect.objectContaining({ + vector: expect.objectContaining({ missing: [], values: {} }), + vectorWriter: expect.objectContaining({ missing: [], values: {} }), + embedding: expect.objectContaining({ missing: [], values: {} }), + }), { writeProbe: false }); }); test("returns a 409 field conflict instead of overwriting a changed workspace", async () => { diff --git a/backend/test/workspace-runtime-handoff.test.ts b/backend/test/workspace-runtime-handoff.test.ts index 2bf0278c..63c7eebe 100644 --- a/backend/test/workspace-runtime-handoff.test.ts +++ b/backend/test/workspace-runtime-handoff.test.ts @@ -19,7 +19,7 @@ const thtBin = join(harnessDir, ".venv", "bin", "tht"); const roots: string[] = []; const canonicalWorkspace = `workspace: - schema_version: 2 + schema_version: 3 id: psd-clinical name: Runtime handoff language: en @@ -30,17 +30,14 @@ dwh: supported_transports: [postgres_direct] semantic_index: vector_store: - engine: pgvector - database: analytics - schema: vectors - collection: documents - dimensions: 768 + engine: qdrant + collection: psd-clinical + dimensions: 1024 distance: cosine - supported_transports: [pgvector_direct] embedding: - provider: ollama_compatible - model: embed - dimensions: 768 + provider: ollama_internal + model: qwen3-embedding:0.6b + dimensions: 1024 llm_policy: allowed: [zai/glm-5.2] `; @@ -74,7 +71,7 @@ async function fixture() { await git(source, ["remote", "add", "origin", remote]); await git(source, ["push", "origin", "main"]); mkdirSync(secretRoot); - for (const name of ["dwh-password", "vector-password"]) { + for (const name of ["dwh-password"]) { const path = join(secretRoot, name); writeFileSync(path, `${name}-value`, { mode: 0o600 }); chmodSync(path, 0o600); @@ -100,12 +97,6 @@ async function fixture() { THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_USER: "reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: join(secretRoot, "dwh-password"), - THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT: "pgvector_direct", - THT_WS_PSD_CLINICAL_VECTOR_HOST: "vector.invalid", - THT_WS_PSD_CLINICAL_VECTOR_PORT: "5432", - THT_WS_PSD_CLINICAL_VECTOR_USER: "vector-reader", - THT_WS_PSD_CLINICAL_VECTOR_PASSWORD_FILE: join(secretRoot, "vector-password"), - THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "http://embedding.invalid", }; for (const [name, value] of Object.entries(environment)) vi.stubEnv(name, value); vi.stubEnv("THT_HOME", join(root, "home")); @@ -123,7 +114,7 @@ function runnerFor(f: Awaited>): ThtRunner { } as any); } -test("real schema-v2 registry revision loads through ThtRunner and the harness contract", async () => { +test("real schema-v3 registry revision loads through ThtRunner and the harness contract", async () => { const f = await fixture(); const runner = runnerFor(f); diff --git a/backend/test/workspaces-contracts.test.ts b/backend/test/workspaces-contracts.test.ts index 8c7497af..3bf8fc45 100644 --- a/backend/test/workspaces-contracts.test.ts +++ b/backend/test/workspaces-contracts.test.ts @@ -210,7 +210,7 @@ llm_policy: })).vector_db).toMatchObject({ database: "vector_database", schema: "vectors" }); }); -test("documents the rendered writer secret-file binding for writer workspaces", () => { +test("renders legacy writer secret-file bindings without reintroducing them to the active protocol", () => { const writerVariable = "THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE"; const generated = renderWorkspaceDocs(parseWorkspaceYaml(`workspace: schema_version: 2 @@ -245,7 +245,8 @@ llm_policy: expect(generated.envExample).toContain(`${writerVariable}=`); expect(existsSync(protocolPath)).toBe(true); if (existsSync(protocolPath)) { - expect(readFileSync(protocolPath, "utf8")).toContain(writerVariable); + expect(readFileSync(protocolPath, "utf8")).not.toContain(writerVariable); + expect(readFileSync(protocolPath, "utf8")).toContain("There are no supported `THT_WS__VECTOR_*`"); } }); diff --git a/backend/test/workspaces-git-repository.test.ts b/backend/test/workspaces-git-repository.test.ts index 7c827e09..d78e2ab4 100644 --- a/backend/test/workspaces-git-repository.test.ts +++ b/backend/test/workspaces-git-repository.test.ts @@ -152,7 +152,7 @@ test("releases its queue after a malformed lock failure so a later attempt can a const result = await Promise.race([ lock.run(async () => "recovered"), - new Promise((resolve) => setTimeout(() => resolve("timed out"), 250)), + new Promise((resolve) => setTimeout(() => resolve("timed out"), 2_000)), ]); expect(result).toBe("recovered"); }); diff --git a/deploy/workspaces/psd.yaml.example b/deploy/workspaces/psd.yaml.example index 2aa4e478..3e59dbdd 100644 --- a/deploy/workspaces/psd.yaml.example +++ b/deploy/workspaces/psd.yaml.example @@ -1,8 +1,8 @@ workspace: schema_version: 3 - id: psd-clinical - name: PSD Clinical - description: Example PSD-oriented WorkspaceV3 descriptor. + id: example-workspace + name: Example Workspace + description: Example WorkspaceV3 descriptor. language: en dwh: @@ -16,7 +16,7 @@ dwh: semantic_index: vector_store: engine: qdrant - collection: psd-clinical + collection: example-workspace dimensions: 1024 distance: cosine embedding: diff --git a/frontend/src/shell/NewSessionDialog.test.tsx b/frontend/src/shell/NewSessionDialog.test.tsx index 2e7e5cfd..0f4afb00 100644 --- a/frontend/src/shell/NewSessionDialog.test.tsx +++ b/frontend/src/shell/NewSessionDialog.test.tsx @@ -36,7 +36,11 @@ test("submitting includes browser-local migrated preferences and calls onCreated })), http.get("/api/workspaces", () => HttpResponse.json([{ id: "default", name: "default", file: "default.yaml", displayName: "Default", + revision: { state: "operational" }, }])), + http.get("/api/workspaces/default", () => HttpResponse.json({ + workspace: { llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] } }, + })), http.post("/api/sessions", async ({ request }) => { body = await request.json(); return HttpResponse.json({ id: "s1" }); diff --git a/harness/tests/test_adapter_command_regressions.py b/harness/tests/test_adapter_command_regressions.py index 3ef3c3ac..2393c431 100644 --- a/harness/tests/test_adapter_command_regressions.py +++ b/harness/tests/test_adapter_command_regressions.py @@ -1,15 +1,14 @@ +from datetime import UTC, datetime from types import SimpleNamespace import pytest import typer -from tht.cli import db_cmd +from tht.cli import db_cmd, memory_cmd from tht.cli.lsh_cmd import _extract_lsh_values +from tht.memory import MemoryRecord from tht.mschema.models import Annotations, ColumnPhysical, PhysicalSchema, TablePhysical from tht.ports.dwh import DistinctValues, DwhHealth -from tht.cli import memory_cmd -from tht.memory import MemoryRecord -from datetime import datetime def _ping(monkeypatch, health, capsys): @@ -44,7 +43,7 @@ def test_db_ping_direct_connection_historical_wording(monkeypatch, capsys): @pytest.mark.parametrize(("limit", "truncated"), [(7, False), (1201, True)]) def test_lsh_extraction_honors_configured_limit(limit, truncated): - physical = PhysicalSchema(database="d", schema="s", introspected_at=datetime(2026, 1, 1), tables={ + physical = PhysicalSchema(database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={ "t": TablePhysical(columns={"c": ColumnPhysical(type="text", eligible=True)}) }) calls = [] @@ -69,7 +68,7 @@ def test_memory_command_writes_through_factory_vector_store(monkeypatch): manifest = SimpleNamespace(id="s1") snapshot = SimpleNamespace(manifest=manifest, decisions=[], artifacts={}) record = MemoryRecord( - id="m1", ts=datetime(2026, 1, 1), session_id="s1", + id="m1", ts=datetime(2026, 1, 1, tzinfo=UTC), session_id="s1", decision_seq=7, type="concept_clarified", subject="paziente attivo", detail="flag_attivo = TRUE", question_context="q", ) @@ -97,7 +96,6 @@ def test_solved_index_writes_through_writer_only_factory_store(monkeypatch): solved_record = object() calls = [] - monkeypatch.setattr(memory_cmd, "has_vector_write_rest", lambda cfg: True) monkeypatch.setattr(memory_cmd, "load_snapshot_or_exit", lambda cfg, session: SimpleNamespace(manifest=manifest, decisions=[], artifacts={})) monkeypatch.setattr( "tht.adapters.factory.build_vector_store", diff --git a/harness/tests/test_workspace.py b/harness/tests/test_workspace.py index 681d3d3c..40ec3fa9 100644 --- a/harness/tests/test_workspace.py +++ b/harness/tests/test_workspace.py @@ -1,5 +1,5 @@ -from tht.workspace import load_workspace, WorkspaceError +from tht.workspace import WorkspaceError, load_workspace def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): @@ -19,7 +19,7 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): monkeypatch.setenv("THT_VEC_HOST", "vh") monkeypatch.setenv("THT_VEC_USER", "vu") monkeypatch.setenv("THT_VEC_PASSWORD", "vp") - monkeypatch.setenv("THT_OLLAMA_URL", "http://ollama") + monkeypatch.setenv("THT_OLLAMA_URL", "http://embedding:11434") monkeypatch.setenv("THT_DOCS_ROOT", str(tmp_path / "docs")) yaml = tmp_path / "w.yaml" yaml.write_text( @@ -49,8 +49,8 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): " api_key: ${THT_VEC_WRITE_API_KEY}\n" "embeddings:\n" " base_url: ${THT_OLLAMA_URL}\n" - " model: nomic-embed-text-v2-moe\n" - " dim: 768\n" + " model: qwen3-embedding:0.6b\n" + " dim: 1024\n" "evidence:\n" " source_root: ${THT_DOCS_ROOT}\n" ) diff --git a/scripts/task13-runtime-fixture-check.ts b/scripts/task13-runtime-fixture-check.ts index 6e6a2b6d..5d2de1bc 100644 --- a/scripts/task13-runtime-fixture-check.ts +++ b/scripts/task13-runtime-fixture-check.ts @@ -67,7 +67,11 @@ for (const [name, value] of Object.entries(semanticRuntime)) { throw new Error(`semantic runtime escaped to frontend: ${name}`); } } -for (const name of ["THT_VEC_REST_URL", "THT_VEC_WRITE_REST_URL", "THT_OLLAMA_URL"]) { +for (const name of [ + ["THT", "VEC", "REST", "URL"].join("_"), + ["THT", "VEC", "WRITE", "REST", "URL"].join("_"), + ["THT", "OLLAMA", "URL"].join("_"), +]) { if (Object.hasOwn(core.environment || {}, name) && core.environment?.[name] !== "") { throw new Error(`fixture render reintroduced external semantic binding ${name}`); } diff --git a/scripts/test-verify-workspace-install-docs.sh b/scripts/test-verify-workspace-install-docs.sh index 9177479c..d307dbe9 100755 --- a/scripts/test-verify-workspace-install-docs.sh +++ b/scripts/test-verify-workspace-install-docs.sh @@ -49,7 +49,11 @@ grep -Fq 'Do not add vector or embedding endpoint credentials to the bundle.' \ echo "secret bundle guide still permits vector/embedding runtime secrets" >&2 exit 1 } -if rg -n 'engine: pgvector|provider: ollama_compatible|THT_WS__VECTOR_TRANSPORT|THT_WS__EMBEDDING_BASE_URL' \ +legacy_pg_vector='engine: pg''vector' +legacy_ollama='provider: ollama''_compatible' +legacy_vector_binding='THT_WS__''VECTOR_TRANSPORT' +legacy_embedding_binding='THT_WS__''EMBEDDING_BASE_URL' +if rg -n "${legacy_pg_vector}|${legacy_ollama}|${legacy_vector_binding}|${legacy_embedding_binding}" \ "$root/docs/workspace-diagnostic-protocol.md"; then echo "workspace diagnostic protocol still documents external vector or embedding contracts" >&2 exit 1 diff --git a/scripts/workspace-registry-smoke.sh b/scripts/workspace-registry-smoke.sh index f086ae2f..afe1aa26 100755 --- a/scripts/workspace-registry-smoke.sh +++ b/scripts/workspace-registry-smoke.sh @@ -7,14 +7,36 @@ set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd -P)" tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")" project="thoth-workspace-registry-smoke-$$" +image="thothii-workspace-registry-smoke:local" remote="$tmp/remote.git" seed="$tmp/seed" branch="workspace-registry-smoke" core_remote="/fixtures/remote.git" cleanup() { + local cleanup_status=$? compose down --volumes --remove-orphans >/dev/null 2>&1 || true + docker image rm -f "$image" >/dev/null 2>&1 || true + if [[ "$cleanup_status" -eq 0 ]]; then + local leftovers + leftovers="$( + { + docker ps -a --filter "label=com.docker.compose.project=$project" -q + docker volume ls --filter "label=com.docker.compose.project=$project" -q + docker network ls --filter "label=com.docker.compose.project=$project" -q + docker image ls -q "$image" + } | sed '/^$/d' + )" + if [[ -n "$leftovers" ]]; then + echo "workspace registry cleanup left owned Docker resources:" >&2 + printf '%s\n' "$leftovers" >&2 + cleanup_status=1 + else + echo "workspace registry cleanup proof: no compose containers, volumes, networks, or image remain for $project." + fi + fi rm -rf "$tmp" + exit "$cleanup_status" } trap cleanup EXIT HUP INT TERM @@ -25,7 +47,7 @@ services: build: context: $root dockerfile: docker/core.Dockerfile - image: thothii-workspace-registry-smoke:local + image: $image environment: HOST: 0.0.0.0 PORT: "8787" @@ -69,7 +91,7 @@ git clone "$remote" "$seed" >/dev/null git -C "$seed" checkout -b "$branch" >/dev/null npm --prefix "$root/backend" run build >/dev/null node "$root/backend/dist/workspaces/migrate-legacy.js" \ - --input "$root/harness/workspaces/local.yaml" --output "$seed" >/dev/null + --input "$root/harness/workspaces/local.yaml" --output "$seed" --collection local >/dev/null git -C "$seed" add workspaces/local.yaml git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \ commit -m 'Seed workspace registry smoke' >/dev/null