fix: finalize semantic repair and test updates

This commit is contained in:
2026-03-21 15:07:06 +03:00
parent 005797334b
commit 9b47b9b667
99 changed files with 2484 additions and 985 deletions

View File

@@ -1,5 +1,5 @@
# [DEF:test_candidate_manifest_services:Module]
# @RELATION: BELONGS_TO -> SrcRoot
# @RELATION: BELONGS_TO -> [SrcRoot:Module]
# @COMPLEXITY: 3
# @PURPOSE: Test lifecycle and manifest versioning for release candidates.
# @LAYER: Tests
@@ -9,12 +9,17 @@ 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.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:")
@@ -24,8 +29,10 @@ def db_session():
yield session
session.close()
# [DEF:test_candidate_lifecycle_transitions:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify release candidate allows legal status transitions and rejects forbidden back-transitions.
def test_candidate_lifecycle_transitions(db_session):
"""
@PURPOSE: Verify legal state transitions for ReleaseCandidate.
@@ -36,7 +43,7 @@ def test_candidate_lifecycle_transitions(db_session):
version="1.0.0",
source_snapshot_ref="ref-1",
created_by="operator",
status=CandidateStatus.DRAFT
status=CandidateStatus.DRAFT,
)
db_session.add(candidate)
db_session.commit()
@@ -47,13 +54,17 @@ def test_candidate_lifecycle_transitions(db_session):
# 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_candidate_lifecycle_transitions:Function]
# [DEF:test_manifest_versioning_and_immutability:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify manifest versions increment monotonically and older snapshots remain queryable.
def test_manifest_versioning_and_immutability(db_session):
"""
@PURPOSE: Verify manifest versioning and immutability invariants.
@@ -70,7 +81,7 @@ def test_manifest_versioning_and_immutability(db_session):
source_snapshot_ref="ref1",
content_json={},
created_at=datetime.now(timezone.utc),
created_by="operator"
created_by="operator",
)
db_session.add(m1)
@@ -84,23 +95,34 @@ def test_manifest_versioning_and_immutability(db_session):
source_snapshot_ref="ref1",
content_json={},
created_at=datetime.now(timezone.utc),
created_by="operator"
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()
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()
all_manifests = (
db_session.query(DistributionManifest)
.filter_by(candidate_id=candidate_id)
.all()
)
assert len(all_manifests) == 2
# [/DEF:test_manifest_versioning_and_immutability:Function]
# [DEF:_valid_artifacts:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Provide canonical valid artifact payload used by candidate registration tests.
def _valid_artifacts():
return [
{
@@ -114,8 +136,10 @@ def _valid_artifacts():
# [/DEF:_valid_artifacts:Function]
# [DEF:test_register_candidate_rejects_duplicate_candidate_id:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify duplicate candidate_id registration is rejected by service invariants.
def test_register_candidate_rejects_duplicate_candidate_id():
repository = CleanReleaseRepository()
register_candidate(
@@ -140,8 +164,10 @@ def test_register_candidate_rejects_duplicate_candidate_id():
# [/DEF:test_register_candidate_rejects_duplicate_candidate_id:Function]
# [DEF:test_register_candidate_rejects_malformed_artifact_input:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify candidate registration rejects artifact payloads missing required fields.
def test_register_candidate_rejects_malformed_artifact_input():
repository = CleanReleaseRepository()
bad_artifacts = [{"id": "art-1", "path": "bin/app", "size": 42}] # missing sha256
@@ -159,8 +185,10 @@ def test_register_candidate_rejects_malformed_artifact_input():
# [/DEF:test_register_candidate_rejects_malformed_artifact_input:Function]
# [DEF:test_register_candidate_rejects_empty_artifact_set:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify candidate registration rejects empty artifact collections.
def test_register_candidate_rejects_empty_artifact_set():
repository = CleanReleaseRepository()
@@ -177,8 +205,10 @@ def test_register_candidate_rejects_empty_artifact_set():
# [/DEF:test_register_candidate_rejects_empty_artifact_set:Function]
# [DEF:test_manifest_service_rebuild_creates_new_version:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify repeated manifest build creates a new incremented immutable version.
def test_manifest_service_rebuild_creates_new_version():
repository = CleanReleaseRepository()
register_candidate(
@@ -190,8 +220,12 @@ def test_manifest_service_rebuild_creates_new_version():
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")
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
@@ -200,8 +234,10 @@ def test_manifest_service_rebuild_creates_new_version():
# [/DEF:test_manifest_service_rebuild_creates_new_version:Function]
# [DEF:test_manifest_service_existing_manifest_cannot_be_mutated:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify existing manifest snapshot remains immutable when rebuilding newer manifest version.
def test_manifest_service_existing_manifest_cannot_be_mutated():
repository = CleanReleaseRepository()
register_candidate(
@@ -213,10 +249,18 @@ def test_manifest_service_existing_manifest_cannot_be_mutated():
artifacts=_valid_artifacts(),
)
created = build_manifest_snapshot(repository=repository, candidate_id="manifest-immutable-1", created_by="operator")
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")
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
@@ -227,13 +271,20 @@ def test_manifest_service_existing_manifest_cannot_be_mutated():
# [/DEF:test_manifest_service_existing_manifest_cannot_be_mutated:Function]
# [DEF:test_manifest_service_rejects_missing_candidate:Function]
# @RELATION: BINDS_TO -> test_candidate_manifest_services
# @RELATION: BINDS_TO -> [test_candidate_manifest_services:Module]
# @PURPOSE: Verify manifest build fails with missing candidate identifier.
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")
build_manifest_snapshot(
repository=repository,
candidate_id="missing-candidate",
created_by="operator",
)
# [/DEF:test_candidate_manifest_services:Module]
# [/DEF:test_manifest_service_rejects_missing_candidate:Function]
# [/DEF:test_candidate_manifest_services:Module]