Warnings fixed: - datetime.utcnow() → datetime.now(UTC) across 48+ files (src/ + tests/) - datetime.utcnow (callback ref) → lambda: datetime.now(UTC) in model fields (18 files) - Pydantic class Config → model_config = ConfigDict(...) (16 files) - Pydantic .dict() → .model_dump() (8 files) - ConfigDict(allow_population_by_field_name=True) → validate_by_name=True - SQLAlchemy declarative_base() import path updated - FastAPI on_event → lifespan context manager (app.py) - Import sorting (ruff I001) auto-fixed across all files - Fixed broken re-export chains that ruff F401 cleanup broke: _validate_bcp47: service.py now imports from dictionary_validation directly job_to_response: _job_routes.py and test imports from service_utils directly fetch_datasource_metadata: restored re-export in service.py - Added missing TranslateJobService import in _job_routes.py (was deleted by F401) - Added ConfigDict(protected_namespaces=()) for DashboardDatasetItem schema field - pytest.ini: replaced deprecated importmode with asyncio_mode All 440 tests pass with zero deprecation warnings.
175 lines
6.6 KiB
Python
175 lines
6.6 KiB
Python
# #region TestComplianceExecutionService [C:2] [TYPE Module]
|
|
# @RELATION BINDS_TO -> SrcRoot
|
|
# @SEMANTICS: tests, clean-release, compliance, pipeline, run-finalization
|
|
# @PURPOSE: Validate stage pipeline and run finalization contracts for compliance execution.
|
|
# @LAYER Tests
|
|
# @INVARIANT: Missing manifest prevents run startup; failed execution cannot finalize as PASSED.
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
import pytest
|
|
|
|
from src.models.clean_release import (
|
|
CleanPolicySnapshot,
|
|
ComplianceDecision,
|
|
DistributionManifest,
|
|
ReleaseCandidate,
|
|
SourceRegistrySnapshot,
|
|
)
|
|
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
|
|
from src.services.clean_release.enums import CandidateStatus, RunStatus
|
|
from src.services.clean_release.report_builder import ComplianceReportBuilder
|
|
from src.services.clean_release.repository import CleanReleaseRepository
|
|
|
|
|
|
# #region _seed_with_candidate_policy_registry [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> TestComplianceExecutionService
|
|
# @PURPOSE: Build deterministic repository state for run startup tests.
|
|
# @PRE: candidate_id and snapshot ids are non-empty.
|
|
# @POST: Returns repository with candidate, policy and registry; manifest is optional.
|
|
def _seed_with_candidate_policy_registry(
|
|
*,
|
|
with_manifest: bool,
|
|
prohibited_detected_count: int = 0,
|
|
) -> tuple[CleanReleaseRepository, str, str, str]:
|
|
repository = CleanReleaseRepository()
|
|
candidate_id = "cand-us2-1"
|
|
policy_id = "policy-us2-1"
|
|
registry_id = "registry-us2-1"
|
|
manifest_id = "manifest-us2-1"
|
|
|
|
repository.save_candidate(
|
|
ReleaseCandidate(
|
|
id=candidate_id,
|
|
version="1.0.0",
|
|
source_snapshot_ref="git:sha-us2",
|
|
created_by="tester",
|
|
created_at=datetime.now(UTC),
|
|
status=CandidateStatus.MANIFEST_BUILT.value,
|
|
)
|
|
)
|
|
repository.save_registry(
|
|
SourceRegistrySnapshot(
|
|
id=registry_id,
|
|
registry_id="trusted-registry",
|
|
registry_version="1.0.0",
|
|
allowed_hosts=["repo.internal.local"],
|
|
allowed_schemes=["https"],
|
|
allowed_source_types=["repo"],
|
|
immutable=True,
|
|
)
|
|
)
|
|
repository.save_policy(
|
|
CleanPolicySnapshot(
|
|
id=policy_id,
|
|
policy_id="trusted-policy",
|
|
policy_version="1.0.0",
|
|
content_json={"rules": []},
|
|
registry_snapshot_id=registry_id,
|
|
immutable=True,
|
|
)
|
|
)
|
|
|
|
if with_manifest:
|
|
repository.save_manifest(
|
|
DistributionManifest(
|
|
id=manifest_id,
|
|
candidate_id=candidate_id,
|
|
manifest_version=1,
|
|
manifest_digest="digest-us2-1",
|
|
artifacts_digest="digest-us2-1",
|
|
source_snapshot_ref="git:sha-us2",
|
|
content_json={
|
|
"summary": {
|
|
"included_count": 1,
|
|
"excluded_count": 0 if prohibited_detected_count == 0 else prohibited_detected_count,
|
|
"prohibited_detected_count": prohibited_detected_count,
|
|
}
|
|
},
|
|
created_by="tester",
|
|
created_at=datetime.now(UTC),
|
|
immutable=True,
|
|
)
|
|
)
|
|
|
|
return repository, candidate_id, policy_id, manifest_id
|
|
# #endregion _seed_with_candidate_policy_registry
|
|
|
|
|
|
# #region test_run_without_manifest_rejected [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> TestComplianceExecutionService
|
|
# @PURPOSE: Ensure compliance run cannot start when manifest is unresolved.
|
|
# @PRE: Candidate/policy exist but manifest is missing.
|
|
# @POST: start_check_run raises ValueError and no run is persisted.
|
|
def test_run_without_manifest_rejected():
|
|
repository, candidate_id, policy_id, manifest_id = _seed_with_candidate_policy_registry(with_manifest=False)
|
|
orchestrator = CleanComplianceOrchestrator(repository)
|
|
|
|
with pytest.raises(ValueError, match="Manifest or Policy not found"):
|
|
orchestrator.start_check_run(
|
|
candidate_id=candidate_id,
|
|
policy_id=policy_id,
|
|
requested_by="tester",
|
|
manifest_id=manifest_id,
|
|
)
|
|
|
|
assert len(repository.check_runs) == 0
|
|
# #endregion test_run_without_manifest_rejected
|
|
|
|
|
|
# #region test_task_crash_mid_run_marks_failed [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> TestComplianceExecutionService
|
|
# @PURPOSE: Ensure execution crash conditions force FAILED run status.
|
|
# @PRE: Run exists, then required dependency becomes unavailable before execute_stages.
|
|
# @POST: execute_stages persists run with FAILED status.
|
|
def test_task_crash_mid_run_marks_failed():
|
|
repository, candidate_id, policy_id, manifest_id = _seed_with_candidate_policy_registry(with_manifest=True)
|
|
orchestrator = CleanComplianceOrchestrator(repository)
|
|
|
|
run = orchestrator.start_check_run(
|
|
candidate_id=candidate_id,
|
|
policy_id=policy_id,
|
|
requested_by="tester",
|
|
manifest_id=manifest_id,
|
|
)
|
|
|
|
# Simulate mid-run crash dependency loss: registry snapshot disappears.
|
|
repository.registries.clear()
|
|
|
|
failed = orchestrator.execute_stages(run)
|
|
assert failed.status == RunStatus.FAILED
|
|
# #endregion test_task_crash_mid_run_marks_failed
|
|
|
|
|
|
# #region test_blocked_run_finalization_blocks_report_builder [C:2] [TYPE Function]
|
|
# @RELATION BINDS_TO -> TestComplianceExecutionService
|
|
# @PURPOSE: Ensure blocked runs require blocking violations before report creation.
|
|
# @PRE: Manifest contains prohibited artifacts leading to BLOCKED decision.
|
|
# @POST: finalize keeps BLOCKED and report_builder rejects zero blocking violations.
|
|
def test_blocked_run_finalization_blocks_report_builder():
|
|
repository, candidate_id, policy_id, manifest_id = _seed_with_candidate_policy_registry(
|
|
with_manifest=True,
|
|
prohibited_detected_count=1,
|
|
)
|
|
orchestrator = CleanComplianceOrchestrator(repository)
|
|
builder = ComplianceReportBuilder(repository)
|
|
|
|
run = orchestrator.start_check_run(
|
|
candidate_id=candidate_id,
|
|
policy_id=policy_id,
|
|
requested_by="tester",
|
|
manifest_id=manifest_id,
|
|
)
|
|
run = orchestrator.execute_stages(run)
|
|
run = orchestrator.finalize_run(run)
|
|
|
|
assert run.final_status == ComplianceDecision.BLOCKED
|
|
assert run.status == RunStatus.SUCCEEDED
|
|
|
|
with pytest.raises(ValueError, match="Blocked run requires at least one blocking violation"):
|
|
builder.build_report_payload(run, [])
|
|
# #endregion test_blocked_run_finalization_blocks_report_builder
|
|
|
|
# #endregion TestComplianceExecutionService
|