# #region Test.CleanRelease.CandidateService [C:3] [TYPE Module] [SEMANTICS test,clean-release,candidate,service] # @BRIEF Unit tests for candidate_service — _validate_artifacts, register_candidate. # @RELATION BINDS_TO -> [candidate_service] # @TEST_EDGE: empty_artifacts -> ValueError # @TEST_EDGE: missing_id_field -> ValueError # @TEST_EDGE: empty_id -> ValueError # @TEST_EDGE: non_positive_size -> ValueError # @TEST_EDGE: duplicate_candidate -> ValueError from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src")) from unittest.mock import MagicMock import pytest # ── _validate_artifacts ── class TestValidateArtifacts: def test_valid_artifacts(self): from src.services.clean_release.candidate_service import _validate_artifacts artifacts = [ {"id": "art-1", "path": "/tmp/a.tar.gz", "sha256": "abc123", "size": 1024}, ] result = _validate_artifacts(artifacts) assert len(result) == 1 assert result[0]["id"] == "art-1" def test_empty_artifacts(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="artifacts must not be empty"): _validate_artifacts([]) def test_non_dict_artifact(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="artifact\\[0\\] must be an object"): _validate_artifacts(["string"]) def test_missing_required_field(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="missing required field 'path'"): _validate_artifacts([{"id": "a1", "sha256": "s", "size": 1}]) def test_empty_id(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="field 'id' must be non-empty"): _validate_artifacts([{"id": "", "path": "/p", "sha256": "s", "size": 1}]) def test_empty_path(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="field 'path' must be non-empty"): _validate_artifacts([{"id": "a1", "path": "", "sha256": "s", "size": 1}]) def test_empty_sha256(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="field 'sha256' must be non-empty"): _validate_artifacts([{"id": "a1", "path": "/p", "sha256": "", "size": 1}]) def test_non_positive_size(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="field 'size' must be a positive integer"): _validate_artifacts([{"id": "a1", "path": "/p", "sha256": "s", "size": 0}]) def test_size_not_int(self): from src.services.clean_release.candidate_service import _validate_artifacts with pytest.raises(ValueError, match="field 'size' must be a positive integer"): _validate_artifacts([{"id": "a1", "path": "/p", "sha256": "s", "size": "not-int"}]) # ── register_candidate ── class TestRegisterCandidateService: def test_register_success(self): repo = MagicMock() repo.get_candidate.return_value = None from src.services.clean_release.candidate_service import register_candidate result = register_candidate( repository=repo, candidate_id="cand-1", version="1.0.0", source_snapshot_ref="main", created_by="admin", artifacts=[{"id": "a1", "path": "/p", "sha256": "s", "size": 1}], ) assert result is not None assert result.id == "cand-1" assert repo.save_candidate.call_count == 2 # DRAFT then PREPARED assert repo.save_artifact.call_count == 1 def test_register_empty_candidate_id(self): repo = MagicMock() from src.services.clean_release.candidate_service import register_candidate with pytest.raises(ValueError, match="candidate_id must be non-empty"): register_candidate(repo, "", "1.0", "main", "admin", []) def test_register_empty_version(self): repo = MagicMock() from src.services.clean_release.candidate_service import register_candidate with pytest.raises(ValueError, match="version must be non-empty"): register_candidate(repo, "cand-1", "", "main", "admin", []) def test_register_empty_source_ref(self): repo = MagicMock() from src.services.clean_release.candidate_service import register_candidate with pytest.raises(ValueError, match="source_snapshot_ref must be non-empty"): register_candidate(repo, "cand-1", "1.0", "", "admin", []) def test_register_empty_created_by(self): repo = MagicMock() from src.services.clean_release.candidate_service import register_candidate with pytest.raises(ValueError, match="created_by must be non-empty"): register_candidate(repo, "cand-1", "1.0", "main", "", []) def test_register_duplicate(self): repo = MagicMock() repo.get_candidate.return_value = MagicMock(id="cand-1") from src.services.clean_release.candidate_service import register_candidate with pytest.raises(ValueError, match="already exists"): register_candidate(repo, "cand-1", "1.0", "main", "admin", [{"id": "a1", "path": "/p", "sha256": "s", "size": 1}]) # #endregion Test.CleanRelease.CandidateService