- 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.
126 lines
4.0 KiB
Python
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()
|