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 "<script>" 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