Files
ss-tools/backend/tests/services/clean_release/test_facade.py
busya f75c15dbc6 test: massive coverage expansion — 15 new test modules + assistant tool fixes + orthogonal testing
- 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
2026-06-15 15:38:59 +03:00

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