titan-iac/testing/tests/test_semgrep_report.py
jenkins 0da9e4c82d refactor: restructure services layout, retire oceanus, add aether scaffolding
- Move flat service manifests into structured subdirs (apps/, bootstrap-jobs/,
  repair-jobs/, migration-jobs/, validation-jobs/, node-ops/, networking/)
- Retire oneoffs/ directories across services
- Remove oceanus cluster and its host roles; add aether cluster + terraform scaffolding
- Reorganize scripts/ into ops/, render/, sync/, manual-tests/
- Add Makefile with render/validate/test/flux targets and repo-structure tests
- Update flux-system application CRs to the new paths
- Add hermes-automated-triage-24h-plan knowledge doc (+ comms mirror)
- Refresh knowledge catalogs, dashboards, vmalert rules, quality contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:21:36 -03:00

171 lines
5.3 KiB
Python

"""Unit tests for Semgrep report normalization."""
from __future__ import annotations
import json
from pathlib import Path
from ci.scripts import semgrep_report
def test_build_report_counts_severities_and_blocks_errors() -> None:
"""ERROR findings should fail the report while lower severities remain visible."""
report = semgrep_report.build_report(
{
"results": [
{"extra": {"severity": "ERROR"}},
{"extra": {"severity": "WARNING"}},
{"extra": {"severity": "INFO"}},
{"extra": {"severity": "unknown-ish"}},
],
"errors": [],
},
semgrep_exit_code=1,
)
assert report["status"] == "failed"
assert report["findings_total"] == 4
assert report["blocking_findings"] == 1
assert report["severity_counts"] == {
"ERROR": 1,
"WARNING": 1,
"INFO": 1,
"UNKNOWN": 1,
}
def test_build_report_marks_clean_scan_ok() -> None:
"""Clean Semgrep JSON with exit code zero should publish an OK report."""
report = semgrep_report.build_report(
{"results": [], "errors": []},
semgrep_exit_code=0,
)
assert report["status"] == "ok"
assert report["blocking_findings"] == 0
assert report["errors_total"] == 0
def test_build_sonar_issues_converts_findings() -> None:
"""Semgrep findings should be importable by SonarQube generic issues."""
issues = semgrep_report.build_sonar_issues(
{
"results": [
{
"check_id": "python.lang.security.audit.danger",
"path": "app/main.py",
"start": {"line": 7},
"end": {"line": 9},
"extra": {"severity": "ERROR", "message": "dangerous call"},
},
{"check_id": "missing-path", "extra": {"severity": "INFO"}},
]
}
)
assert len(issues["issues"]) == 1
assert issues["issues"][0]["engineId"] == "semgrep"
assert issues["issues"][0]["severity"] == "CRITICAL"
assert issues["issues"][0]["primaryLocation"]["filePath"] == "app/main.py"
assert issues["issues"][0]["primaryLocation"]["textRange"] == {
"startLine": 7,
"endLine": 9,
}
def test_build_sonar_issues_uses_safe_defaults_for_sparse_findings() -> None:
"""Sparse findings should still produce valid file-level Sonar issues."""
issues = semgrep_report.build_sonar_issues(
{
"results": [
{
"path": "app/config.yaml",
"start": {"line": "bad"},
"end": {"line": 0},
"extra": "not-a-dict",
}
]
}
)
issue = issues["issues"][0]
assert issue["ruleId"] == "semgrep.unknown"
assert issue["type"] == "CODE_SMELL"
assert issue["severity"] == "INFO"
assert issue["primaryLocation"]["message"] == "Semgrep finding"
assert issue["primaryLocation"]["textRange"] == {"startLine": 1, "endLine": 1}
def test_read_json_handles_invalid_json_and_non_object(tmp_path: Path) -> None:
"""Report loading should fail closed for bad JSON and wrong top-level shapes."""
bad_json = tmp_path / "bad.json"
bad_json.write_text("{bad", encoding="utf-8")
list_json = tmp_path / "list.json"
list_json.write_text("[]", encoding="utf-8")
assert semgrep_report._read_json(bad_json)["errors"][0]["message"].startswith("invalid JSON:")
assert semgrep_report._read_json(list_json) == {
"errors": [{"message": "report payload is not an object"}],
"results": [],
}
def test_main_handles_missing_or_invalid_input(tmp_path: Path) -> None:
"""Missing or invalid Semgrep JSON should still produce a failed report file."""
output = tmp_path / "build" / "semgrep-report.json"
rc = semgrep_report.main(
[
"--semgrep-json",
str(tmp_path / "missing.json"),
"--exit-code",
"2",
"--output",
str(output),
"--sonar-issues-output",
str(tmp_path / "build" / "sonar-issues.json"),
]
)
assert rc == 0
payload = json.loads(output.read_text(encoding="utf-8"))
assert payload["status"] == "failed"
assert payload["errors_total"] == 1
sonar_payload = json.loads((tmp_path / "build" / "sonar-issues.json").read_text(encoding="utf-8"))
assert sonar_payload == {"issues": []}
def test_main_honors_custom_blocking_severity_without_sonar_output(tmp_path: Path) -> None:
"""CLI callers can choose stricter blocking severities without writing Sonar issues."""
semgrep_json = tmp_path / "semgrep.json"
semgrep_json.write_text(
json.dumps({"results": [{"path": "app.py", "extra": {"severity": "WARNING"}}], "errors": []}),
encoding="utf-8",
)
output = tmp_path / "semgrep-report.json"
rc = semgrep_report.main(
[
"--semgrep-json",
str(semgrep_json),
"--exit-code",
"1",
"--output",
str(output),
"--blocking-severity",
"WARNING",
]
)
assert rc == 0
payload = json.loads(output.read_text(encoding="utf-8"))
assert payload["status"] == "failed"
assert payload["blocking_findings"] == 1