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

  1"""Tests for the command-line interface."""
  2
  3from pathlib import Path
  4
  5import pytest
  6
  7from control_coverage import cli
  8
  9FIXTURES = Path(__file__).parent / "fixtures"
 10GITHUB = str(FIXTURES / "github_audit_acme_2026-01-01.json")
 11AWS = str(FIXTURES / "aws_audit_acme_2026-01-01.json")
 12SCOPE = str(FIXTURES / "scope.yaml")
 13
 14
 15def test_stdout_markdown_default(capsys):
 16    rc = cli.main([GITHUB, AWS, "--framework", "SOC2"])
 17    out = capsys.readouterr().out
 18    assert rc == 0
 19    assert "# Control Coverage" in out
 20    assert "Coverage" in out
 21
 22
 23def test_frameworks_inferred_from_corpus(capsys):
 24    cli.main([GITHUB, "--format", "json"])
 25    out = capsys.readouterr().out
 26    # github fixture cites SOC2, ISO, NIST codes -> all three inferred.
 27    for fw in ("SOC2", "ISO", "NIST"):
 28        assert f'"framework": "{fw}"' in out
 29
 30
 31def test_scope_file_supplies_frameworks_and_subject(capsys):
 32    cli.main([GITHUB, AWS, "--scope", SCOPE, "--format", "json"])
 33    out = capsys.readouterr().out
 34    assert '"subject": "Acme Production"' in out
 35    assert '"framework": "NIST"' not in out  # scope lists only SOC2, ISO
 36
 37
 38def test_blind_spots_mode(capsys):
 39    rc = cli.main([GITHUB, "--framework", "SOC2", "--blind-spots"])
 40    out = capsys.readouterr().out
 41    assert rc == 0
 42    assert "unaddressed" in out
 43    assert "SOC2:CC1.1" in out
 44
 45
 46def test_fail_under_gate_trips(capsys):
 47    rc = cli.main([GITHUB, "--framework", "SOC2", "--fail-under", "90"])
 48    assert rc == 1
 49    err = capsys.readouterr().err
 50    assert "coverage gate" in err
 51
 52
 53def test_fail_under_gate_passes(capsys):
 54    rc = cli.main([GITHUB, "--framework", "SOC2", "--fail-under", "1"])
 55    assert rc == 0
 56
 57
 58def test_out_dir_writes_files(tmp_path, capsys):
 59    rc = cli.main(
 60        [GITHUB, "--scope", SCOPE, "--format", "md,html,json,soa", "--out", str(tmp_path)]
 61    )
 62    assert rc == 0
 63    written = {p.name for p in tmp_path.iterdir()}
 64    assert "soa.md" in written
 65    assert any(n.endswith(".html") for n in written)
 66    assert any(n.endswith(".json") for n in written)
 67
 68
 69def test_missing_reports_errors():
 70    with pytest.raises(SystemExit):
 71        cli.main([str(FIXTURES / "nope.json"), "--framework", "SOC2"])
 72
 73
 74BASELINE = str(FIXTURES / "baseline_github.json")
 75
 76
 77def test_trend_mode_markdown(capsys):
 78    rc = cli.main([GITHUB, AWS, "--framework", "SOC2", "--baseline", BASELINE])
 79    out = capsys.readouterr().out
 80    assert rc == 0
 81    assert "# Coverage Trend" in out
 82
 83
 84def test_trend_html_output(tmp_path):
 85    cli.main([GITHUB, AWS, "--framework", "SOC2", "--baseline", BASELINE,
 86              "--format", "html,json", "--out", str(tmp_path)])
 87    names = {p.name for p in tmp_path.iterdir()}
 88    assert "trend.html" in names and "trend.json" in names
 89
 90
 91def test_crosswalk_mode(capsys):
 92    rc = cli.main([GITHUB, AWS, "--framework", "SOC2,ISO", "--crosswalk"])
 93    out = capsys.readouterr().out
 94    assert rc == 0
 95    assert "Minimal evidence set" in out
 96
 97
 98def test_crosswalk_and_baseline_conflict():
 99    with pytest.raises(SystemExit, match="cannot be combined"):
100        cli.main([GITHUB, "--crosswalk", "--baseline", BASELINE])
101
102
103def test_trend_rejects_soa_format():
104    with pytest.raises(SystemExit, match="trend mode supports"):
105        cli.main([GITHUB, "--framework", "SOC2", "--baseline", BASELINE, "--format", "soa"])
106
107
108def test_family_exclusion_via_cli(tmp_path, capsys):
109    scope_file = tmp_path / "scope.yaml"
110    scope_file.write_text(
111        "frameworks: [SOC2]\n"
112        "exclude_families:\n"
113        "  - {framework: SOC2, family: Privacy, reason: 'Not in scope.'}\n"
114    )
115    cli.main([GITHUB, "--scope", str(scope_file), "--format", "json"])
116    out = capsys.readouterr().out
117    assert '"state": "out_of_scope"' in out