- 5519 unit tests covering all modules (aco, bcda, bls, cms, pfs, rex, bib) - ruff lint + format enforcement across entire codebase (377 files reformatted) - pre-commit hook: ruff check, ruff format, pytest - Woodpecker CI split into ci.yml (quality gate) and deploy.yml (package + images) - ci.yml: lint → test → validate-compose, runs on every push/PR - deploy.yml: build + publish Python package to Gitea PyPI registry, then container image builds, Trivy scans, and registry push (main branch only) - Gitea branch protection on main: requires CI status checks to pass - .gitignore updated for .coverage, dist/, *.egg-info/ - grafana config moved to dev/grafana/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""Tests for aco.express.base — Expr DSL primitive model."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from aco.express.base import Expr
|
|
from bib.tag import Tag
|
|
|
|
# ── fixtures ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _dummy_fn(core__encounter):
|
|
"""A dummy narwhalify function for Expr tests."""
|
|
return core__encounter
|
|
|
|
|
|
def _multi_input_fn(core__encounter, core__patient):
|
|
"""A function with multiple inputs."""
|
|
return core__encounter
|
|
|
|
|
|
# ── Expr model ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestExpr:
|
|
def test_create_minimal(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert expr.name == "core.encounter"
|
|
assert expr.fn is _dummy_fn
|
|
|
|
def test_create_with_all_fields(self) -> None:
|
|
expr = Expr(
|
|
name="readmissions._int_encounter",
|
|
fn=_dummy_fn,
|
|
output=None,
|
|
after=["core.encounter"],
|
|
refs=[Tag.module("aco")],
|
|
description="Test expression",
|
|
)
|
|
assert expr.after == ["core.encounter"]
|
|
assert len(expr.refs) == 1
|
|
assert expr.description == "Test expression"
|
|
|
|
def test_name_must_be_qualified(self) -> None:
|
|
with pytest.raises(ValueError, match="qualified"):
|
|
Expr(name="encounter", fn=_dummy_fn)
|
|
|
|
def test_doc_returns_description(self) -> None:
|
|
expr = Expr(
|
|
name="core.encounter",
|
|
fn=_dummy_fn,
|
|
description="My description",
|
|
)
|
|
assert expr.doc == "My description"
|
|
|
|
def test_doc_falls_back_to_docstring(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert "dummy narwhalify" in expr.doc
|
|
|
|
def test_inputs_from_signature(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert "core.encounter" in expr.inputs
|
|
|
|
def test_multi_inputs(self) -> None:
|
|
expr = Expr(name="core.test", fn=_multi_input_fn)
|
|
inputs = expr.inputs
|
|
assert "core.encounter" in inputs
|
|
assert "core.patient" in inputs
|
|
|
|
def test_qualified_refs_includes_auto_tag(self) -> None:
|
|
tag = Tag.module("aco")
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn, refs=[tag])
|
|
qrefs = expr.qualified_refs
|
|
# First ref should be auto-generated table tag
|
|
assert qrefs[0].namespace == "table"
|
|
assert qrefs[0].value == "core.encounter"
|
|
# Second should be the user-provided tag
|
|
assert qrefs[1] == tag
|
|
|
|
def test_repr(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
r = repr(expr)
|
|
assert "core.encounter" in r
|
|
assert "_dummy_fn" in r
|
|
|
|
def test_default_after_is_empty(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert expr.after == []
|
|
|
|
def test_default_refs_is_empty(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert expr.refs == []
|
|
|
|
def test_default_description_is_empty(self) -> None:
|
|
expr = Expr(name="core.encounter", fn=_dummy_fn)
|
|
assert expr.description == ""
|