audit-labs/control-coverage
Control coverage and blind-spot analysis for audit evidence.
clone: git clone https://gitbay.org/audit-labs/control-coverage.git
v1.0.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