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