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_scope.py · raw

 1"""Tests for scope / Statement of Applicability parsing."""
 2
 3from pathlib import Path
 4
 5import pytest
 6
 7from control_coverage import scope
 8
 9FIXTURES = Path(__file__).parent / "fixtures"
10
11
12def test_loads_exclusions_and_owners():
13    scp = scope.load(FIXTURES / "scope.yaml")
14    assert scp.subject == "Acme Production"
15    assert scp.frameworks == ["SOC2", "ISO"]
16    assert scp.excluded("ISO:A.7.1")
17    assert "cloud-hosted" in scp.reason("ISO:A.7.1")
18    assert scp.owner("SOC2:CC6.1") == "platform-team"
19
20
21def test_exclusion_without_reason_is_rejected(tmp_path):
22    bad = tmp_path / "bad.yaml"
23    bad.write_text("exclusions:\n  - control: ISO:A.5.7\n")
24    with pytest.raises(ValueError, match="needs a non-empty 'reason'"):
25        scope.load(bad)
26
27
28def test_exclusion_missing_control_key_is_rejected(tmp_path):
29    bad = tmp_path / "bad.yaml"
30    bad.write_text("exclusions:\n  - reason: no control key\n")
31    with pytest.raises(ValueError, match="must be a mapping with a 'control' key"):
32        scope.load(bad)
33
34
35def test_family_exclusion_parses(tmp_path):
36    f = tmp_path / "s.yaml"
37    f.write_text(
38        "exclude_families:\n"
39        "  - {framework: SOC2, family: Privacy, reason: 'Not in the audit scope.'}\n"
40    )
41    scp = scope.load(f)
42    assert scp.family_excluded("SOC2", "Privacy")
43    assert "audit scope" in scp.family_reason("SOC2", "Privacy")
44    assert not scp.family_excluded("SOC2", "Availability")
45
46
47def test_family_exclusion_needs_reason(tmp_path):
48    f = tmp_path / "s.yaml"
49    f.write_text("exclude_families:\n  - {framework: SOC2, family: Privacy}\n")
50    with pytest.raises(ValueError, match="needs a non-empty 'reason'"):
51        scope.load(f)
52
53
54def test_family_exclusion_needs_framework_and_family(tmp_path):
55    f = tmp_path / "s.yaml"
56    f.write_text("exclude_families:\n  - {family: Privacy, reason: x}\n")
57    with pytest.raises(ValueError, match="'framework' and 'family'"):
58        scope.load(f)
59
60
61def test_empty_scope_excludes_nothing():
62    scp = scope.empty()
63    assert not scp.excluded("ISO:A.7.1")
64    assert not scp.family_excluded("SOC2", "Privacy")
65    assert scp.frameworks == []