# #region test_clean_release_cli [C:2] [TYPE Module] # @RELATION BINDS_TO -> SrcRoot # @PURPOSE: Smoke tests for the redesigned clean release CLI. # @LAYER Domain """Smoke tests for the redesigned clean release CLI commands.""" from datetime import UTC, datetime from types import SimpleNamespace from uuid import uuid4 from src.dependencies import get_clean_release_repository, get_config_manager from src.models.clean_release import ( CleanPolicySnapshot, ComplianceReport, ReleaseCandidate, SourceRegistrySnapshot, ) from src.scripts.clean_release_cli import main as cli_main from src.services.clean_release.enums import CandidateStatus, ComplianceDecision # #region test_cli_candidate_register_scaffold [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify candidate-register command exits successfully for valid required arguments. def test_cli_candidate_register_scaffold() -> None: """Candidate register CLI command smoke test.""" exit_code = cli_main( [ "candidate-register", "--candidate-id", "cli-candidate-1", "--version", "1.0.0", "--source-snapshot-ref", "git:sha123", "--created-by", "cli-test", ] ) assert exit_code == 0 # #endregion test_cli_candidate_register_scaffold # #region test_cli_manifest_build_scaffold [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify candidate-register/artifact-import/manifest-build smoke path succeeds end-to-end. def test_cli_manifest_build_scaffold() -> None: """Manifest build CLI command smoke test.""" register_exit = cli_main( [ "candidate-register", "--candidate-id", "cli-candidate-2", "--version", "1.0.0", "--source-snapshot-ref", "git:sha234", "--created-by", "cli-test", ] ) assert register_exit == 0 import_exit = cli_main( [ "artifact-import", "--candidate-id", "cli-candidate-2", "--artifact-id", "artifact-2", "--path", "bin/app", "--sha256", "feedbeef", "--size", "24", ] ) assert import_exit == 0 manifest_exit = cli_main( [ "manifest-build", "--candidate-id", "cli-candidate-2", "--created-by", "cli-test", ] ) assert manifest_exit == 0 # #endregion test_cli_manifest_build_scaffold # #region test_cli_compliance_run_scaffold [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify compliance run/status/violations/report commands complete for prepared candidate. def test_cli_compliance_run_scaffold() -> None: """Compliance CLI command smoke test for run/status/report/violations.""" from unittest.mock import MagicMock, patch # get_config_manager() tries to open project_root/config.json which is a # directory, not a file. Mock the entire ConfigManager class so that both # the test's direct call and the CLI's internal lazy import use the mock. with patch("src.dependencies.ConfigManager") as MockConfigMgr: mock_cfg_mgr = MagicMock() mock_cfg = MagicMock() mock_cfg.settings = SimpleNamespace() mock_cfg_mgr.get_config.return_value = mock_cfg MockConfigMgr.return_value = mock_cfg_mgr repository = get_clean_release_repository() config_manager = get_config_manager() registry = SourceRegistrySnapshot( id="cli-registry", registry_id="trusted-registry", registry_version="1.0.0", allowed_hosts=["repo.internal.local"], allowed_schemes=["https"], allowed_source_types=["repo"], immutable=True, ) policy = CleanPolicySnapshot( id="cli-policy", policy_id="trusted-policy", policy_version="1.0.0", content_json={"rules": []}, registry_snapshot_id=registry.id, immutable=True, ) repository.save_registry(registry) repository.save_policy(policy) config = config_manager.get_config() if getattr(config, "settings", None) is None: # @INVARIANT: SimpleNamespace substitutes for GlobalSettings — any field rename in GlobalSettings will silently not propagate here; re-verify on GlobalSettings schema changes. config.settings = SimpleNamespace() config.settings.clean_release = SimpleNamespace( active_policy_id=policy.id, active_registry_id=registry.id, ) register_exit = cli_main( [ "candidate-register", "--candidate-id", "cli-candidate-3", "--version", "1.0.0", "--source-snapshot-ref", "git:sha345", "--created-by", "cli-test", ] ) assert register_exit == 0 import_exit = cli_main( [ "artifact-import", "--candidate-id", "cli-candidate-3", "--artifact-id", "artifact-1", "--path", "bin/app", "--sha256", "deadbeef", "--size", "42", ] ) assert import_exit == 0 manifest_exit = cli_main( [ "manifest-build", "--candidate-id", "cli-candidate-3", "--created-by", "cli-test", ] ) assert manifest_exit == 0 run_exit = cli_main( [ "compliance-run", "--candidate-id", "cli-candidate-3", "--actor", "cli-test", "--json", ] ) assert run_exit == 0 run_id = next( run.id for run in repository.check_runs.values() if run.candidate_id == "cli-candidate-3" ) status_exit = cli_main(["compliance-status", "--run-id", run_id, "--json"]) assert status_exit == 0 violations_exit = cli_main(["compliance-violations", "--run-id", run_id, "--json"]) assert violations_exit == 0 report_exit = cli_main(["compliance-report", "--run-id", run_id, "--json"]) assert report_exit == 0 # #endregion test_cli_compliance_run_scaffold # #region test_cli_release_gate_commands_scaffold [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify approve/reject/publish/revoke release-gate commands execute with valid fixtures. def test_cli_release_gate_commands_scaffold() -> None: """Release gate CLI smoke test for approve/reject/publish/revoke commands.""" repository = get_clean_release_repository() approved_candidate_id = f"cli-release-approved-{uuid4()}" rejected_candidate_id = f"cli-release-rejected-{uuid4()}" approved_report_id = f"CCR-cli-release-approved-{uuid4()}" rejected_report_id = f"CCR-cli-release-rejected-{uuid4()}" repository.save_candidate( ReleaseCandidate( id=approved_candidate_id, version="1.0.0", source_snapshot_ref="git:sha-approved", created_by="cli-test", created_at=datetime.now(UTC), status=CandidateStatus.CHECK_PASSED.value, ) ) repository.save_candidate( ReleaseCandidate( id=rejected_candidate_id, version="1.0.0", source_snapshot_ref="git:sha-rejected", created_by="cli-test", created_at=datetime.now(UTC), status=CandidateStatus.CHECK_PASSED.value, ) ) repository.save_report( ComplianceReport( id=approved_report_id, run_id=f"run-{uuid4()}", candidate_id=approved_candidate_id, final_status=ComplianceDecision.PASSED.value, summary_json={ "operator_summary": "ok", "violations_count": 0, "blocking_violations_count": 0, }, generated_at=datetime.now(UTC), immutable=True, ) ) repository.save_report( ComplianceReport( id=rejected_report_id, run_id=f"run-{uuid4()}", candidate_id=rejected_candidate_id, final_status=ComplianceDecision.PASSED.value, summary_json={ "operator_summary": "ok", "violations_count": 0, "blocking_violations_count": 0, }, generated_at=datetime.now(UTC), immutable=True, ) ) approve_exit = cli_main( [ "approve", "--candidate-id", approved_candidate_id, "--report-id", approved_report_id, "--actor", "cli-test", "--comment", "approve candidate", "--json", ] ) assert approve_exit == 0 reject_exit = cli_main( [ "reject", "--candidate-id", rejected_candidate_id, "--report-id", rejected_report_id, "--actor", "cli-test", "--comment", "reject candidate", "--json", ] ) assert reject_exit == 0 publish_exit = cli_main( [ "publish", "--candidate-id", approved_candidate_id, "--report-id", approved_report_id, "--actor", "cli-test", "--target-channel", "stable", "--publication-ref", "rel-cli-001", "--json", ] ) assert publish_exit == 0 publication_records = getattr(repository, "publication_records", []) assert publication_records publication_id = publication_records[-1].id revoke_exit = cli_main( [ "revoke", "--publication-id", publication_id, "--actor", "cli-test", "--comment", "rollback", "--json", ] ) assert revoke_exit == 0 # #endregion test_cli_release_gate_commands_scaffold # #region test_cli_error_branches [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify error-handling branches return expected exit codes and error messages. def test_cli_duplicate_candidate() -> None: """Registering an existing candidate returns exit code 1.""" cli_main(["candidate-register", "--candidate-id", "dup-cand", "--version", "1.0", "--source-snapshot-ref", "git:x"]) exit_code = cli_main(["candidate-register", "--candidate-id", "dup-cand", "--version", "2.0", "--source-snapshot-ref", "git:y"]) assert exit_code == 1 def test_cli_artifact_import_candidate_not_found() -> None: """Importing artifact for non-existent candidate returns exit code 1.""" exit_code = cli_main(["artifact-import", "--candidate-id", "no-such-cand", "--artifact-id", "a1", "--path", "x", "--sha256", "0" * 64, "--size", "1"]) assert exit_code == 1 def test_cli_manifest_build_value_error(monkeypatch) -> None: """Manifest build on unprepared candidate returns exit code 1.""" from src.dependencies import get_clean_release_repository repository = get_clean_release_repository() # Register candidate but skip artifact import — manifest build will fail cli_main(["candidate-register", "--candidate-id", "no-artifacts", "--version", "1.0", "--source-snapshot-ref", "git:x", "--created-by", "test"]) # Manually set status to DRAFT so manifest build fails candidate = repository.get_candidate("no-artifacts") if candidate: candidate.status = "DRAFT" repository.save_candidate(candidate) exit_code = cli_main(["manifest-build", "--candidate-id", "no-artifacts", "--created-by", "test"]) assert exit_code == 1 def test_cli_compliance_run_exception(monkeypatch) -> None: """Compliance run when config is broken returns exit code 2.""" from unittest.mock import patch, MagicMock import json with patch("src.dependencies.ConfigManager") as MockCfgMgr: mock_mgr = MagicMock() mock_mgr.get_config.side_effect = RuntimeError("config broken") MockCfgMgr.return_value = mock_mgr exit_code = cli_main(["compliance-run", "--candidate-id", "no-such", "--actor", "test", "--json"]) assert exit_code == 2 def test_cli_compliance_status_not_found() -> None: """Status query for non-existent run returns exit code 2.""" exit_code = cli_main(["compliance-status", "--run-id", "no-such-run", "--json"]) assert exit_code == 2 def test_cli_compliance_report_not_found() -> None: """Report query for non-existent run returns exit code 2.""" exit_code = cli_main(["compliance-report", "--run-id", "no-such-run", "--json"]) assert exit_code == 2 def test_cli_compliance_report_missing() -> None: """Report query when run exists but no report returns exit code 2.""" from src.dependencies import get_clean_release_repository from unittest.mock import MagicMock repository = get_clean_release_repository() run = MagicMock() run.id = "cli-run-no-report" run.candidate_id = "cli-cand-nr" run.status = "PENDING" run.final_status = "PENDING" repository.save_check_run(run) exit_code = cli_main(["compliance-report", "--run-id", "cli-run-no-report", "--json"]) assert exit_code == 2 def test_cli_compliance_violations_not_found() -> None: """Violations query for non-existent run returns exit code 2.""" exit_code = cli_main(["compliance-violations", "--run-id", "no-such-run", "--json"]) assert exit_code == 2 def test_cli_approve_exception(monkeypatch) -> None: """Approve with approval service failure returns exit code 2.""" from unittest.mock import patch with patch("src.services.clean_release.approval_service.approve_candidate", side_effect=RuntimeError("approval failed")): exit_code = cli_main(["approve", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--json"]) assert exit_code == 2 def test_cli_reject_exception(monkeypatch) -> None: """Reject with approval service failure returns exit code 2.""" from unittest.mock import patch with patch("src.services.clean_release.approval_service.reject_candidate", side_effect=RuntimeError("reject failed")): exit_code = cli_main(["reject", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--json"]) assert exit_code == 2 def test_cli_publish_exception(monkeypatch) -> None: """Publish with publication service failure returns exit code 2.""" from unittest.mock import patch with patch("src.services.clean_release.publication_service.publish_candidate", side_effect=RuntimeError("publish failed")): exit_code = cli_main(["publish", "--candidate-id", "x", "--report-id", "y", "--actor", "test", "--target-channel", "stable", "--json"]) assert exit_code == 2 def test_cli_revoke_exception(monkeypatch) -> None: """Revoke with publication service failure returns exit code 2.""" from unittest.mock import patch with patch("src.services.clean_release.publication_service.revoke_publication", side_effect=RuntimeError("revoke failed")): exit_code = cli_main(["revoke", "--publication-id", "x", "--actor", "test", "--json"]) assert exit_code == 2 def test_cli_unknown_command() -> None: """Unknown command raises SystemExit with code 2.""" import pytest with pytest.raises(SystemExit) as exc_info: cli_main(["bogus-command"]) assert exc_info.value.code == 2 # #endregion test_cli_error_branches # #region test_to_payload [C:2] [TYPE Function] # @RELATION BINDS_TO -> test_clean_release_cli # @PURPOSE: Verify _to_payload serialization for datetime, date, dict, list, tuple, Pydantic, SQLAlchemy, and unsupported types. def test_to_payload_datetime() -> None: """_to_payload handles datetime.isoformat().""" from datetime import UTC, datetime, date from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock obj = MagicMock() obj.model_dump.return_value = {"ts": datetime(2024, 1, 15, 10, 30, 0, tzinfo=UTC)} result = _to_payload(obj) assert result["ts"] == "2024-01-15T10:30:00+00:00" def test_to_payload_date() -> None: """_to_payload handles date.isoformat().""" from datetime import date from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock obj = MagicMock() obj.model_dump.return_value = {"d": date(2024, 6, 15)} result = _to_payload(obj) assert result["d"] == "2024-06-15" def test_to_payload_dict_nested() -> None: """_to_payload recursively normalizes nested dicts.""" from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock obj = MagicMock() obj.model_dump.return_value = {"nested": {"key": 42, "flag": True}} result = _to_payload(obj) assert result["nested"] == {"key": 42, "flag": True} def test_to_payload_list() -> None: """_to_payload handles list items.""" from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock obj = MagicMock() obj.model_dump.return_value = {"items": ["a", "b", "c"]} result = _to_payload(obj) assert result["items"] == ["a", "b", "c"] def test_to_payload_tuple() -> None: """_to_payload converts tuple to list.""" from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock obj = MagicMock() obj.model_dump.return_value = {"coords": (1, 2)} result = _to_payload(obj) assert result["coords"] == [1, 2] def test_to_payload_sqlalchemy_model() -> None: """_to_payload handles SQLAlchemy model with __table__.""" from src.scripts.clean_release_cli import _to_payload from unittest.mock import MagicMock # Use a real class (not Mock) so model_dump is not auto-created class _FakeModel: pass col1 = MagicMock() col1.name = "id" col2 = MagicMock() col2.name = "name" _FakeModel.__table__ = MagicMock(columns=[col1, col2]) obj = _FakeModel() obj.id = 1 obj.name = "test" result = _to_payload(obj) assert result["id"] == 1 assert result["name"] == "test" def test_to_payload_unsupported_type() -> None: """_to_payload raises TypeError for unsupported types.""" from src.scripts.clean_release_cli import _to_payload import pytest with pytest.raises(TypeError, match="unsupported payload type"): _to_payload(42) # #endregion test_to_payload # #endregion test_clean_release_cli