fix(preprocess): initialize fresh DWH writers safely
This commit is contained in:
@@ -97,6 +97,61 @@ def test_writer_rejects_unbound_legacy_artifacts_before_building(tmp_path):
|
||||
assert not (tmp_path / ".tht-dwh" / "OWNER.json").exists()
|
||||
|
||||
|
||||
def test_writer_rejects_dangling_legacy_symlinks_before_claim_or_callback(tmp_path):
|
||||
import pytest
|
||||
|
||||
legacy = tmp_path / "artifacts" / "mschema" / "physical.yaml"
|
||||
legacy.parent.mkdir(parents=True)
|
||||
legacy.symlink_to(tmp_path / "missing-catalog")
|
||||
calls = []
|
||||
pipeline = DwhPreprocessPipeline(
|
||||
workspace_id="demo", workspace_root=tmp_path,
|
||||
config_fingerprint=FP, input_fingerprint=FP,
|
||||
introspect=lambda output: calls.append("called"),
|
||||
build_lsh=lambda physical, output: None,
|
||||
current_physical=legacy,
|
||||
)
|
||||
with pytest.raises(Exception, match="legacy artifacts are unbound"):
|
||||
pipeline.run()
|
||||
assert calls == []
|
||||
assert not (tmp_path / ".tht-dwh" / "OWNER.json").exists()
|
||||
|
||||
|
||||
def test_owner_publication_remains_on_locked_root_when_path_is_swapped(
|
||||
monkeypatch, tmp_path,
|
||||
):
|
||||
import os
|
||||
import pytest
|
||||
import tht.jobs.dwh_pipeline as module
|
||||
|
||||
real_replace = module.os.replace
|
||||
moved = tmp_path / "locked-root"
|
||||
replacement = tmp_path / ".tht-dwh"
|
||||
swapped = False
|
||||
|
||||
def swapping_replace(source, destination, *args, **kwargs):
|
||||
nonlocal swapped
|
||||
if destination == "OWNER.json" and kwargs.get("dst_dir_fd") is not None:
|
||||
swapped = True
|
||||
replacement.rename(moved)
|
||||
replacement.mkdir(mode=0o700)
|
||||
return real_replace(source, destination, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(module.os, "replace", swapping_replace)
|
||||
pipeline = DwhPreprocessPipeline(
|
||||
workspace_id="demo", workspace_root=tmp_path,
|
||||
config_fingerprint=FP, input_fingerprint=FP,
|
||||
introspect=lambda output: (_ for _ in ()).throw(AssertionError("callback called")),
|
||||
build_lsh=lambda physical, output: None,
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
pipeline.run()
|
||||
assert swapped
|
||||
assert (moved / "OWNER.json").is_file()
|
||||
assert not (replacement / "OWNER.json").exists()
|
||||
assert os.stat(moved / "generation.lock").st_ino != os.stat(replacement).st_ino
|
||||
|
||||
|
||||
def test_owner_requires_exact_read_only_owner_mode_and_active_requires_binding(tmp_path):
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -34,17 +34,62 @@ def _write_config(tmp_path):
|
||||
return cfg
|
||||
|
||||
|
||||
def test_introspect_cache_hit_skips_dwh(tmp_path):
|
||||
# Le credenziali sono fasulle: se la guardia non scattasse PRIMA del branch
|
||||
# transport, il comando tenterebbe la connessione e fallirebbe.
|
||||
def test_introspect_rejects_unbound_legacy_cache(tmp_path):
|
||||
catalog = _write_catalog(tmp_path)
|
||||
before = catalog.read_bytes()
|
||||
cfg = _write_config(tmp_path)
|
||||
res = CliRunner().invoke(app, ["schema", "introspect", "-c", str(cfg)])
|
||||
assert res.exit_code == 1
|
||||
assert "legacy artifacts are unbound" in res.output
|
||||
assert catalog.exists()
|
||||
|
||||
|
||||
def test_introspect_fresh_root_initializes_through_writer_job(tmp_path, monkeypatch):
|
||||
import tht.cli.schema_cmd as module
|
||||
|
||||
cfg = _write_config(tmp_path)
|
||||
physical = PhysicalSchema(
|
||||
database="d", schema="s", introspected_at=datetime(2026, 1, 1),
|
||||
tables={"dim_patient": TablePhysical(columns={"id": ColumnPhysical(type="bigint")})},
|
||||
)
|
||||
|
||||
def refresh(_cfg, *, output_path=None, **_kwargs):
|
||||
physical.to_yaml(output_path)
|
||||
return physical
|
||||
|
||||
monkeypatch.setattr(module, "refresh_catalog", refresh)
|
||||
res = CliRunner().invoke(app, ["schema", "introspect", "-c", str(cfg)])
|
||||
assert res.exit_code == 0, res.output
|
||||
assert "OK (cache)" in res.output
|
||||
assert (tmp_path / ".tht-dwh" / "OWNER.json").is_file()
|
||||
assert "1 tabelle" in res.output
|
||||
assert catalog.read_bytes() == before
|
||||
|
||||
|
||||
def test_lsh_build_fresh_root_initializes_introspection_and_lsh(tmp_path, monkeypatch):
|
||||
import tht.cli.lsh_cmd as lsh_module
|
||||
import tht.cli.schema_cmd as schema_module
|
||||
import tht.lshindex as lshindex_module
|
||||
|
||||
cfg = _write_config(tmp_path)
|
||||
physical = PhysicalSchema(
|
||||
database="d", schema="s", introspected_at=datetime(2026, 1, 1),
|
||||
tables={"dim_patient": TablePhysical(columns={"id": ColumnPhysical(type="bigint")})},
|
||||
)
|
||||
|
||||
def refresh(_cfg, *, output_path=None, **_kwargs):
|
||||
physical.to_yaml(output_path)
|
||||
return physical
|
||||
|
||||
def build(_cfg, *, physical_file, output_dir, **_kwargs):
|
||||
assert physical_file.is_file()
|
||||
for name in ("s_lsh.pkl", "s_minhashes.pkl", "s_meta.json"):
|
||||
(output_dir / name).write_text("index")
|
||||
return {}, [], [], {}
|
||||
|
||||
monkeypatch.setattr(schema_module, "refresh_catalog", refresh)
|
||||
monkeypatch.setattr(lsh_module, "build_lsh_artifacts", build)
|
||||
monkeypatch.setattr(lshindex_module, "load_index", lambda *_args, **_kwargs: (None, {}, None))
|
||||
res = CliRunner().invoke(app, ["lsh", "build", "-c", str(cfg)])
|
||||
assert res.exit_code == 0, res.output
|
||||
assert (tmp_path / ".tht-dwh" / "OWNER.json").is_file()
|
||||
|
||||
|
||||
def test_introspect_refresh_bypasses_cache(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user