Archived
KofTwentyTwo/ci-policy: ci / fork-guard (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-nix-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-docker-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-maven-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / manifests-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / nix-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / python-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / not-enrolled (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / content-validate-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / standards-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-matrix-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-15 (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / verify (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / secrets (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / gate (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: ci / ci-result (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: native / fork-deny (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-15 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-16 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-17 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-18 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-19 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / native-result (pull_request) Canceled after 6s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
478 lines
26 KiB
Python
478 lines
26 KiB
Python
"""Central publication and completion orchestration, the generic completion receipt, idempotent release-record finalization, and the configuration that asks for them.
|
|
|
|
NOT YET RUN: written while test execution was suspended. The frozen Central primitives are reached through injected fakes of their interfaces (`publish_central`, `CentralPending`,
|
|
`CentralDuplicate`, `verified_purls`), so these cases prove OUR orchestration, binding and receipt handling, not Central, the primitives or any credential.
|
|
"""
|
|
|
|
import hashlib
|
|
import importlib
|
|
import io
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
import urllib.error
|
|
from collections.abc import Callable, Iterable
|
|
from dataclasses import dataclass, field, replace
|
|
from email.message import Message
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from typing import Any, ClassVar, Literal, NoReturn
|
|
from unittest.mock import patch
|
|
|
|
from platform_ci.configuration import ConfigurationError, parse_project_configuration
|
|
from platform_ci.delivery import completion_receipt, legacy
|
|
from platform_ci.delivery.completion import CentralOutcome, CentralServices, central_evidence, ensure_central_completion, reactor_for, verified_modules
|
|
from platform_ci.delivery.enrollment import CentralAuthorization, EnrollmentError, parse_enrollment
|
|
from platform_ci.delivery.errors import DeliveryError
|
|
from platform_ci.delivery.manifest import ManifestFile
|
|
from platform_ci.delivery.maven_reactor import build_reactor
|
|
from tests.test_maven_reactor import retained
|
|
from tests.test_tag_driven_release import configuration
|
|
|
|
BUILD = "c" * 40
|
|
GAV = ["com.example:lib:1.2.3", "com.example:parent:1.2.3"]
|
|
PURLS = ["pkg:maven/com.example/lib@1.2.3", "pkg:maven/com.example/parent@1.2.3"]
|
|
|
|
|
|
class PendingError(Exception):
|
|
"""Stands in for qrun_central.CentralPending."""
|
|
|
|
|
|
class DuplicateError(Exception):
|
|
"""Stands in for qrun_central.CentralDuplicate."""
|
|
|
|
|
|
def entry(path: str, digest: str) -> ManifestFile:
|
|
"""A retained manifest file entry."""
|
|
return ManifestFile(kind="artifact", name="maven-reactor", path=path, size=1, sha256=digest)
|
|
|
|
|
|
def artifact() -> Any:
|
|
"""The parts of a verified release artifact the orchestration reads."""
|
|
return SimpleNamespace(build_sha=BUILD, run=SimpleNamespace(run_id="41", created_at="2026-11-05T01:02:03Z"), manifest=SimpleNamespace(digest="a" * 64))
|
|
|
|
|
|
def modules(version: str = "1.2.3", jar: bytes = b"jar-bytes") -> list[dict[str, Any]]:
|
|
"""Two verified modules of the retained reactor: a parent POM and a jar module."""
|
|
return [
|
|
{"groupId": "com.example", "artifactId": "lib", "version": version, "packaging": "jar", "files": [{"classifier": None, "extension": "jar", "data": jar}, {"classifier": None, "extension": "pom", "data": b"<lib/>"}]},
|
|
{"groupId": "com.example", "artifactId": "parent", "version": version, "packaging": "pom", "files": [{"classifier": None, "extension": "pom", "data": b"<parent/>"}]},
|
|
]
|
|
|
|
|
|
FILES = (entry("artifact/p/pom.xml", "1" * 64), entry("artifact/p/lib.jar", "2" * 64))
|
|
|
|
|
|
def verified_purls(evidence: object, purls: Iterable[str]) -> list[str]:
|
|
"""Stand in for `qrun_central.verified_purls`: the purls, sorted."""
|
|
return sorted(purls)
|
|
|
|
|
|
def refuse_as_duplicate(*_arguments: object, **_options: object) -> NoReturn:
|
|
"""Stand in for a `publish_central` that finds the deployment already at Central."""
|
|
raise DuplicateError
|
|
|
|
|
|
class FakeCentral:
|
|
"""The interface of `qrun_central`, recording every upload."""
|
|
|
|
def __init__(self, *, pending: bool = False) -> None:
|
|
self.pending = pending
|
|
self.uploads: list[str | None] = []
|
|
self.central = SimpleNamespace(CentralPending=PendingError, CentralDuplicate=DuplicateError, publish_central=self.publish_central, verified_purls=verified_purls)
|
|
|
|
def publish_central(self, evidence: dict[str, Any], *transport: object, deployment_id: str | None, save_deployment: Callable[[str], None], wait: bool) -> dict[str, Any]:
|
|
"""Record the upload; `transport` is the primitive's (signer, client, snapshot), positional."""
|
|
snapshot = transport[2]
|
|
assert wait is False
|
|
self.uploads.append(deployment_id)
|
|
if deployment_id is None:
|
|
save_deployment("dep-1")
|
|
if self.pending:
|
|
raise PendingError("Original deployment is PENDING")
|
|
if snapshot is not None:
|
|
return {"central_status": "UPLOADED", "central_deployment": None, "central_purls": [], "central_files": [{"path": "a.jar", "sha256": "3" * 64}]}
|
|
return {"central_status": "PUBLISHED", "central_deployment": "dep-1", "central_files": [], "central_purls": PURLS}
|
|
|
|
|
|
class Registry:
|
|
"""An in-memory generic registry: GET (404 as the client raises it) and PUT."""
|
|
|
|
def __init__(self) -> None:
|
|
self.files: dict[str, bytes] = {}
|
|
|
|
def reader(self, method: str, path: str) -> tuple[int, bytes]:
|
|
if path not in self.files:
|
|
raise urllib.error.HTTPError(path, 404, "nf", Message(), io.BytesIO(b""))
|
|
return 200, self.files[path]
|
|
|
|
def writer(self, method: str, path: str, data: bytes, content_type: str) -> tuple[int, bytes]:
|
|
self.files[path] = data
|
|
return 201, b""
|
|
|
|
|
|
class Client:
|
|
"""A Central client double for the re-check of an existing receipt."""
|
|
|
|
def __init__(self, state: str = "PUBLISHED") -> None:
|
|
self.state = state
|
|
|
|
def status(self, deployment_id: str) -> dict[str, Any]:
|
|
return {"deploymentState": self.state, "deploymentId": deployment_id, "purls": PURLS}
|
|
|
|
def read_snapshot(self, path: str) -> bytes:
|
|
return b"x"
|
|
|
|
|
|
class Forge:
|
|
"""The repository document read for Central's public-route rule."""
|
|
|
|
def __init__(self, *, private: bool = False, identity: int = 7) -> None:
|
|
self.document = {"id": identity, "private": private}
|
|
|
|
def get(self, path: str) -> object:
|
|
return self.document
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ServiceFactory:
|
|
"""A services factory over the in-memory registry, recording that its signer was closed."""
|
|
|
|
made: CentralServices
|
|
closed: list[bool] = field(default_factory=list[bool])
|
|
|
|
def __call__(self) -> CentralServices:
|
|
"""Return the services."""
|
|
return self.made
|
|
|
|
|
|
def services(registry: Registry, client: Client | None = None) -> ServiceFactory:
|
|
"""A services factory over the in-memory registry, recording that its signer was closed."""
|
|
closed: list[bool] = []
|
|
made = CentralServices(client=client or Client(), signer=object(), completion_writer=registry.writer, completion_reader=registry.reader, close=lambda: closed.append(True))
|
|
return ServiceFactory(made, closed)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Scenario:
|
|
"""What one orchestration run varies; the defaults are the valid published release (`"default"` means the fixture's own authorization or services)."""
|
|
|
|
version: str = "1.2.3"
|
|
repository: str = "QRun-IO/r"
|
|
forge: Forge | None = None
|
|
authorization: CentralAuthorization | Literal["default"] | None = "default"
|
|
factory: Callable[[], CentralServices] | Literal["default"] | None = "default"
|
|
jar: bytes = b"jar-bytes"
|
|
|
|
|
|
def ensure(directory: Path, central: FakeCentral, registry: Registry, scenario: Scenario | None = None) -> CentralOutcome:
|
|
"""Run the orchestration for the fixture bundle with the fakes."""
|
|
chosen = scenario or Scenario()
|
|
authorization = CentralAuthorization("QRun-IO") if isinstance(chosen.authorization, str) else chosen.authorization
|
|
factory = services(registry) if isinstance(chosen.factory, str) else chosen.factory
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules(chosen.version, chosen.jar)):
|
|
return ensure_central_completion(
|
|
repository=chosen.repository,
|
|
repository_id=7,
|
|
authorization=authorization,
|
|
forge=chosen.forge or Forge(),
|
|
artifact=artifact(),
|
|
files=FILES,
|
|
version=chosen.version,
|
|
request_name="pub",
|
|
channel="release",
|
|
receipts=directory,
|
|
services=factory,
|
|
primitives=SimpleNamespace(central=central.central),
|
|
)
|
|
|
|
|
|
class ReactorBindingTest(unittest.TestCase):
|
|
"""The receipt's hashes are the hashes of the deterministic reactor archive and its manifest.json, rebuilt from the retained files."""
|
|
|
|
def test_the_manifest_hash_is_the_hash_of_the_manifest_json_and_everything_is_deterministic(self) -> None:
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules()):
|
|
first, second = reactor_for(artifact(), FILES, "1.2.3"), reactor_for(artifact(), FILES, "1.2.3")
|
|
self.assertEqual((first.archive, first.members), (second.archive, second.members))
|
|
self.assertEqual(first.manifest_sha256, hashlib.sha256(first.members["manifest.json"]).hexdigest())
|
|
self.assertEqual(first.archive_sha256, hashlib.sha256(first.archive).hexdigest())
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules(jar=b"other")):
|
|
changed = reactor_for(artifact(), FILES, "1.2.3")
|
|
self.assertNotEqual((changed.archive_sha256, changed.manifest_sha256), (first.archive_sha256, first.manifest_sha256))
|
|
|
|
def test_a_version_the_build_did_not_record_is_refused(self) -> None:
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules("1.2.3")), self.assertRaises(DeliveryError):
|
|
reactor_for(artifact(), FILES, "1.2.4")
|
|
|
|
|
|
def captured_bundle() -> tuple[Any, tuple[ManifestFile, ...], list[dict[str, Any]], dict[str, bytes]]:
|
|
"""An artifact whose bundle retains a reactor capture under `artifact/p/`: (artifact, retained entries, the modules the adapter grouped, the members)."""
|
|
members = retained()
|
|
data = {f"artifact/p/{name}": content for name, content in members.items()}
|
|
entries = tuple(ManifestFile(kind="artifact", name="maven-reactor", path=path, size=len(content), sha256=hashlib.sha256(content).hexdigest()) for path, content in sorted(data.items()))
|
|
|
|
def read_bytes(item: ManifestFile) -> bytes:
|
|
return data[item.path]
|
|
|
|
bundle = SimpleNamespace(read_bytes=read_bytes)
|
|
subject = SimpleNamespace(build_sha=BUILD, run=SimpleNamespace(run_id="41", created_at="2026-11-05T01:02:03Z"), manifest=SimpleNamespace(digest="a" * 64), bundle=bundle)
|
|
grouped = [
|
|
{
|
|
"groupId": "com.example",
|
|
"artifactId": "lib",
|
|
"version": "1.2.3",
|
|
"packaging": "jar",
|
|
"files": [{"classifier": None, "extension": "jar", "data": members["files/lib/lib-1.2.3.jar"]}, {"classifier": None, "extension": "pom", "data": members["files/lib/lib-1.2.3.pom"]}, {"classifier": None, "extension": "module", "data": members["files/lib/lib-1.2.3.module"]}],
|
|
},
|
|
{"groupId": "com.example", "artifactId": "parent", "version": "1.2.3", "packaging": "pom", "files": [{"classifier": None, "extension": "pom", "data": members["files/parent/parent-1.2.3.pom"]}]},
|
|
]
|
|
return subject, entries, grouped, members
|
|
|
|
|
|
class RetainedCaptureTest(unittest.TestCase):
|
|
"""A captured reactor's manifest bytes stay exact for `manifest_sha256`; the `.module` file reaches Central; contradictions are refused."""
|
|
|
|
def test_the_retained_manifest_bytes_give_the_receipt_hash_and_the_module_file_reaches_the_evidence(self) -> None:
|
|
subject, entries, grouped, members = captured_bundle()
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=grouped):
|
|
reactor = reactor_for(subject, entries, "1.2.3")
|
|
evidence = central_evidence(verified_modules(subject, entries, "1.2.3"), "1.2.3")
|
|
self.assertEqual(reactor.members["manifest.json"], members["manifest.json"])
|
|
self.assertEqual(reactor.manifest_sha256, hashlib.sha256(members["manifest.json"]).hexdigest())
|
|
self.assertIn("files/lib/lib-1.2.3.module", reactor.members)
|
|
self.assertIn("module", {item["extension"] for module in evidence["modules"] for item in module["files"]})
|
|
self.assertEqual([module["artifactId"] for module in evidence["modules"]], ["parent", "lib"])
|
|
|
|
def test_a_hash_the_bundle_records_for_other_manifest_bytes_or_unretained_module_data_is_refused(self) -> None:
|
|
subject, entries, grouped, _members = captured_bundle()
|
|
tampered = tuple(replace(item, sha256="0" * 64) if item.path.endswith("/manifest.json") else item for item in entries)
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=grouped), self.assertRaises(DeliveryError):
|
|
reactor_for(subject, tampered, "1.2.3")
|
|
other = [{**module, "files": [{**item, "data": b"not retained"} if item["extension"] == "jar" else item for item in module["files"]]} for module in grouped]
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=other), self.assertRaises(DeliveryError):
|
|
reactor_for(subject, entries, "1.2.3")
|
|
|
|
def test_incomplete_or_ambiguous_metadata_and_foreign_roots_are_refused(self) -> None:
|
|
subject, entries, grouped, _members = captured_bundle()
|
|
for broken in (tuple(item for item in entries if not item.path.endswith("/SOURCE_COMMIT")), (*entries, entry("elsewhere/manifest.json", "1" * 64))):
|
|
with self.subTest(len(broken)), patch("platform_ci.delivery.completion.maven_modules", return_value=grouped), self.assertRaises(DeliveryError):
|
|
reactor_for(subject, broken, "1.2.3")
|
|
|
|
def test_a_bundle_without_captured_metadata_still_generates_the_deterministic_archive(self) -> None:
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules()):
|
|
generated = reactor_for(artifact(), FILES, "1.2.3")
|
|
self.assertEqual(generated.members["manifest.json"], build_reactor(BUILD, "1.2.3", modules()).members["manifest.json"])
|
|
|
|
|
|
class CentralCompletionTest(unittest.TestCase):
|
|
"""Complete only with a terminal bound receipt; pending resumes the saved deployment; a repeat uploads nothing."""
|
|
|
|
def test_a_published_release_is_recorded_with_the_reactor_hashes_in_the_configured_namespace(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
central, registry = FakeCentral(), Registry()
|
|
outcome = ensure(Path(directory), central, registry)
|
|
with patch("platform_ci.delivery.completion.maven_modules", return_value=modules()):
|
|
reactor = reactor_for(artifact(), FILES, "1.2.3")
|
|
self.assertEqual(outcome.state, "complete")
|
|
(path,) = registry.files
|
|
self.assertEqual(path, f"/api/packages/QRun-IO/generic/qrun-publication-completion/repo-7-run-41/{BUILD}.json")
|
|
stored = json.loads(registry.files[path])
|
|
self.assertEqual((stored["manifest_sha256"], stored["archive_sha256"], stored["gav"], stored["target"], stored["state"], stored["schema"]), (reactor.manifest_sha256, reactor.archive_sha256, GAV, "central-publisher", "PUBLISHED", "qrun-publication-completion/1"))
|
|
|
|
def test_a_repeat_uploads_nothing_and_a_changed_byte_is_refused(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
central, registry = FakeCentral(), Registry()
|
|
ensure(Path(directory), central, registry)
|
|
self.assertEqual(ensure(Path(directory), central, registry).state, "complete")
|
|
self.assertEqual(central.uploads, [None], "the second call read the immutable receipt and uploaded nothing")
|
|
with self.assertRaises(DeliveryError):
|
|
ensure(Path(directory), central, registry, Scenario(jar=b"relabelled"))
|
|
with self.assertRaises(DeliveryError):
|
|
ensure(Path(directory), central, registry, Scenario(factory=services(registry, Client("FAILED"))))
|
|
|
|
def test_a_pending_deployment_is_not_complete_and_is_resumed_not_repeated(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
central, registry = FakeCentral(pending=True), Registry()
|
|
first, second = ensure(Path(directory), central, registry), ensure(Path(directory), central, registry)
|
|
self.assertEqual((first.state, second.state, registry.files), ("pending", "pending", {}))
|
|
self.assertEqual(central.uploads, [None, "dep-1"], "the saved original deployment id is reused, never a second upload")
|
|
record = json.loads((Path(directory) / "QRun-IO/r" / f"{BUILD}.pub.release.central-upload.json").read_text(encoding="utf-8"))
|
|
self.assertEqual((record["deployment_id"], record["version"], record["source_commit"]), ("dep-1", "1.2.3", BUILD))
|
|
|
|
def test_a_saved_upload_for_different_bytes_is_refused(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
target = Path(directory) / "QRun-IO/r" / f"{BUILD}.pub.release.central-upload.json"
|
|
target.parent.mkdir(parents=True)
|
|
target.write_text(json.dumps({"source_commit": BUILD, "archive_sha256": "0" * 64, "version": "1.2.3", "deployment_id": "old"}), encoding="utf-8")
|
|
with self.assertRaises(DeliveryError):
|
|
ensure(Path(directory), FakeCentral(), Registry())
|
|
|
|
def test_a_snapshot_is_uploaded_and_recorded_with_its_files(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
registry = Registry()
|
|
outcome = ensure(Path(directory), FakeCentral(), registry, Scenario(version="1.2.3-SNAPSHOT"))
|
|
self.assertEqual((outcome.state, outcome.completion and outcome.completion["target"]), ("complete", "central-snapshots"))
|
|
|
|
def test_the_destination_is_authorization_data_for_any_owner_and_nothing_is_switched_on_a_name(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
registry = Registry()
|
|
outcome = ensure(Path(directory), FakeCentral(), registry, Scenario(repository="Acme/r", authorization=CentralAuthorization("Acme")))
|
|
self.assertEqual(outcome.state, "complete")
|
|
self.assertTrue(next(iter(registry.files)).startswith("/api/packages/Acme/generic/qrun-publication-completion/"))
|
|
with self.assertRaises(DeliveryError): # a repository outside the configured owner is refused by the receipt's own validation
|
|
ensure(Path(directory), FakeCentral(), Registry(), Scenario(repository="Other/r", authorization=CentralAuthorization("Acme")))
|
|
|
|
def test_missing_authorization_private_repositories_identity_credentials_and_duplicates_fail_closed(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory)
|
|
with self.assertRaises(DeliveryError):
|
|
ensure(base, FakeCentral(), Registry(), Scenario(forge=Forge(private=True)))
|
|
self.assertEqual(ensure(base, FakeCentral(), Registry(), Scenario(forge=Forge(private=True), authorization=CentralAuthorization("QRun-IO", allow_private=True))).state, "complete")
|
|
for name, scenario in (("authorization", Scenario(authorization=None)), ("forge", Scenario(forge=Forge(identity=8))), ("factory", Scenario(factory=None))):
|
|
with self.subTest(name), self.assertRaises(DeliveryError):
|
|
ensure(base, FakeCentral(), Registry(), scenario)
|
|
central = FakeCentral()
|
|
central.central.publish_central = refuse_as_duplicate
|
|
with self.assertRaises(DeliveryError):
|
|
ensure(base / "dup", central, Registry())
|
|
|
|
def test_the_signer_is_always_closed_and_an_unvendored_primitive_is_an_explicit_error(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
registry = Registry()
|
|
factory = services(registry)
|
|
ensure(Path(directory), FakeCentral(), registry, Scenario(factory=factory))
|
|
self.assertEqual(factory.closed, [True])
|
|
with patch("platform_ci.delivery.legacy.importlib.import_module", side_effect=ModuleNotFoundError("qrun_central")), self.assertRaisesRegex(DeliveryError, "not vendored"):
|
|
legacy.central_primitives()
|
|
|
|
|
|
class CompletionReceiptTest(unittest.TestCase):
|
|
"""The generic receipt module keeps the frozen primitive's rules and takes the owner as data."""
|
|
|
|
def receipt(self, owner: str = "QRun-IO") -> dict[str, Any]:
|
|
return {
|
|
"schema": "qrun-publication-completion/1",
|
|
"repository": f"{owner}/r",
|
|
"repository_id": 7,
|
|
"run_id": 41,
|
|
"source_commit": BUILD,
|
|
"version": "1.2.3",
|
|
"archive_sha256": "e" * 64,
|
|
"manifest_sha256": "f" * 64,
|
|
"gav": GAV,
|
|
"target": "central-publisher",
|
|
"state": "PUBLISHED",
|
|
"deployment_id": "dep-1",
|
|
"files": [],
|
|
"purls": PURLS,
|
|
}
|
|
|
|
def test_the_frozen_rules_still_refuse_every_incomplete_or_contradictory_receipt(self) -> None:
|
|
completion_receipt.validate("QRun-IO", self.receipt())
|
|
for changes in ({"state": "PENDING"}, {"deployment_id": None}, {"files": [{"path": "a", "sha256": "1" * 64}]}, {"purls": PURLS[:1]}, {"gav": GAV[::-1]}, {"version": "1.2.4"}, {"manifest_sha256": "x"}, {"schema": "other/1"}, {"target": "other"}, {"repository": "Other/r"}):
|
|
with self.subTest(changes), self.assertRaises(ValueError):
|
|
completion_receipt.validate("QRun-IO", self.receipt() | changes)
|
|
|
|
def test_publish_is_immutable_and_read_back_independently(self) -> None:
|
|
registry = Registry()
|
|
completion_receipt.publish("QRun-IO", self.receipt(), registry.writer, registry.reader)
|
|
completion_receipt.publish("QRun-IO", self.receipt(), registry.writer, registry.reader) # identical replay is accepted
|
|
with self.assertRaises(ValueError):
|
|
completion_receipt.publish("QRun-IO", self.receipt() | {"archive_sha256": "d" * 64}, registry.writer, registry.reader)
|
|
|
|
def test_for_the_owner_the_frozen_primitive_hardcodes_the_location_and_bytes_are_identical(self) -> None:
|
|
try:
|
|
frozen = importlib.import_module("qrun_completion_receipt") # the vendored original (its directory is on `sys.path` through `legacy`), used only to prove equivalence
|
|
except ModuleNotFoundError:
|
|
self.skipTest("the frozen qrun_completion_receipt is not vendored here")
|
|
self.assertEqual(frozen.location(7, 41, BUILD), completion_receipt.location("QRun-IO", 7, 41, BUILD))
|
|
self.assertEqual(frozen.encode(self.receipt()), completion_receipt.encode("QRun-IO", self.receipt()))
|
|
|
|
|
|
class EnrollmentCentralTest(unittest.TestCase):
|
|
"""The registry destination is explicit, company-bound data."""
|
|
|
|
def document(self, central: object) -> dict[str, Any]:
|
|
return {
|
|
"schema": 1,
|
|
"company": "qqq",
|
|
"repository": "QRun-IO/r",
|
|
"repository_id": 7,
|
|
"platform_revisions": ["a" * 40],
|
|
"max_artifact_bytes": 1000,
|
|
"max_runs_scanned": 10,
|
|
"workflow_references": [f"self:QRun-IO/platform-ci/.gitea/scoped_workflows/pipeline.yaml@{'b' * 40}"],
|
|
"destinations": [{"destination": "d", "formats": ["maven"], "channels": ["release"], "target": {"kind": "package", "owner": "QRun-IO"}}],
|
|
"central": central,
|
|
}
|
|
|
|
def test_the_central_object_is_validated_and_stays_inside_the_company(self) -> None:
|
|
parsed = parse_enrollment(self.document({"completion_owner": "QRun-IO", "allow_private": True}))
|
|
self.assertEqual(parsed.central, CentralAuthorization("QRun-IO", True))
|
|
for bad in ({"completion_owner": "KofTwentyTwo"}, {}, {"completion_owner": "QRun-IO", "other": 1}, {"completion_owner": "QRun-IO", "allow_private": "yes"}):
|
|
with self.subTest(bad), self.assertRaises(EnrollmentError):
|
|
parse_enrollment(self.document(bad))
|
|
|
|
|
|
class FakeReleases:
|
|
"""The forge release endpoints: GET by tag (404 until a record exists) and POST."""
|
|
|
|
def __init__(self) -> None:
|
|
self.records: dict[str, dict[str, Any]] = {}
|
|
self.posts = 0
|
|
|
|
def __call__(self, method: str, path: str, body: dict[str, Any] | None = None) -> tuple[int, Any]:
|
|
if method == "GET":
|
|
tag = path.rsplit("/", 1)[1]
|
|
if tag not in self.records:
|
|
raise urllib.error.HTTPError(path, 404, "nf", Message(), io.BytesIO(b""))
|
|
return 200, self.records[tag]
|
|
assert body is not None
|
|
self.posts += 1
|
|
self.records[body["tag_name"]] = {"tag_name": body["tag_name"], "draft": False}
|
|
return 201, self.records[body["tag_name"]]
|
|
|
|
|
|
class ReleaseRecordTest(unittest.TestCase):
|
|
"""The frozen record finalizer is idempotent: a retry posts nothing and never duplicates or rewrites a record."""
|
|
|
|
def test_a_retry_confirms_the_existing_record_without_posting_again(self) -> None:
|
|
releases = FakeReleases()
|
|
first = legacy.ensure_release_record(releases, "O/r", "1.2.3", "O", "maven")
|
|
second = legacy.ensure_release_record(releases, "O/r", "1.2.3", "O", "maven")
|
|
self.assertEqual((first, second, releases.posts), ({"release": "v1.2.3"}, {"release": "v1.2.3"}, 1))
|
|
|
|
def test_a_draft_or_mismatched_existing_record_is_refused(self) -> None:
|
|
releases = FakeReleases()
|
|
releases.records["v1.2.3"] = {"tag_name": "v1.2.3", "draft": True}
|
|
with self.assertRaises(DeliveryError):
|
|
legacy.ensure_release_record(releases, "O/r", "1.2.3", "O", "maven")
|
|
self.assertEqual(releases.posts, 0)
|
|
|
|
def test_a_version_the_native_contract_does_not_know_is_refused_before_any_request(self) -> None:
|
|
releases = FakeReleases()
|
|
with self.assertRaises(DeliveryError):
|
|
legacy.ensure_release_record(releases, "O/r", "1.2", "O", "maven")
|
|
self.assertEqual(releases.posts, 0)
|
|
|
|
|
|
class CompletionConfigurationTest(unittest.TestCase):
|
|
"""The repository's own channel asks for terminal evidence, and only where it can apply."""
|
|
|
|
CHANNELS: ClassVar[dict[str, Any]] = {"release": {"trigger": {"push_tags": ["v*"]}, "tag_policy": "stable", "release_record": True, "central_completion": True}}
|
|
|
|
def test_release_record_needs_a_tag_policy(self) -> None:
|
|
with self.assertRaises(ConfigurationError):
|
|
parse_project_configuration(configuration(channels={"release": {"trigger": {"push_branches": ["main"]}, "release_record": True}}))
|
|
|
|
def test_central_completion_is_for_maven_publications_only(self) -> None:
|
|
container = {"artifacts": {"out": {"paths": ["out/*"], "format": "container"}}}
|
|
publication = {"out": {"component": "checks", "artifact": "out", "destination": "dest", "channels": ["release"]}}
|
|
with self.assertRaises(ConfigurationError):
|
|
parse_project_configuration(configuration(channels=self.CHANNELS, component=container, publications=publication))
|
|
parse_project_configuration(configuration(channels={"release": {"trigger": {"push_tags": ["v*"]}, "tag_policy": "stable"}}, component=container, publications=publication))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|