Files
stack/tests/aco/test_load_modules.py
kert e07c8e76e9 add P8 quality: 99% coverage, health checks, snapshots, CI pipeline
- 76 new tests covering CLI commands, API routes, auth, runner,
  load modules — coverage 99% (169 remaining lines are infrastructure)
- Enhanced /health endpoint with DuckDB, bib, pipeline service checks
- Added `stack health` CLI command with graceful degradation
- SnapshotManager for DuckDB data versioning and rollback
- .woodpecker.yml CI pipeline: lint, test (99% gate), marimo check
- Error handling audit: no bare except clauses, all exceptions logged
Closes #31, #32, #33, #34, #35.
2026-03-12 18:28:55 -04:00

126 lines
4.0 KiB
Python

"""Tests for aco.load.bcda, aco.load.stage modules."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
class TestLoadBcda:
def test_load_bcda_with_dir(self, tmp_path) -> None:
flat_dir = tmp_path / "flat"
flat_dir.mkdir()
(flat_dir / "patient.parquet").write_text("fake")
with (
patch("aco.load.bcda.Path"),
patch("duckdb.connect") as mock_con,
patch("conf.path", return_value=tmp_path),
):
con = MagicMock()
con.execute.return_value.fetchone.return_value = (42,)
mock_con.return_value = con
from aco.load.bcda import load_bcda
load_bcda(ndjson_dir=tmp_path, database=str(tmp_path / "db"))
def test_find_latest_export_missing(self, tmp_path) -> None:
from aco.load.bcda import _find_latest_export
with pytest.raises(FileNotFoundError, match="No exports directory"):
_find_latest_export(tmp_path)
def test_find_latest_export_empty(self, tmp_path) -> None:
(tmp_path / "exports").mkdir()
from aco.load.bcda import _find_latest_export
with pytest.raises(FileNotFoundError, match="No export directories"):
_find_latest_export(tmp_path)
def test_find_latest_export_ok(self, tmp_path) -> None:
exports = tmp_path / "exports"
exports.mkdir()
(exports / "job_001").mkdir()
(exports / "job_002").mkdir()
from aco.load.bcda import _find_latest_export
result = _find_latest_export(tmp_path)
assert result.is_dir()
class TestStageAll:
def test_stage_empty(self) -> None:
from aco.load.stage import stage_all
result = stage_all()
assert result == {}
def test_stage_cclf(self) -> None:
with patch(
"aco.load.cclf.load_cclf_directory",
return_value={"cclf.cclf1": 100},
):
from aco.load.stage import stage_all
result = stage_all(cclf_dir=Path("/tmp/fake"))
assert "cclf" in result
assert result["cclf"]["cclf.cclf1"] == 100
def test_stage_bcda(self) -> None:
with patch(
"aco.load.bcda.load_bcda",
return_value={"bcda.patient": 50},
):
from aco.load.stage import stage_all
result = stage_all(bcda_ndjson_dir=Path("/tmp/fake"))
assert "bcda" in result
def test_stage_seed(self) -> None:
with patch(
"aco.load.seed.load_seeds",
return_value={"ref.zips": 42000},
):
from aco.load.stage import stage_all
result = stage_all(seed_dir=Path("/tmp/fake"))
assert "seed" in result
def test_stage_promote_to_iceberg(self) -> None:
with (
patch(
"aco.load.cclf.load_cclf_directory",
return_value={"cclf.cclf1": 100},
),
patch("aco.load.stage._promote_to_iceberg") as mock_promote,
):
from aco.load.stage import stage_all
stage_all(cclf_dir=Path("/tmp/fake"), promote_to_iceberg=True)
mock_promote.assert_called_once()
def test_promote_to_iceberg(self) -> None:
mock_src = MagicMock()
mock_ice = MagicMock()
mock_df = MagicMock()
mock_src.load.return_value = mock_df
with (
patch("aco.lake.context.DuckDBContext", return_value=mock_src),
patch("aco.lake.context.IcebergContext", return_value=mock_ice),
patch("conf.cfg") as mock_cfg,
patch("conf.path", return_value="/tmp/fake.duckdb"),
):
mock_cfg.lake.nessie.catalog_uri = "http://nessie:19120"
mock_cfg.lake.warehouse = "s3://wh"
mock_cfg.lake.get.return_value = "http://rustfs:9000"
from aco.load.stage import _promote_to_iceberg
_promote_to_iceberg({"cclf": {"cclf.cclf1": 100}})
mock_ice.save.assert_called_once()