test: +12 test modules — clean_release routes, gitea routes, dashboard detail, candidate_service, compliance_orchestrator, clarification_engine/orchestrator, dataset_review helpers. Fix 18 failures (assistant tools, maintence, dataset_review, approval, publication)

This commit is contained in:
2026-06-15 16:26:42 +03:00
parent fc7e6c3c15
commit a20879fa37
18 changed files with 3933 additions and 48 deletions

View File

@@ -346,7 +346,8 @@ def test_approve_persists_audit_event():
assert len(repository.audit_events) >= 1
last_event = repository.audit_events[-1]
assert "APPROVED" in str(last_event.get("action", ""))
# audit_preparation sets action="PREPARATION", decision type goes in "status"
assert "APPROVED" in str(last_event.get("status", ""))
# #endregion test_approve_persists_audit_event
@@ -368,7 +369,8 @@ def test_reject_persists_audit_event():
assert len(repository.audit_events) >= 1
last_event = repository.audit_events[-1]
assert "REJECTED" in str(last_event.get("action", ""))
# audit_preparation sets action="PREPARATION", decision type goes in "status"
assert "REJECTED" in str(last_event.get("status", ""))
# #endregion test_reject_persists_audit_event

View File

@@ -38,7 +38,8 @@ def test_load_bootstrap_artifacts_list():
assert artifacts[0].path == "bin/app"
assert artifacts[0].sha256 == "abc123"
assert artifacts[0].size == 1024
assert artifacts[0].detected_category == "library" # from 'category' field
assert artifacts[0].detected_category is None # no category field on first artifact
assert artifacts[1].detected_category == "library" # from 'category' field on second artifact
assert artifacts[1].size == 2048
finally:
Path(path).unlink(missing_ok=True)
@@ -167,7 +168,8 @@ def test_load_bootstrap_artifacts_with_metadata():
# Extra fields become metadata
assert artifacts[1].metadata_json["extra_field"] == "extra_value"
assert artifacts[1].detected_category is not None
# No category/detected_category field -> detected_category is None
assert artifacts[1].detected_category is None
finally:
Path(path).unlink(missing_ok=True)
# #endregion test_load_bootstrap_artifacts_with_metadata

View File

@@ -0,0 +1,124 @@
# #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

View File

