Files
ss-tools/backend/tests/services/clean_release/test_candidate_service.py

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