import json from datetime import UTC, datetime from types import SimpleNamespace import pytest from typer.testing import CliRunner from tht.cli import app from tht.config import load_config from tht.mschema.context import SchemaContextError, load_schema_context from tht.mschema.models import ( Annotations, ColumnPhysical, ForeignKey, PhysicalSchema, TableAnnotation, TablePhysical, ) from tht.mschema.render import to_mschema_text REVISION = "a" * 40 RUNNER = CliRunner() def _workspace(tmp_path, snapshot: dict) -> object: artifacts = tmp_path / "artifacts" relationships = tmp_path / "effective-relationships.json" relationships.write_text(json.dumps(snapshot)) physical = PhysicalSchema( database="warehouse", schema="analytics", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={ "users": TablePhysical( columns={"id": ColumnPhysical(type="bigint", pk=True)}, ), "orders": TablePhysical( columns={ "physical_user_id": ColumnPhysical(type="bigint"), "annotated_user_id": ColumnPhysical(type="bigint"), "generated_user_id": ColumnPhysical(type="bigint"), }, foreign_keys=[ ForeignKey( columns=["physical_user_id"], ref_table="users", ref_columns=["id"], ) ], ), }, ) physical.to_yaml(artifacts / "mschema" / "physical.yaml") Annotations( tables={ "orders": TableAnnotation( description="Customer orders", foreign_keys=[ ForeignKey( columns=["annotated_user_id"], ref_table="users", ref_columns=["id"], ) ], ) } ).to_yaml(artifacts / "mschema" / "annotations.yaml") config = tmp_path / "runtime.yaml" config.write_text( f""" runtime_identity: workspace_id: demo workspace_revision: {REVISION} source_identity: workspace://demo database: {{database: warehouse, schema: analytics, user: reader, password: secret, transport: direct}} vector_db: {{database: vectors, schema: public, user: reader, password: secret}} embeddings: {{base_url: 'http://localhost:11434', model: 'qwen3-embedding:0.6b', dim: 1024}} paths: artifacts: {artifacts} indexes: {tmp_path / 'indexes'} sessions: {tmp_path / 'sessions'} effective_relationships: {relationships} """ ) return load_config(config) def test_effective_snapshot_is_the_exclusive_relationship_source(tmp_path): cfg = _workspace( tmp_path, { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "orders", "sourceColumns": ["generated_user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "generated", } ], }, ) context = load_schema_context(cfg) rendered = to_mschema_text( context.physical, context.annotations, effective_relationships=context.effective_relationships, ) assert "-- Customer orders" in rendered assert "orders.generated_user_id=users.id" in rendered assert "orders.physical_user_id=users.id" not in rendered assert "orders.annotated_user_id=users.id" not in rendered def test_declared_effective_snapshot_must_exist(tmp_path): cfg = _workspace( tmp_path, {"schemaVersion": 1, "workspaceId": "demo", "relationships": []}, ) cfg.paths.effective_relationships.unlink() with pytest.raises(SchemaContextError, match="snapshot is missing"): load_schema_context(cfg) def test_data_root_override_preserves_the_runtime_relationship_snapshot_path( tmp_path, monkeypatch ): monkeypatch.setenv("THT_DATA_ROOT", str(tmp_path / "mounted-data")) monkeypatch.delenv("THT_HOME", raising=False) cfg = _workspace( tmp_path, {"schemaVersion": 1, "workspaceId": "demo", "relationships": []}, ) assert cfg.paths.effective_relationships == tmp_path / "effective-relationships.json" @pytest.mark.parametrize( ("snapshot", "message"), [ ( {"schemaVersion": 1, "workspaceId": "another", "relationships": []}, "workspace does not match", ), ( { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "missing_orders", "sourceColumns": ["user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "generated", } ], }, "endpoint table is absent", ), ( { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "orders", "sourceColumns": ["missing_user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "manual", } ], }, "endpoint column is absent", ), ( { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "orders", "sourceColumns": ["generated_user_id", "annotated_user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "physical", } ], }, "snapshot is invalid", ), ], ) def test_effective_snapshot_rejects_wrong_workspace_and_orphan_endpoints( tmp_path, snapshot, message ): cfg = _workspace(tmp_path, snapshot) with pytest.raises(SchemaContextError, match=message): load_schema_context(cfg) def test_legacy_relationship_merge_remains_available_and_deduplicated(tmp_path): cfg = _workspace( tmp_path, {"schemaVersion": 1, "workspaceId": "demo", "relationships": []}, ) cfg.paths.effective_relationships = None annotations = Annotations.from_yaml( cfg.paths.artifacts / "mschema" / "annotations.yaml" ) annotations.tables["orders"].foreign_keys.append( ForeignKey( columns=["annotated_user_id"], ref_table="users", ref_columns=["id"], ) ) annotations.to_yaml(cfg.paths.artifacts / "mschema" / "annotations.yaml") context = load_schema_context(cfg) rendered = to_mschema_text(context.physical, context.annotations) assert context.effective_relationships is None assert rendered.count("orders.physical_user_id=users.id") == 1 assert rendered.count("orders.annotated_user_id=users.id") == 1 def test_schema_render_uses_the_effective_snapshot(tmp_path): _workspace( tmp_path, { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "orders", "sourceColumns": ["generated_user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "manual", } ], }, ) result = RUNNER.invoke( app, ["schema", "render", "--format", "mschema-text", "-c", str(tmp_path / "runtime.yaml")], ) assert result.exit_code == 0, result.output assert "orders.generated_user_id=users.id" in result.stdout assert "orders.physical_user_id=users.id" not in result.stdout assert "orders.annotated_user_id=users.id" not in result.stdout def test_schema_search_uses_the_effective_snapshot(tmp_path, monkeypatch): cfg = _workspace( tmp_path, { "schemaVersion": 1, "workspaceId": "demo", "relationships": [ { "sourceTable": "orders", "sourceColumns": ["generated_user_id"], "targetTable": "users", "targetColumns": ["id"], "origin": "generated", } ], }, ) physical = cfg.paths.artifacts / "mschema" / "physical.yaml" class Searcher: def search(self, _vector, **_kwargs): return [ SimpleNamespace( id="orders", kind="schema_table", ref="orders", title="orders", similarity=1.0, content="orders", metadata={}, ) ] class Embedder: def embed_query(self, _text): return [1.0, 0.0, 0.0] import tht.cli.search_cmd as search_module import tht.cli.vector_cmd as vector_module import tht.evidence as evidence_module monkeypatch.setattr( search_module, "_leased_dwh_snapshot", lambda _cfg, _ctx: SimpleNamespace(physical=physical, lsh_dir=tmp_path / "no-lsh"), ) monkeypatch.setattr(vector_module, "require_vector_cfg", lambda _cfg: None) monkeypatch.setattr(vector_module, "open_searcher", lambda _cfg: Searcher()) monkeypatch.setattr(vector_module, "make_embedder", lambda _cfg: Embedder()) monkeypatch.setattr(evidence_module, "validate_corpus_workspace", lambda *_args: None) monkeypatch.setattr(evidence_module, "active_searcher", lambda _cfg, searcher, **_kwargs: searcher) result = RUNNER.invoke( app, [ "search", "find", "orders", "--kind", "schema", "--json", "-c", str(tmp_path / "runtime.yaml"), ], ) assert result.exit_code == 0, result.output payload = json.loads(result.stdout) assert "orders.generated_user_id=users.id" in payload["mschema"] assert "orders.physical_user_id=users.id" not in payload["mschema"] assert "orders.annotated_user_id=users.id" not in payload["mschema"] def test_schema_search_fails_closed_before_returning_no_candidates(tmp_path, monkeypatch): cfg = _workspace( tmp_path, {"schemaVersion": 1, "workspaceId": "demo", "relationships": []}, ) cfg.paths.effective_relationships.unlink() physical = cfg.paths.artifacts / "mschema" / "physical.yaml" import tht.cli.search_cmd as search_module import tht.cli.vector_cmd as vector_module import tht.evidence as evidence_module import tht.search as search_core monkeypatch.setattr( search_module, "_leased_dwh_snapshot", lambda _cfg, _ctx: SimpleNamespace(physical=physical, lsh_dir=tmp_path / "no-lsh"), ) monkeypatch.setattr(vector_module, "require_vector_cfg", lambda _cfg: None) monkeypatch.setattr(vector_module, "open_searcher", lambda _cfg: object()) monkeypatch.setattr(vector_module, "make_embedder", lambda _cfg: object()) monkeypatch.setattr(evidence_module, "validate_corpus_workspace", lambda *_args: None) monkeypatch.setattr(evidence_module, "active_searcher", lambda _cfg, searcher, **_kwargs: searcher) monkeypatch.setattr(search_core, "combined_search", lambda **_kwargs: []) result = RUNNER.invoke( app, [ "search", "find", "orders", "--kind", "schema", "--json", "-c", str(tmp_path / "runtime.yaml"), ], ) assert result.exit_code == 1 assert "effective relationship snapshot is missing" in result.stderr def test_suggest_fks_cannot_write_when_relationships_are_catalog_managed(tmp_path): _workspace( tmp_path, {"schemaVersion": 1, "workspaceId": "demo", "relationships": []}, ) annotations = tmp_path / "artifacts" / "mschema" / "annotations.yaml" before = annotations.read_text() result = RUNNER.invoke( app, [ "schema", "suggest-fks", "--write", "-c", str(tmp_path / "runtime.yaml"), ], ) assert result.exit_code == 1 assert "managed by the catalog" in result.stderr assert annotations.read_text() == before