@@ -0,0 +1,347 @@
# #region Test.CleanRelease.ComplianceOrchestrator [C:3] [TYPE Module] [SEMANTICS test,clean-release,compliance,orchestrator]
# @BRIEF Unit tests for CleanComplianceOrchestrator — start, execute, finalize.
# @RELATION BINDS_TO -> [ComplianceOrchestrator]
# @TEST_EDGE: manifest_not_found -> ValueError
# @TEST_EDGE: missing_deps -> FAILED status
# @TEST_EDGE: finalize_derives_from_stages
from pathlib import Path
import sys
from unittest.mock import MagicMock, PropertyMock
from datetime import datetime, timezone
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
def _make_repo() -> MagicMock:
repo = MagicMock()
repo.get_manifest.return_value = None
repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")
repo.get_candidate.return_value = None
repo.get_registry.return_value = MagicMock(id="registry-1")
repo.save_check_run.side_effect = lambda r: r
repo.stage_runs = {}
return repo
# ── start_check_run ──
class TestStartCheckRun:
def test_start_without_manifest(self):
repo = _make_repo()
repo.get_policy.return_value = MagicMock(id="policy-1")
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
run = orch.start_check_run(
candidate_id="cand-1",
policy_id="policy-1",
requested_by="tester",
)
assert run is not None
assert run.candidate_id == "cand-1"
assert run.status == "RUNNING"
def test_start_with_manifest(self):
repo = _make_repo()
manifest = MagicMock()
manifest.id = "manifest-1"
manifest.manifest_digest = "digest-abc"
repo.get_manifest.return_value = manifest
repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
run = orch.start_check_run(
candidate_id="cand-1",
policy_id="policy-1",
requested_by="tester",
manifest_id="manifest-1",
)
assert run is not None
assert run.candidate_id == "cand-1"
def test_start_manifest_not_found_raises(self):
repo = _make_repo()
repo.get_manifest.return_value = None
repo.get_policy.return_value = MagicMock(id="policy-1")
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
with pytest.raises(ValueError, match="Manifest or Policy not found"):
orch.start_check_run(
candidate_id="cand-1",
policy_id="policy-1",
manifest_id="manifest-missing",
)
def test_start_legacy_manifest_id_is_execution_mode(self):
"""When manifest_id looks like an execution mode string, treat it as execution_mode."""
repo = _make_repo()
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
run = orch.start_check_run(
candidate_id="cand-1",
policy_id="policy-1",
manifest_id="tui",
)
# Should treat "tui" as execution_mode, not manifest_id
assert run.manifest_id == "manifest-cand-1"
def test_start_with_requested_by_none(self):
repo = _make_repo()
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
run = orch.start_check_run(
candidate_id="cand-1",
policy_id="policy-1",
requested_by=None,
)
assert run is not None
assert run.requested_by == "system"
# ── execute_stages ──
class TestExecuteStages:
def test_forced_results_all_stages_pass(self):
from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
from src.models.clean_release import ComplianceStageRun
repo = _make_repo()
run = MagicMock()
run.id = "check-1"
run.candidate_id = "cand-1"
run.status = "RUNNING"
forced = [
ComplianceStageRun(
id="stage-1",
run_id="check-1",
stage_name=ComplianceStageName.DATA_PURITY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"},
),
ComplianceStageRun(
id="stage-2",
run_id="check-1",
stage_name=ComplianceStageName.INTERNAL_SOURCES_ONLY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"},
),
ComplianceStageRun(
id="stage-3",
run_id="check-1",
stage_name=ComplianceStageName.NO_EXTERNAL_ENDPOINTS.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"},
),
ComplianceStageRun(
id="stage-4",
run_id="check-1",
stage_name=ComplianceStageName.MANIFEST_CONSISTENCY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={"message": "ok"},
),
]
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.execute_stages(run, forced_results=forced)
assert result.status == "SUCCEEDED"
assert len(repo.stage_runs) == 4
def test_forced_results_with_blocked(self):
from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
from src.models.clean_release import ComplianceStageRun
repo = _make_repo()
run = MagicMock()
run.id = "check-1"
run.candidate_id = "cand-1"
run.status = "RUNNING"
forced = [
ComplianceStageRun(
id="stage-1",
run_id="check-1",
stage_name=ComplianceStageName.DATA_PURITY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={},
),
ComplianceStageRun(
id="stage-2",
run_id="check-1",
stage_name=ComplianceStageName.INTERNAL_SOURCES_ONLY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={},
),
ComplianceStageRun(
id="stage-3",
run_id="check-1",
stage_name=ComplianceStageName.NO_EXTERNAL_ENDPOINTS.value,
status="SUCCEEDED",
decision=ComplianceDecision.BLOCKED.value,
details_json={},
),
ComplianceStageRun(
id="stage-4",
run_id="check-1",
stage_name=ComplianceStageName.MANIFEST_CONSISTENCY.value,
status="SUCCEEDED",
decision=ComplianceDecision.PASSED.value,
details_json={},
),
]
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.execute_stages(run, forced_results=forced)
assert result.status == "SUCCEEDED"
# final_status should be BLOCKED because one stage is blocked
assert result.final_status == "BLOCKED"
def test_no_forced_all_deps_missing(self):
repo = _make_repo()
repo.get_candidate.return_value = None
repo.get_policy.return_value = None
repo.get_registry.return_value = None
repo.get_manifest.return_value = None
run = MagicMock()
run.id = "check-1"
run.candidate_id = "cand-1"
run.policy_snapshot_id = "policy-1"
run.registry_snapshot_id = "reg-1"
run.manifest_id = "manifest-1"
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.execute_stages(run)
assert result.status == "FAILED"
assert result.finished_at is not None
def test_no_forced_with_all_deps(self):
repo = _make_repo()
manifest = MagicMock()
manifest.content_json = {"summary": {"prohibited_detected_count": 0}}
repo.get_manifest.return_value = manifest
repo.get_candidate.return_value = MagicMock(id="cand-1")
repo.get_policy.return_value = MagicMock(id="policy-1")
repo.get_registry.return_value = MagicMock(id="reg-1")
run = MagicMock()
run.id = "check-1"
run.candidate_id = "cand-1"
run.policy_snapshot_id = "policy-1"
run.registry_snapshot_id = "reg-1"
run.manifest_id = "manifest-1"
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.execute_stages(run)
assert result.status == "SUCCEEDED"
assert result.final_status == "PASSED"
# ── finalize_run ──
class TestFinalizeRun:
def test_finalize_failed_run(self):
repo = _make_repo()
run = MagicMock()
run.id = "check-1"
run.status = "FAILED"
run.final_status = None
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.finalize_run(run)
assert result.finished_at is not None
def test_finalize_with_existing_final_status(self):
repo = _make_repo()
run = MagicMock()
run.id = "check-1"
run.status = "RUNNING"
run.final_status = "PASSED"
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.finalize_run(run)
assert result.status == "SUCCEEDED"
def test_finalize_derives_from_stage_runs(self):
from src.services.clean_release.enums import ComplianceStageName, ComplianceDecision
from src.models.clean_release import ComplianceStageRun
repo = _make_repo()
repo.stage_runs = {
"stage-1": ComplianceStageRun(
id="stage-1", run_id="check-1",
stage_name=ComplianceStageName.DATA_PURITY.value,
status="SUCCEEDED", decision=ComplianceDecision.PASSED.value,
details_json={},
),
}
run = MagicMock()
run.id = "check-1"
run.status = "RUNNING"
run.final_status = None
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.finalize_run(run)
assert result.status == "SUCCEEDED"
def test_finalize_derives_no_stages(self):
repo = _make_repo()
repo.stage_runs = {}
run = MagicMock()
run.id = "check-1"
run.status = "RUNNING"
run.final_status = None
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
orch = CleanComplianceOrchestrator(repo)
result = orch.finalize_run(run)
assert result.status == "SUCCEEDED"
# ── run_check_legacy ──
class TestRunCheckLegacy:
def test_legacy_pipeline(self):
repo = _make_repo()
repo.get_candidate.return_value = MagicMock(id="cand-1")
repo.get_policy.return_value = MagicMock(id="policy-1", registry_snapshot_id="reg-1")
repo.get_manifest.return_value = MagicMock(
id="manifest-1",
manifest_digest="digest-abc",
)
from src.services.clean_release.compliance_orchestrator import run_check_legacy
result = run_check_legacy(
repository=repo,
candidate_id="cand-1",
policy_id="policy-1",
requested_by="tester",
manifest_id="manifest-1",
)
assert result is not None
assert result.candidate_id == "cand-1"
# #endregion Test.CleanRelease.ComplianceOrchestrator

