Files
ss-tools/backend/tests/services/clean_release/test_compliance_execution_full.py

272 lines
13 KiB
Python

# #region Test.ComplianceExecution.Full [C:3] [TYPE Module] [SEMANTICS test,clean-release,compliance,execution]
# @BRIEF Full tests for ComplianceExecutionService — manifest resolution, run execution, stage persistence, violation tracking, error handling, report generation.
# @RELATION BINDS_TO -> [ComplianceExecutionService]
from __future__ import annotations
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from src.models.clean_release import (
CheckFinalStatus,
CleanPolicySnapshot,
ComplianceRun,
ComplianceStageRun,
ComplianceViolation,
DistributionManifest,
ReleaseCandidate,
SourceRegistrySnapshot,
)
from src.services.clean_release.compliance_execution_service import (
ComplianceExecutionResult,
ComplianceExecutionService,
)
from src.services.clean_release.enums import CandidateStatus, ComplianceDecision, RunStatus
from src.services.clean_release.exceptions import ComplianceRunError
from src.services.clean_release.repository import CleanReleaseRepository
from src.services.clean_release.stages.base import ComplianceStage, ComplianceStageContext, StageExecutionResult
from src.services.clean_release.enums import ComplianceStageName
class _MockStage(ComplianceStage):
"""Deterministic mock stage for test isolation."""
def __init__(self, name: str = "DATA_PURITY", decision: ComplianceDecision = ComplianceDecision.PASSED):
self.stage_name = ComplianceStageName(name)
self._decision = decision
def execute(self, context: ComplianceStageContext) -> StageExecutionResult:
return StageExecutionResult(
decision=self._decision,
details_json={"checked": True},
violations=[],
)
def _make_repo_with_candidate(
candidate_id: str = "cand-exe-1",
status: str = CandidateStatus.MANIFEST_BUILT.value,
policy_id: str = "policy-exe-1",
registry_id: str = "registry-exe-1",
manifest_id: str = "manifest-exe-1",
) -> CleanReleaseRepository:
repo = CleanReleaseRepository()
repo.save_candidate(ReleaseCandidate(
id=candidate_id, version="1.0.0", source_snapshot_ref="git:sha",
created_by="tester", created_at=datetime.now(UTC), status=status,
))
repo.save_policy(CleanPolicySnapshot(
id=policy_id, policy_id=policy_id, policy_version="1",
content_json={}, registry_snapshot_id=registry_id, immutable=True,
))
repo.save_registry(SourceRegistrySnapshot(
id=registry_id, registry_id=registry_id, registry_version="1",
allowed_hosts=["internal.local"], allowed_schemes=["https"],
immutable=True,
))
repo.save_manifest(DistributionManifest(
id=manifest_id, candidate_id=candidate_id, manifest_version=1,
manifest_digest="digest1", artifacts_digest="digest1",
source_snapshot_ref="git:sha",
content_json={"summary": {"included_count": 1, "excluded_count": 0, "prohibited_detected_count": 0}},
created_by="tester", created_at=datetime.now(UTC), immutable=True,
))
return repo
class TestComplianceExecutionServiceBasic:
"""ComplianceExecutionService — basic error paths."""
# #region test_missing_candidate_raises [C:2] [TYPE Function]
def test_missing_candidate_raises(self):
"""Missing candidate → ComplianceRunError."""
repo = CleanReleaseRepository()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
with pytest.raises(ComplianceRunError, match="not found"):
svc.execute_run(candidate_id="nonexistent", requested_by="tester")
# #endregion test_missing_candidate_raises
# #region test_resolve_manifest_by_id [C:2] [TYPE Function]
def test_resolve_manifest_by_id(self):
"""Explicit manifest_id resolves correctly."""
repo = _make_repo_with_candidate()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
manifest = svc._resolve_manifest("cand-exe-1", "manifest-exe-1")
assert manifest is not None
assert manifest.id == "manifest-exe-1"
# #endregion test_resolve_manifest_by_id
# #region test_resolve_manifest_not_found_raises [C:2] [TYPE Function]
def test_resolve_manifest_not_found_raises(self):
"""Nonexistent manifest_id → ComplianceRunError."""
repo = _make_repo_with_candidate()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
with pytest.raises(ComplianceRunError, match="not found"):
svc._resolve_manifest("cand-exe-1", "nonexistent")
# #endregion test_resolve_manifest_not_found_raises
# #region test_resolve_manifest_not_belonging_raises [C:2] [TYPE Function]
def test_resolve_manifest_not_belonging_raises(self):
"""Manifest not belonging to candidate → ComplianceRunError."""
repo = _make_repo_with_candidate()
repo.save_manifest(DistributionManifest(
id="manifest-other", candidate_id="cand-other", manifest_version=1,
manifest_digest="d", artifacts_digest="d",
source_snapshot_ref="ref", content_json={},
created_by="tester", created_at=datetime.now(UTC), immutable=True,
))
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
with pytest.raises(ComplianceRunError, match="does not belong"):
svc._resolve_manifest("cand-exe-1", "manifest-other")
# #endregion test_resolve_manifest_not_belonging_raises
# #region test_resolve_manifest_no_manifests_raises [C:2] [TYPE Function]
def test_resolve_manifest_no_manifests_raises(self):
"""Candidate has no manifests → ComplianceRunError."""
repo = _make_repo_with_candidate()
repo.manifests.clear()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
with pytest.raises(ComplianceRunError, match="no manifest"):
svc._resolve_manifest("cand-exe-1", None)
# #endregion test_resolve_manifest_no_manifests_raises
# #region test_persist_stage_run [C:2] [TYPE Function]
def test_persist_stage_run(self):
"""Stage run is persisted via repository."""
repo = _make_repo_with_candidate()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
stage_run = ComplianceStageRun(
id="stg-test-1", run_id="run-test-1", stage_name="DATA_PURITY",
status="SUCCEEDED", decision="PASSED", details_json={},
)
svc._persist_stage_run(stage_run)
assert repo.stage_runs.get("stg-test-1") is not None
# #endregion test_persist_stage_run
# #region test_persist_violations [C:2] [TYPE Function]
def test_persist_violations(self):
"""Violations are persisted via repository."""
repo = _make_repo_with_candidate()
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
violations = [
ComplianceViolation(
id="viol-1", run_id="run-1", stage_name="DATA_PURITY",
code="EXT", severity="MAJOR", message="test",
)
]
svc._persist_violations(violations)
assert repo.violations.get("viol-1") is not None
# #endregion test_persist_violations
class TestComplianceExecutionServiceRun:
"""ComplianceExecutionService.execute_run — full execution flow."""
# #region test_execute_run_success [C:2] [TYPE Function]
@patch("src.services.clean_release.compliance_execution_service.resolve_trusted_policy_snapshots")
def test_execute_run_success(self, mock_resolve):
"""Full successful run returns ComplianceExecutionResult."""
repo = _make_repo_with_candidate()
mock_resolve.return_value = (
repo.policies["policy-exe-1"],
repo.registries["registry-exe-1"],
)
config = MagicMock()
config.get_config.return_value.settings.clean_release.active_policy_id = "policy-exe-1"
config.get_config.return_value.settings.clean_release.active_registry_id = "registry-exe-1"
stage = _MockStage(name="DATA_PURITY")
svc = ComplianceExecutionService(repository=repo, config_manager=config, stages=[stage])
result = svc.execute_run(candidate_id="cand-exe-1", requested_by="tester")
assert isinstance(result, ComplianceExecutionResult)
assert result.run is not None
assert result.run.candidate_id == "cand-exe-1"
assert result.run.manifest_id == "manifest-exe-1"
assert len(result.stage_runs) == 1
assert result.stage_runs[0].stage_name == "DATA_PURITY"
assert result.report is not None
# #endregion test_execute_run_success
# #region test_execute_run_stage_failure [C:2] [TYPE Function]
@patch("src.services.clean_release.compliance_execution_service.resolve_trusted_policy_snapshots")
def test_execute_run_stage_failure(self, mock_resolve):
"""Stage execution error → run marked FAILED."""
repo = _make_repo_with_candidate()
mock_resolve.return_value = (
repo.policies["policy-exe-1"],
repo.registries["registry-exe-1"],
)
config = MagicMock()
config.get_config.return_value.settings.clean_release.active_policy_id = "policy-exe-1"
config.get_config.return_value.settings.clean_release.active_registry_id = "registry-exe-1"
failing_stage = _MockStage(name="DATA_PURITY", decision=ComplianceDecision.ERROR)
svc = ComplianceExecutionService(repository=repo, config_manager=config, stages=[failing_stage])
result = svc.execute_run(candidate_id="cand-exe-1", requested_by="tester")
assert result.run.status == RunStatus.SUCCEEDED.value
assert result.run.final_status == CheckFinalStatus.FAILED.value
# #endregion test_execute_run_stage_failure
# #region test_execute_run_policy_resolution_error [C:2] [TYPE Function]
@patch("src.services.clean_release.compliance_execution_service.resolve_trusted_policy_snapshots")
def test_execute_run_policy_resolution_error(self, mock_resolve):
"""Policy resolution failure → ComplianceRunError."""
from src.services.clean_release.exceptions import PolicyResolutionError
repo = _make_repo_with_candidate()
mock_resolve.side_effect = PolicyResolutionError("policy missing")
config = MagicMock()
svc = ComplianceExecutionService(repository=repo, config_manager=config)
with pytest.raises(ComplianceRunError, match="policy missing"):
svc.execute_run(candidate_id="cand-exe-1", requested_by="tester")
# #endregion test_execute_run_policy_resolution_error
# #region test_execute_result_dataclass [C:2] [TYPE Function]
def test_execute_result_dataclass(self):
"""ComplianceExecutionResult dataclass works as expected."""
run = MagicMock(spec=ComplianceRun)
stage_runs = [MagicMock(spec=ComplianceStageRun)]
violations = [MagicMock(spec=ComplianceViolation)]
result = ComplianceExecutionResult(
run=run, report=None, stage_runs=stage_runs, violations=violations,
)
assert result.run == run
assert result.report is None
assert result.stage_runs == stage_runs
assert result.violations == violations
# #endregion test_execute_result_dataclass
# #region test_execute_run_stage_raises_exception [C:2] [TYPE Function]
@patch("src.services.clean_release.compliance_execution_service.resolve_trusted_policy_snapshots")
def test_execute_run_stage_raises_exception(self, mock_resolve):
"""Stage that raises an exception triggers the except handler (lines 211-217)."""
repo = _make_repo_with_candidate()
mock_resolve.return_value = (
repo.policies["policy-exe-1"],
repo.registries["registry-exe-1"],
)
config = MagicMock()
config.get_config.return_value.settings.clean_release.active_policy_id = "policy-exe-1"
config.get_config.return_value.settings.clean_release.active_registry_id = "registry-exe-1"
class CrashingStage(ComplianceStage):
stage_name = ComplianceStageName("DATA_PURITY")
def execute(self, context):
raise RuntimeError("stage crashed unexpectedly")
svc = ComplianceExecutionService(repository=repo, config_manager=config, stages=[CrashingStage()])
result = svc.execute_run(candidate_id="cand-exe-1", requested_by="tester")
assert result.run.status == RunStatus.FAILED.value
assert result.run.final_status == ComplianceDecision.ERROR.value
assert "stage crashed" in result.run.failure_reason
# #endregion test_execute_run_stage_raises_exception