Agent reference: read before writing any test code. All test decisions flow from this document.
tests/
unit/ Pure function tests. No Neo4j. No I/O. Fast (<100ms total).
integration/ Full pipeline via testcontainers-neo4j. Requires Docker.
compliance/ Prohibited factor enforcement. Gate compile-time blocking.
contracts/ All 24 behavioral contracts, one TestContractNN class each.
invariants/ Engine invariants: score bounds, weight sums, determinism.
property/ Hypothesis-based property tests for scoring math.
make test # Full suite: unit + integration + compliance + contracts
make test-unit # Gate compilation, scoring math, parameter resolution only
make test-integration # testcontainers-neo4j — requires Docker running
make test-contracts # Verify all 24 contracts pass (fast, no Neo4j)
make test-property # Hypothesis property tests (scoring, gate logic)| Layer | Minimum Coverage |
|---|---|
engine/gates/ |
95% |
engine/scoring/ |
95% |
engine/config/ |
90% |
engine/feedback/ |
85% |
engine/causal/ |
85% |
engine/resolution/ |
85% |
chassis/ |
70% |
CI fails if thresholds are not met. Run make coverage to check locally.
# tests/unit/gates/test_proximity_gate.py
def test_proximity_gate_compiles_sanitized_label():
spec = GateSpec(type="proximity", candidateprop="distance", threshold=50)
domain = make_domain_spec(targetnode="Contact")
gate = ProximityGate()
clause = gate.compile_where(spec, domain)
assert "$max_distance" in clause # parameterized
assert "Contact" not in clause # label not in WHERE
assert "distance" in clause # prop name present# tests/unit/scoring/test_signal_weights.py
def test_lift_formula_clamps_to_bounds():
calc = SignalWeightCalculator(min_weight=0.1, max_weight=2.0)
weight = calc.compute(positive_count=5, total_count=5, base_rate=0.5)
assert 0.1 <= weight <= 2.0
def test_confidence_dampening_reduces_uncertain_weights():
weight_small_sample = calc.compute(positive_count=2, total_count=3, base_rate=0.5)
weight_large_sample = calc.compute(positive_count=200, total_count=300, base_rate=0.5)
assert abs(weight_small_sample - 1.0) < abs(weight_large_sample - 1.0)# tests/unit/config/test_schema.py
def test_domain_spec_rejects_unknown_gate_type():
with pytest.raises(ValidationError):
DomainSpec.model_validate({"gates": [{"type": "nonexistent_gate"}]})# tests/integration/test_match_pipeline.py
@pytest.mark.integration
async def test_full_match_pipeline(neo4j_container):
driver = await GraphDriver.create(neo4j_container.get_connection_url())
spec = load_domain_spec("domains/test_domain_spec.yaml")
result = await handle_match(
tenant=TenantContext(id="test"),
payload=MatchRequest(query_id="q1", limit=10),
driver=driver,
spec=spec,
)
assert result["status"] == "ok"
assert isinstance(result["matches"], list)
assert all(0.0 <= m["score"] <= 1.0 for m in result["matches"])# tests/compliance/test_prohibited_factors.py
def test_age_gate_rejected_at_compile_time():
spec = GateSpec(type="range", candidateprop="age")
with pytest.raises(ProhibitedFactorError, match="age"):
gate_compiler.compile(spec, domain)
def test_gender_scoring_dimension_blocked():
spec = ScoringDimension(field="gender")
with pytest.raises(ProhibitedFactorError, match="gender"):
scoring_assembler.compile(spec, domain)All 24 contracts are verified by three complementary gates:
python -m pytest tests/contracts/ # behavioral assertions, one class per contract
python tools/verify_contracts.py # the 27 contract docs exist and are wired
python tools/contract_scanner.py # banned-pattern regex rulestests/contracts/test_contract_registry.py is the drift gate: it fails if a YAML contract,
its docs: pointers, its scanner_rules, and its verification.test node ID ever disagree.
Contracts are documented in docs/contracts/ and .claude/rules/contracts.md.
Contract C-001 through C-024 must all pass before any merge.
# tests/property/test_score_bounds.py
from hypothesis import given, strategies as st
@given(st.floats(min_value=-1000, max_value=1000))
def test_score_always_clamped(raw_score):
result = clamp_score(raw_score)
assert 0.0 <= result <= 1.0
@given(st.dictionaries(st.text(), st.floats(0, 1), min_size=1))
def test_weight_sum_assertion(weights):
assume(sum(weights.values()) <= 1.0)
assert validate_weights(weights) is NoneRoot conftest.py provides:
neo4j_container— testcontainers-managed Neo4j instance (integration only)make_domain_spec()— factory for minimal valid DomainSpecmake_tenant_context()— factory for TenantContext with test defaultsmock_driver— AsyncMock GraphDriver for unit tests
tests/{layer}/{subsystem}/test_{module_name}.py
tests/unit/gates/test_proximity_gate.py
tests/unit/scoring/test_signal_weights.py
tests/integration/test_match_pipeline.py
tests/compliance/test_prohibited_factors.py
Functions: test_{what}_{condition}_{expected_outcome}
def test_gate_compiler_rejects_unsanitized_label_injection(): ...
def test_score_clamped_to_unit_interval_when_raw_exceeds_1(): ...