audit-labs/control-coverage

Control coverage and blind-spot analysis for audit evidence.

clone: git clone https://gitbay.org/audit-labs/control-coverage.git

v0.1.0: tests/test_trend.py · raw

 1"""Tests for coverage trend (diffing two corpora)."""
 2
 3from pathlib import Path
 4
 5from control_coverage import catalog, corpus, trend
 6from control_coverage.coverage import evaluate
 7
 8FIXTURES = Path(__file__).parent / "fixtures"
 9GITHUB = FIXTURES / "github_audit_acme_2026-01-01.json"
10AWS = FIXTURES / "aws_audit_acme_2026-01-01.json"
11BASELINE = FIXTURES / "baseline_github.json"
12
13
14def _cov(paths):
15    obs = corpus.load_corpus(paths)
16    cats = catalog.load_frameworks(["SOC2"])
17    return evaluate(cats, obs)
18
19
20def _compare():
21    return trend.compare(_cov([BASELINE]), _cov([GITHUB, AWS]))
22
23
24def _delta(fc, control_id):
25    return next(d for d in fc.deltas if d.id == control_id)
26
27
28def test_categories_reflect_state_movement():
29    fc = _compare().frameworks[0]
30    assert _delta(fc, "CC6.1").category == trend.IMPROVED  # failing -> supported
31    assert _delta(fc, "CC6.3").category == trend.UNCHANGED  # failing -> failing
32    assert _delta(fc, "CC6.6").category == trend.GAINED  # unaddressed -> failing
33    assert _delta(fc, "CC7.2").category == trend.GAINED  # unaddressed -> supported
34    assert _delta(fc, "CC9.2").category == trend.LOST  # supported -> unaddressed
35
36
37def test_counts_and_coverage_delta():
38    fc = _compare().frameworks[0]
39    c = fc.counts
40    assert c[trend.IMPROVED] == 1
41    assert c[trend.GAINED] == 3  # CC6.6, CC7.2, CC8.1
42    assert c[trend.LOST] == 1
43    assert c[trend.REGRESSED] == 0
44    assert fc.coverage_delta == round(fc.new_coverage_pct - fc.old_coverage_pct, 1)
45    assert fc.new_coverage_pct > fc.old_coverage_pct
46
47
48def test_regressions_count_lost_and_regressed():
49    report = _compare()
50    assert report.total_regressions == 1  # the single LOST control
51
52
53def test_markdown_lists_changes_only():
54    md = trend.render_markdown(_compare())
55    assert "# Coverage Trend" in md
56    assert "CC9.2" in md  # a lost control appears
57    assert "CC1.1" not in md  # an unchanged blind spot does not
58
59
60def test_json_omits_unchanged():
61    import json
62
63    doc = json.loads(trend.render_json(_compare()))
64    changes = doc["frameworks"][0]["changes"]
65    ids = {c["id"] for c in changes}
66    assert "CC9.2" in ids
67    assert "CC1.1" not in ids
68
69
70def test_html_is_self_contained():
71    html = trend.render_html(_compare())
72    assert html.startswith("<!doctype html>")
73    assert "<style>" in html
74    assert "http://" not in html and "https://" not in html
75    assert "CC9.2" in html  # a changed control shows up