- 10 translate plugin test files (100% coverage on 12 modules) - assistant/handler tools: 85+ tests covering dispatch, registry, resolvers, routes, llm_planner, 13 tool handlers - clean release: artifact_catalog_loader, mappers, approval, publication tests - API routes: translate_helpers, validation_service extensions, datasets to 100% - notifications: providers/service tests - services: profile_preference_service - docs/orthogonal-test-report.md — full speckit.tests audit - Fixes: 3 git_base async mock failures, 4 assistant handler permission-check patches - .gitignore: coverage artifacts
219 lines
10 KiB
Python
219 lines
10 KiB
Python
# #region Test.CleanRelease.Facade [C:3] [TYPE Module] [SEMANTICS test,clean-release,facade,orchestration]
|
|
# @BRIEF Tests for CleanReleaseFacade — policy/registry resolution, candidate overview, list candidates.
|
|
# @RELATION BINDS_TO -> [clean_release_facade]
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock, PropertyMock
|
|
import pytest
|
|
|
|
from src.models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
|
|
from src.services.clean_release.dto import CandidateOverviewDTO
|
|
from src.services.clean_release.enums import CandidateStatus, ComplianceDecision, RunStatus
|
|
from src.services.clean_release.facade import CleanReleaseFacade
|
|
from src.services.clean_release.repositories import (
|
|
ApprovalRepository,
|
|
ArtifactRepository,
|
|
AuditRepository,
|
|
CandidateRepository,
|
|
ComplianceRepository,
|
|
ManifestRepository,
|
|
PolicyRepository,
|
|
PublicationRepository,
|
|
ReportRepository,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_repos():
|
|
"""Create mock repositories and config manager."""
|
|
return {
|
|
"candidate_repo": MagicMock(spec=CandidateRepository),
|
|
"artifact_repo": MagicMock(spec=ArtifactRepository),
|
|
"manifest_repo": MagicMock(spec=ManifestRepository),
|
|
"policy_repo": MagicMock(spec=PolicyRepository),
|
|
"compliance_repo": MagicMock(spec=ComplianceRepository),
|
|
"report_repo": MagicMock(spec=ReportRepository),
|
|
"approval_repo": MagicMock(spec=ApprovalRepository),
|
|
"publication_repo": MagicMock(spec=PublicationRepository),
|
|
"audit_repo": MagicMock(spec=AuditRepository),
|
|
"config_manager": MagicMock(),
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def facade(mock_repos):
|
|
"""Create CleanReleaseFacade with mock repos."""
|
|
return CleanReleaseFacade(**mock_repos)
|
|
|
|
|
|
class TestResolveActivePolicySnapshot:
|
|
"""resolve_active_policy_snapshot — policy resolution from config."""
|
|
|
|
# #region test_resolve_policy_found [C:2] [TYPE Function]
|
|
def test_resolve_policy_found(self, facade, mock_repos):
|
|
"""Active policy_id found → returns snapshot."""
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = "policy-1"
|
|
mock_repos["policy_repo"].get_policy_snapshot.return_value = MagicMock(spec=CleanPolicySnapshot)
|
|
result = facade.resolve_active_policy_snapshot()
|
|
assert result is not None
|
|
mock_repos["policy_repo"].get_policy_snapshot.assert_called_with("policy-1")
|
|
# #endregion test_resolve_policy_found
|
|
|
|
# #region test_resolve_policy_missing_id [C:2] [TYPE Function]
|
|
def test_resolve_policy_missing_id(self, facade, mock_repos):
|
|
"""No active_policy_id → returns None."""
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
|
|
result = facade.resolve_active_policy_snapshot()
|
|
assert result is None
|
|
# #endregion test_resolve_policy_missing_id
|
|
|
|
|
|
class TestResolveActiveRegistrySnapshot:
|
|
"""resolve_active_registry_snapshot — registry resolution from config."""
|
|
|
|
# #region test_resolve_registry_found [C:2] [TYPE Function]
|
|
def test_resolve_registry_found(self, facade, mock_repos):
|
|
"""Active registry_id found → returns snapshot."""
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = "reg-1"
|
|
mock_repos["policy_repo"].get_registry_snapshot.return_value = MagicMock(spec=SourceRegistrySnapshot)
|
|
result = facade.resolve_active_registry_snapshot()
|
|
assert result is not None
|
|
mock_repos["policy_repo"].get_registry_snapshot.assert_called_with("reg-1")
|
|
# #endregion test_resolve_registry_found
|
|
|
|
# #region test_resolve_registry_missing_id [C:2] [TYPE Function]
|
|
def test_resolve_registry_missing_id(self, facade, mock_repos):
|
|
"""No active_registry_id → returns None."""
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None
|
|
result = facade.resolve_active_registry_snapshot()
|
|
assert result is None
|
|
# #endregion test_resolve_registry_missing_id
|
|
|
|
|
|
class TestGetCandidateOverview:
|
|
"""get_candidate_overview — comprehensive candidate overview."""
|
|
|
|
def _make_candidate(self, **kwargs):
|
|
"""Create a candidate mock with proper attribute values."""
|
|
c = MagicMock()
|
|
for k, v in kwargs.items():
|
|
setattr(c, k, v)
|
|
return c
|
|
|
|
# #region test_get_overview_missing_candidate [C:2] [TYPE Function]
|
|
def test_get_overview_missing_candidate(self, facade, mock_repos):
|
|
"""Candidate not found → returns None."""
|
|
mock_repos["candidate_repo"].get_by_id.return_value = None
|
|
result = facade.get_candidate_overview("nonexistent")
|
|
assert result is None
|
|
# #endregion test_get_overview_missing_candidate
|
|
|
|
# #region test_get_overview_full [C:2] [TYPE Function]
|
|
def test_get_overview_full(self, facade, mock_repos):
|
|
"""Full candidate overview with all data."""
|
|
candidate = self._make_candidate(
|
|
id="cand-1", version="1.0.0",
|
|
source_snapshot_ref="git:sha", status=CandidateStatus.APPROVED.value,
|
|
)
|
|
|
|
manifest = self._make_candidate(
|
|
id="man-1", manifest_digest="digest",
|
|
)
|
|
|
|
run = self._make_candidate(
|
|
id="run-1", status=RunStatus.SUCCEEDED.value,
|
|
)
|
|
|
|
report = self._make_candidate(
|
|
id="CCR-1", final_status=ComplianceDecision.PASSED.value,
|
|
)
|
|
|
|
approval = self._make_candidate(decision="APPROVED")
|
|
|
|
publication = self._make_candidate(id="pub-1", status="ACTIVE")
|
|
|
|
policy = MagicMock(spec=CleanPolicySnapshot)
|
|
policy.id = "policy-1"
|
|
policy.policy_version = "1.0"
|
|
|
|
registry = MagicMock(spec=SourceRegistrySnapshot)
|
|
registry.id = "reg-1"
|
|
registry.registry_version = "1.0"
|
|
|
|
mock_repos["candidate_repo"].get_by_id.return_value = candidate
|
|
mock_repos["manifest_repo"].get_latest_for_candidate.return_value = manifest
|
|
mock_repos["compliance_repo"].list_runs_by_candidate.return_value = [run]
|
|
mock_repos["report_repo"].get_by_run.return_value = report
|
|
mock_repos["approval_repo"].get_latest_for_candidate.return_value = approval
|
|
mock_repos["publication_repo"].get_latest_for_candidate.return_value = publication
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = "policy-1"
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = "reg-1"
|
|
mock_repos["policy_repo"].get_policy_snapshot.return_value = policy
|
|
mock_repos["policy_repo"].get_registry_snapshot.return_value = registry
|
|
|
|
result = facade.get_candidate_overview("cand-1")
|
|
assert isinstance(result, CandidateOverviewDTO)
|
|
assert result.candidate_id == "cand-1"
|
|
assert result.latest_manifest_id == "man-1"
|
|
assert result.latest_run_status == RunStatus.SUCCEEDED
|
|
assert result.latest_report_final_status == ComplianceDecision.PASSED
|
|
assert result.latest_approval_decision == "APPROVED"
|
|
# #endregion test_get_overview_full
|
|
|
|
# #region test_get_overview_no_runs [C:2] [TYPE Function]
|
|
def test_get_overview_no_runs(self, facade, mock_repos):
|
|
"""Candidate without runs → report/run fields are None."""
|
|
candidate = self._make_candidate(
|
|
id="cand-1", version="1.0.0",
|
|
source_snapshot_ref="git:sha", status=CandidateStatus.DRAFT.value,
|
|
)
|
|
mock_repos["candidate_repo"].get_by_id.return_value = candidate
|
|
mock_repos["manifest_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["compliance_repo"].list_runs_by_candidate.return_value = []
|
|
mock_repos["approval_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["publication_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None
|
|
|
|
result = facade.get_candidate_overview("cand-1")
|
|
assert result.latest_manifest_id is None
|
|
assert result.latest_run_id is None
|
|
assert result.latest_report_id is None
|
|
assert result.latest_approval_decision is None
|
|
# #endregion test_get_overview_no_runs
|
|
|
|
|
|
class TestListCandidates:
|
|
"""list_candidates — list all candidates with overviews."""
|
|
|
|
# #region test_list_candidates_empty [C:2] [TYPE Function]
|
|
def test_list_candidates_empty(self, facade, mock_repos):
|
|
"""No candidates → returns empty list."""
|
|
mock_repos["candidate_repo"].list_all.return_value = []
|
|
result = facade.list_candidates()
|
|
assert result == []
|
|
# #endregion test_list_candidates_empty
|
|
|
|
# #region test_list_candidates_with_data [C:2] [TYPE Function]
|
|
def test_list_candidates_with_data(self, facade, mock_repos):
|
|
"""Candidates exist → returns list of overviews."""
|
|
candidate = MagicMock()
|
|
candidate.id = "cand-1"
|
|
candidate.version = "1.0.0"
|
|
candidate.source_snapshot_ref = "ref"
|
|
candidate.status = CandidateStatus.DRAFT.value
|
|
|
|
mock_repos["candidate_repo"].list_all.return_value = [candidate]
|
|
mock_repos["candidate_repo"].get_by_id.return_value = candidate
|
|
mock_repos["manifest_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["compliance_repo"].list_runs_by_candidate.return_value = []
|
|
mock_repos["approval_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["publication_repo"].get_latest_for_candidate.return_value = None
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_policy_id = None
|
|
mock_repos["config_manager"].get_config.return_value.settings.clean_release.active_registry_id = None
|
|
|
|
result = facade.list_candidates()
|
|
assert len(result) == 1
|
|
assert result[0].candidate_id == "cand-1"
|
|
# #endregion test_list_candidates_with_data
|