122 lines
4.9 KiB
Python
122 lines
4.9 KiB
Python
# #region Test.CleanRelease.ReportBuilder [C:3] [TYPE Module] [SEMANTICS test,clean-release,report,builder,compliance]
|
|
# @BRIEF Tests for report_builder.py — ComplianceReportBuilder.build_report_payload, persist_report.
|
|
# @RELATION BINDS_TO -> [ReportBuilder]
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
|
|
|
|
from datetime import UTC, datetime
|
|
from unittest.mock import MagicMock
|
|
import pytest
|
|
|
|
|
|
# #region TestBuildReportPayload [C:2] [TYPE Module]
|
|
class TestBuildReportPayload:
|
|
@pytest.fixture
|
|
def repo(self):
|
|
return MagicMock()
|
|
|
|
@pytest.fixture
|
|
def builder(self, repo):
|
|
from src.services.clean_release.report_builder import ComplianceReportBuilder
|
|
return ComplianceReportBuilder(repo)
|
|
|
|
def test_raises_for_running_run(self, builder):
|
|
"""Non-terminal (RUNNING) run raises ValueError (line 37)."""
|
|
from src.services.clean_release.enums import RunStatus
|
|
run = MagicMock()
|
|
run.status = RunStatus.RUNNING
|
|
with pytest.raises(ValueError, match="non-terminal"):
|
|
builder.build_report_payload(run, [])
|
|
|
|
def test_raises_for_blocked_with_no_violations(self, builder):
|
|
"""BLOCKED final_status with zero blocking violations raises ValueError."""
|
|
from src.services.clean_release.enums import ComplianceDecision, RunStatus
|
|
run = MagicMock()
|
|
run.status = RunStatus.SUCCEEDED
|
|
run.final_status = ComplianceDecision.BLOCKED
|
|
violations = []
|
|
with pytest.raises(ValueError, match="blocking violation"):
|
|
builder.build_report_payload(run, violations)
|
|
|
|
def test_raises_for_blocked_with_non_blocking_violations(self, builder):
|
|
"""BLOCKED run with violations but none blocking raises ValueError."""
|
|
from src.services.clean_release.enums import ComplianceDecision, RunStatus
|
|
from src.models.clean_release import ComplianceViolation
|
|
run = MagicMock()
|
|
run.status = RunStatus.SUCCEEDED
|
|
run.final_status = ComplianceDecision.BLOCKED
|
|
violations = [
|
|
ComplianceViolation(
|
|
id="v1", run_id="run-1", stage_name="DATA_PURITY",
|
|
code="WARN001", message="warning", severity="MINOR",
|
|
evidence_json={"blocked_release": False},
|
|
)
|
|
]
|
|
with pytest.raises(ValueError, match="blocking violation"):
|
|
builder.build_report_payload(run, violations)
|
|
|
|
def test_returns_report_for_passed_run(self, builder):
|
|
"""PASSED run returns ComplianceReport with correct summary."""
|
|
from src.services.clean_release.enums import ComplianceDecision, RunStatus
|
|
from src.models.clean_release import ComplianceViolation
|
|
run = MagicMock()
|
|
run.id = "check-run-1"
|
|
run.candidate_id = "cand-1"
|
|
run.status = RunStatus.SUCCEEDED
|
|
run.final_status = ComplianceDecision.PASSED
|
|
|
|
report = builder.build_report_payload(run, [])
|
|
assert report is not None
|
|
assert report.run_id == "check-run-1"
|
|
assert report.final_status == ComplianceDecision.PASSED
|
|
assert report.immutable is True
|
|
assert "no blocking violations" in report.summary_json["operator_summary"]
|
|
|
|
def test_returns_report_for_blocked_run(self, builder):
|
|
"""BLOCKED run with blocking violations returns report."""
|
|
from src.services.clean_release.enums import ComplianceDecision, RunStatus
|
|
from src.models.clean_release import ComplianceViolation
|
|
run = MagicMock()
|
|
run.id = "check-run-2"
|
|
run.candidate_id = "cand-2"
|
|
run.status = RunStatus.SUCCEEDED
|
|
run.final_status = ComplianceDecision.BLOCKED
|
|
|
|
violations = [
|
|
ComplianceViolation(
|
|
id="v1", run_id="run-1", stage_name="DATA_PURITY",
|
|
code="POL001", message="prohibited artifact", severity="CRITICAL",
|
|
evidence_json={"blocked_release": True},
|
|
)
|
|
]
|
|
report = builder.build_report_payload(run, violations)
|
|
assert report is not None
|
|
assert report.summary_json["violations_count"] == 1
|
|
assert report.summary_json["blocking_violations_count"] == 1
|
|
assert "Blocked" in report.summary_json["operator_summary"]
|
|
|
|
|
|
# #endregion TestBuildReportPayload
|
|
|
|
|
|
# #region TestPersistReport [C:2] [TYPE Module]
|
|
class TestPersistReport:
|
|
def test_persist_report(self):
|
|
"""persist_report delegates to repository."""
|
|
repo = MagicMock()
|
|
repo.save_report.return_value = "saved_report"
|
|
from src.services.clean_release.report_builder import ComplianceReportBuilder
|
|
builder = ComplianceReportBuilder(repo)
|
|
mock_report = MagicMock()
|
|
result = builder.persist_report(mock_report)
|
|
assert result == "saved_report"
|
|
repo.save_report.assert_called_once_with(mock_report)
|
|
|
|
|
|
# #endregion TestPersistReport
|
|
|
|
# #endregion Test.CleanRelease.ReportBuilder
|