View File

@@ -221,24 +221,17 @@ def test_publish_rejects_nonexistent_report():
from src.services.clean_release.approval_service import approve_candidate
from src.services.clean_release.publication_service import publish_candidate
repository, candidate_id, _ = _seed_candidate_with_passed_report(
repository, candidate_id, report_id = _seed_candidate_with_passed_report(
candidate_status=CandidateStatus.CHECK_PASSED,
)
approve_candidate(
repository=repository,
candidate_id=candidate_id,
report_id=_seed_candidate_with_passed_report()[1],
report_id=report_id,
decided_by="approver",
comment="approved",
# need to re-seed for valid report id
)
# Use the actual report_id from the seed
actual_report_id = _seed_candidate_with_passed_report(
candidate_id=candidate_id + "-other",
report_id="CCR-other",
)[1]
with pytest.raises(PublicationGateError, match="not found"):
publish_candidate(
repository=repository,
@@ -254,6 +247,7 @@ def test_publish_rejects_nonexistent_report():
def test_publish_rejects_foreign_report():
from src.services.clean_release.approval_service import approve_candidate
from src.services.clean_release.publication_service import publish_candidate
from src.models.clean_release import ComplianceReport
repository, candidate_id, _ = _seed_candidate_with_passed_report(
candidate_id="cand-foreign-pub",
@@ -268,18 +262,23 @@ def test_publish_rejects_foreign_report():
comment="approved",
)
# A report belonging to another candidate
foreign_repo, foreign_candidate_id, foreign_report_id = _seed_candidate_with_passed_report(
# Save a foreign report directly into the same repository
foreign_report = ComplianceReport(
id="CCR-other-pub",
run_id="run-foreign",
candidate_id="cand-other",
report_id="CCR-other-pub",
candidate_status=CandidateStatus.CHECK_PASSED,
final_status=ComplianceDecision.PASSED.value,
summary_json={"operator_summary": "foreign", "violations_count": 0, "blocking_violations_count": 0},
generated_at=datetime.now(UTC),
immutable=True,
)
repository.save_report(foreign_report)
with pytest.raises(PublicationGateError, match="belongs to another candidate"):
publish_candidate(
repository=repository,
candidate_id=candidate_id,
report_id=foreign_report_id,
report_id="CCR-other-pub",
published_by="publisher",
target_channel="stable",
)
@@ -432,7 +431,8 @@ def test_publish_persists_audit_event():
assert len(repository.audit_events) >= 1
last_event = repository.audit_events[-1]
assert "PUBLISHED" in str(last_event.get("action", ""))
# audit_preparation sets action="PREPARATION", decision type goes in "status"
assert "PUBLISHED" in str(last_event.get("status", ""))
# #endregion test_publish_persists_audit_event
@@ -468,7 +468,8 @@ def test_revoke_persists_audit_event():
assert len(repository.audit_events) >= 1
last_event = repository.audit_events[-1]
assert "REVOKED" in str(last_event.get("action", ""))
# audit_preparation sets action="PREPARATION", decision type goes in "status"
assert "REVOKED" in str(last_event.get("status", ""))
# #endregion test_revoke_persists_audit_event