125 lines
5.6 KiB
Python
125 lines
5.6 KiB
Python
# #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
|