Files
ss-tools/backend/tests/services/clean_release/test_candidate_manifest_services.py
busya 274510fc38 refactor(semantics): migrate legacy @TIER to @COMPLEXITY annotations
- Replaced @TIER: TRIVIAL with @COMPLEXITY: 1
- Replaced @TIER: STANDARD with @COMPLEXITY: 3
- Replaced @TIER: CRITICAL with @COMPLEXITY: 5
- Manually elevated specific critical/complex components to levels 2 and 4
- Ignored legacy, specs, and node_modules directories
- Updated generated semantic map
2026-03-16 10:06:44 +03:00

204 lines
6.6 KiB
Python

# [DEF:test_candidate_manifest_services:Module]
# @COMPLEXITY: 3
# @PURPOSE: Test lifecycle and manifest versioning for release candidates.
# @LAYER: Tests
import pytest
from datetime import datetime, timezone
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from src.core.database import Base
from src.models.clean_release import ReleaseCandidate, DistributionManifest, CandidateArtifact
from src.services.clean_release.enums import CandidateStatus
from src.services.clean_release.candidate_service import register_candidate
from src.services.clean_release.manifest_service import build_manifest_snapshot
from src.services.clean_release.repository import CleanReleaseRepository
@pytest.fixture
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_candidate_lifecycle_transitions(db_session):
"""
@PURPOSE: Verify legal state transitions for ReleaseCandidate.
"""
candidate = ReleaseCandidate(
id="test-candidate-1",
name="Test Candidate",
version="1.0.0",
source_snapshot_ref="ref-1",
created_by="operator",
status=CandidateStatus.DRAFT
)
db_session.add(candidate)
db_session.commit()
# Valid transition: DRAFT -> PREPARED
candidate.transition_to(CandidateStatus.PREPARED)
assert candidate.status == CandidateStatus.PREPARED
# Invalid transition: PREPARED -> DRAFT (should raise IllegalTransitionError)
from src.services.clean_release.exceptions import IllegalTransitionError
with pytest.raises(IllegalTransitionError, match="Forbidden transition"):
candidate.transition_to(CandidateStatus.DRAFT)
def test_manifest_versioning_and_immutability(db_session):
"""
@PURPOSE: Verify manifest versioning and immutability invariants.
"""
candidate_id = "test-candidate-2"
# Create version 1
m1 = DistributionManifest(
id="manifest-v1",
candidate_id=candidate_id,
manifest_version=1,
manifest_digest="hash1",
artifacts_digest="hash1",
source_snapshot_ref="ref1",
content_json={},
created_at=datetime.now(timezone.utc),
created_by="operator"
)
db_session.add(m1)
# Create version 2
m2 = DistributionManifest(
id="manifest-v2",
candidate_id=candidate_id,
manifest_version=2,
manifest_digest="hash2",
artifacts_digest="hash2",
source_snapshot_ref="ref1",
content_json={},
created_at=datetime.now(timezone.utc),
created_by="operator"
)
db_session.add(m2)
db_session.commit()
latest = db_session.query(DistributionManifest).filter_by(candidate_id=candidate_id).order_by(DistributionManifest.manifest_version.desc()).first()
assert latest.manifest_version == 2
assert latest.id == "manifest-v2"
all_manifests = db_session.query(DistributionManifest).filter_by(candidate_id=candidate_id).all()
assert len(all_manifests) == 2
def _valid_artifacts():
return [
{
"id": "art-1",
"path": "bin/app",
"sha256": "abc123",
"size": 42,
}
]
def test_register_candidate_rejects_duplicate_candidate_id():
repository = CleanReleaseRepository()
register_candidate(
repository=repository,
candidate_id="dup-1",
version="1.0.0",
source_snapshot_ref="git:sha1",
created_by="operator",
artifacts=_valid_artifacts(),
)
with pytest.raises(ValueError, match="already exists"):
register_candidate(
repository=repository,
candidate_id="dup-1",
version="1.0.0",
source_snapshot_ref="git:sha1",
created_by="operator",
artifacts=_valid_artifacts(),
)
def test_register_candidate_rejects_malformed_artifact_input():
repository = CleanReleaseRepository()
bad_artifacts = [{"id": "art-1", "path": "bin/app", "size": 42}] # missing sha256
with pytest.raises(ValueError, match="missing required field 'sha256'"):
register_candidate(
repository=repository,
candidate_id="bad-art-1",
version="1.0.0",
source_snapshot_ref="git:sha2",
created_by="operator",
artifacts=bad_artifacts,
)
def test_register_candidate_rejects_empty_artifact_set():
repository = CleanReleaseRepository()
with pytest.raises(ValueError, match="artifacts must not be empty"):
register_candidate(
repository=repository,
candidate_id="empty-art-1",
version="1.0.0",
source_snapshot_ref="git:sha3",
created_by="operator",
artifacts=[],
)
def test_manifest_service_rebuild_creates_new_version():
repository = CleanReleaseRepository()
register_candidate(
repository=repository,
candidate_id="manifest-version-1",
version="1.0.0",
source_snapshot_ref="git:sha10",
created_by="operator",
artifacts=_valid_artifacts(),
)
first = build_manifest_snapshot(repository=repository, candidate_id="manifest-version-1", created_by="operator")
second = build_manifest_snapshot(repository=repository, candidate_id="manifest-version-1", created_by="operator")
assert first.manifest_version == 1
assert second.manifest_version == 2
assert first.id != second.id
def test_manifest_service_existing_manifest_cannot_be_mutated():
repository = CleanReleaseRepository()
register_candidate(
repository=repository,
candidate_id="manifest-immutable-1",
version="1.0.0",
source_snapshot_ref="git:sha11",
created_by="operator",
artifacts=_valid_artifacts(),
)
created = build_manifest_snapshot(repository=repository, candidate_id="manifest-immutable-1", created_by="operator")
original_digest = created.manifest_digest
rebuilt = build_manifest_snapshot(repository=repository, candidate_id="manifest-immutable-1", created_by="operator")
old_manifest = repository.get_manifest(created.id)
assert old_manifest is not None
assert old_manifest.manifest_digest == original_digest
assert old_manifest.id == created.id
assert rebuilt.id != created.id
def test_manifest_service_rejects_missing_candidate():
repository = CleanReleaseRepository()
with pytest.raises(ValueError, match="not found"):
build_manifest_snapshot(repository=repository, candidate_id="missing-candidate", created_by="operator")
# [/DEF:test_candidate_manifest_services:Module]