fix: harden task2 preprocessing and vector guards

This commit is contained in:
2026-08-11 06:47:07 +02:00
parent f62b4dbc4c
commit f5e76fff53
6 changed files with 185 additions and 9 deletions
+65
View File
@@ -2,9 +2,13 @@ import json
from pathlib import Path
from types import SimpleNamespace
import pytest
from typer.testing import CliRunner
from tht.cli import app
from tht.ports.evidence import EvidenceSourceError, EvidenceSourceErrorCategory
from tht.ports.vector import VectorStoreError
from tht.vectorstore.embeddings import EmbeddingsError
def test_preprocess_evidence_json_is_pristine(monkeypatch, tmp_path):
@@ -191,3 +195,64 @@ def test_preprocess_gc_uses_runtime_identity_for_dev_fd_config(monkeypatch, tmp_
command.gc_from_config(Path("/dev/fd/3"), dry_run=True)
assert captured["dry_run"] is True
assert captured["workspace_id"] == "runtime-workspace"
@pytest.mark.parametrize(
"error",
[
pytest.param(
EvidenceSourceError("secret source", category=EvidenceSourceErrorCategory.PERMANENT),
id="evidence-source",
),
pytest.param(VectorStoreError("secret vector"), id="vector-store"),
pytest.param(EmbeddingsError("secret embeddings"), id="embeddings"),
],
)
def test_preprocess_evidence_json_catches_domain_failures_without_stderr(monkeypatch, tmp_path, error):
import tht.cli.preprocess_cmd as command
monkeypatch.setattr(command, "run_from_config", lambda *a, **k: (_ for _ in ()).throw(error))
response = CliRunner().invoke(
app, ["preprocess", "evidence", "--json", "-c", str(tmp_path / "workspace.yaml")]
)
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "error": "preprocessing failed"}
assert response.stderr == ""
@pytest.mark.parametrize(
"error",
[
pytest.param(
EvidenceSourceError("secret source", category=EvidenceSourceErrorCategory.PERMANENT),
id="evidence-source",
),
pytest.param(VectorStoreError("secret vector"), id="vector-store"),
pytest.param(EmbeddingsError("secret embeddings"), id="embeddings"),
],
)
def test_preprocess_evidence_gc_json_catches_domain_failures_without_stderr(monkeypatch, tmp_path, error):
import tht.cli.preprocess_cmd as command
monkeypatch.setattr(command, "gc_from_config", lambda *a, **k: (_ for _ in ()).throw(error))
response = CliRunner().invoke(
app, ["preprocess", "evidence", "gc", "--json", "-c", str(tmp_path / "workspace.yaml")]
)
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "error": "evidence cleanup failed"}
assert response.stderr == ""
def test_preprocess_evidence_json_unexpected_failure_has_safe_boundary(monkeypatch, tmp_path):
import tht.cli.preprocess_cmd as command
monkeypatch.setattr(command, "run_from_config", lambda *a, **k: (_ for _ in ()).throw(Exception("secret unexpected")))
response = CliRunner().invoke(
app, ["preprocess", "evidence", "--json", "-c", str(tmp_path / "workspace.yaml")]
)
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "error": "preprocessing failed"}
assert response.stderr == ""
+38 -1
View File
@@ -225,7 +225,7 @@ def test_vector_index_schema_json_failure_is_safe(monkeypatch, tmp_path):
_write_schema_artifacts(tmp_path)
monkeypatch.setattr(
"tht.adapters.factory.build_vector_store",
lambda cfg, require_write: (_ for _ in ()).throw(RuntimeError("secret qdrant endpoint")),
lambda cfg, require_write: (_ for _ in ()).throw(Exception("secret qdrant endpoint")),
)
response = CliRunner().invoke(app, ["vector", "index-schema", "--json", "-c", str(cfg)])
assert response.exit_code != 0
@@ -233,6 +233,7 @@ def test_vector_index_schema_json_failure_is_safe(monkeypatch, tmp_path):
payload = json.loads(response.stdout)
assert payload == {"status": "failed", "code": "schema_index_failed"}
assert "secret qdrant" not in response.stdout
assert response.stderr == ""
@@ -294,3 +295,39 @@ def test_vector_index_schema_json_legacy_config_has_no_stderr_on_failure(tmp_pat
assert json.loads(response.stdout) == {
"status": "failed", "code": "physical_schema_missing"
}
def test_vector_index_schema_guards_before_artifact_access(tmp_path, monkeypatch):
import tht.cli.vector_cmd as command
cfg = _legacy_qdrant_runtime_config(tmp_path)
text = cfg.read_text()
text = text.replace("vectors:\n type: qdrant\n base_url: http://qdrant:6333\n collection: psd-clinical\n", "")
text = text.replace("embeddings:\n provider: ollama_internal\n base_url: http://embedding:11434\n model: qwen3-embedding:0.6b\n dim: 1024\n", "")
cfg.write_text(text)
monkeypatch.setattr(command, "_load_schema_artifacts", lambda cfg: (_ for _ in ()).throw(AssertionError("artifact access")))
response = CliRunner().invoke(app, ["vector", "index-schema", "--json", "-c", str(cfg)])
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "code": "vector_configuration_missing"}
assert response.stderr == ""
def test_vector_index_schema_core_reuses_injected_artifacts_without_path_resolution(tmp_path, monkeypatch):
import tht.cli.vector_cmd as command
from tht.config import load_config
from tht.mschema.models import Annotations, PhysicalSchema
cfg_path = _qdrant_runtime_config(tmp_path)
_write_schema_artifacts(tmp_path)
physical = PhysicalSchema.from_yaml(tmp_path / "artifacts" / "mschema" / "physical.yaml")
annotations = Annotations.from_yaml(tmp_path / "artifacts" / "mschema" / "annotations.yaml")
store = _FakeVectorStore()
monkeypatch.setattr(command, "physical_path", lambda cfg: (_ for _ in ()).throw(AssertionError("physical path")))
monkeypatch.setattr(command, "annotations_path", lambda cfg: (_ for _ in ()).throw(AssertionError("annotations path")))
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: store)
monkeypatch.setattr(command, "make_embedder", lambda _: _FakeEmbedder())
payload = command.index_schema_data(load_config(cfg_path), physical=physical, annotations=annotations)
assert payload["status"] == "succeeded"
@@ -228,6 +228,24 @@ def test_suggest_fks_reports_staged_file_without_mined_joins(tmp_path):
assert "Minati 0 equi-join da 1 file SQL" in response.output
def test_suggest_fks_human_write_reads_one_annotation_snapshot(tmp_path, monkeypatch):
cfg = _write_workspace(tmp_path)
ann_path = tmp_path / "artifacts" / "mschema" / "annotations.yaml"
Annotations().to_yaml(ann_path)
reads = []
original = Annotations.from_yaml
def tracked(path):
reads.append(path)
return original(path)
monkeypatch.setattr(Annotations, "from_yaml", tracked)
response = CliRunner().invoke(app, ["schema", "suggest-fks", "-c", str(cfg), "--write"])
assert response.exit_code == 0, response.output
assert reads == [ann_path]
def test_suggest_fks_write_merges_and_is_idempotent(tmp_path):
cfg = _write_workspace(tmp_path)
ann_path = tmp_path / "artifacts" / "mschema" / "annotations.yaml"
@@ -601,3 +619,27 @@ def test_fresh_process_human_warning_cardinality_is_one_across_failure_and_write
check=True, capture_output=True, text=True,
)
assert json.loads(response.stdout)["warnings"] == 0
def test_schema_check_json_unexpected_failure_has_no_stderr_secret(monkeypatch, tmp_path):
import tht.cli.schema_cmd as command
cfg = _write_workspace(tmp_path)
monkeypatch.setattr(command, "_physical_or_error", lambda cfg: (_ for _ in ()).throw(Exception("secret schema adapter")))
response = CliRunner().invoke(app, ["schema", "check", "--json", "-c", str(cfg)])
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "code": "schema_check_failed"}
assert response.stderr == ""
def test_schema_suggest_json_unexpected_failure_has_no_stderr_secret(monkeypatch, tmp_path):
import tht.cli.schema_cmd as command
cfg = _write_workspace(tmp_path)
monkeypatch.setattr(command, "_physical_or_error", lambda cfg: (_ for _ in ()).throw(Exception("secret schema adapter")))
response = CliRunner().invoke(app, ["schema", "suggest-fks", "--json", "-c", str(cfg)])
assert response.exit_code == 1
assert json.loads(response.stdout) == {"status": "failed", "code": "schema_suggestion_failed"}
assert response.stderr == ""