audit-labs/audit-report

Turn audit-tools evidence packages into control-mapped, auditor-ready reports.

clone: git clone https://gitbay.org/audit-labs/audit-report.git

main: tests/test_reporters.py · raw

  1"""Tests for the report renderers and the CLI entry point."""
  2
  3import json
  4from pathlib import Path
  5
  6import pytest
  7
  8from audit_report import __version__, reporters
  9from audit_report.cli import main
 10from audit_report.engine import evaluate
 11from audit_report.loader import load_package
 12from audit_report.rules import load_ruleset
 13
 14FIXTURES = Path(__file__).parent / "fixtures"
 15RULESETS = Path("audit_report/rulesets")
 16
 17
 18def _report(pkg_name="aws_audit_acme_2026-01-01", ruleset="aws.yaml"):
 19    pkg = load_package(FIXTURES / pkg_name)
 20    rs = load_ruleset(RULESETS / ruleset)
 21    findings = evaluate(pkg, rs)
 22    return reporters.build_report(pkg, findings, rs)
 23
 24
 25def test_markdown_render_contains_sections():
 26    md = reporters.render(_report(), "md")
 27    assert "# Evidence Report — acme" in md
 28    assert "## Control coverage" in md
 29    assert "## Findings" in md
 30    assert "aws.network.no-open-ssh" in md
 31
 32
 33def test_html_render_is_self_contained():
 34    html = reporters.render(_report(), "html")
 35    assert html.startswith("<!doctype html>")
 36    assert "<style>" in html
 37    # No external resource references.
 38    assert "http://" not in html
 39    assert "https://" not in html
 40    assert "src=" not in html
 41
 42
 43def test_json_render_roundtrips():
 44    data = json.loads(reporters.render(_report(), "json"))
 45    assert data["platform"] == "aws"
 46    assert data["subject"] == "acme"
 47    ids = {f["id"]: f["status"] for f in data["findings"]}
 48    assert ids["aws.iam.console-mfa"] == "fail"
 49
 50
 51def test_json_stamps_tool_and_ruleset_provenance():
 52    data = json.loads(reporters.render(_report(), "json"))
 53    assert data["tool"] == {"name": "audit-report", "version": __version__}
 54    rs = data["ruleset"]
 55    assert rs["name"] == "AWS ITGC ruleset"
 56    assert rs["platform"] == "aws"
 57    assert rs["version"] == "2026.08.0"
 58    assert len(rs["sha256"]) == 64  # full SHA-256 hex digest of the ruleset file
 59
 60
 61def test_markdown_shows_ruleset_provenance():
 62    md = reporters.render(_report(), "md")
 63    assert "**Tool:** audit-report" in md
 64    assert "sha256:" in md
 65
 66
 67def test_html_escapes_evidence(tmp_path):
 68    pkg_dir = tmp_path / "aws_audit_x_2026-01-01"
 69    pkg_dir.mkdir()
 70    (pkg_dir / "open_security_groups.csv").write_text(
 71        "region,group_id,group_name,protocol,from_port,to_port,open_to\n"
 72        "us-east-1,sg-1,<script>x</script>,tcp,22,22,0.0.0.0/0\n",
 73        encoding="utf-8",
 74    )
 75    pkg = load_package(pkg_dir)
 76    report = reporters.build_report(pkg, evaluate(pkg, load_ruleset(RULESETS / "aws.yaml")))
 77    html = reporters.render(report, "html")
 78    assert "<script>x</script>" not in html
 79    assert "&lt;script&gt;" in html
 80
 81
 82def test_unknown_format_raises():
 83    report = _report()
 84    with pytest.raises(ValueError, match="unknown format"):
 85        reporters.render(report, "pdf")
 86
 87
 88def test_cli_writes_files_and_exit_code(tmp_path):
 89    out = tmp_path / "out"
 90    code = main(
 91        [
 92            str(FIXTURES / "aws_audit_acme_2026-01-01"),
 93            "--format",
 94            "md,html,json",
 95            "--out",
 96            str(out),
 97            "--fail-on",
 98            "high",
 99        ]
100    )
101    assert (out / "report.md").exists()
102    assert (out / "report.html").exists()
103    assert (out / "report.json").exists()
104    # The AWS fixture has high-severity failures (open SSH), so exit is non-zero.
105    assert code == 1
106
107
108def test_cli_fail_on_none_is_zero(tmp_path):
109    code = main(
110        [str(FIXTURES / "aws_audit_acme_2026-01-01"), "--format", "json", "--out", str(tmp_path)]
111    )
112    assert code == 0
113
114
115def test_cli_missing_package_returns_2():
116    assert main([str(FIXTURES / "nope")]) == 2