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>
169 lines
8.9 KiB
Python
169 lines
8.9 KiB
Python
"""The `finalize-version` command and the `version_write_back` configuration contract. UNTESTED: written while test execution was suspended; nothing is run, signed, pushed or contacted.
|
|
|
|
The forge, run trust, artifact selection and the write itself are patched at the module boundary: these tests pin what the command reads, refuses and passes on, not the write (see
|
|
`test_source_lifecycle.py`).
|
|
"""
|
|
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
from typing import Any, cast
|
|
from unittest.mock import patch
|
|
|
|
import yaml
|
|
|
|
from platform_ci.__main__ import main
|
|
from platform_ci.configuration import ConfigurationError, WriteBackPolicy, parse_project_configuration
|
|
from platform_ci.delivery.errors import DeliveryError
|
|
from platform_ci.delivery.finalize_version import finalize_version
|
|
from platform_ci.delivery.publisher import RunInProgressError
|
|
from platform_ci.delivery.source_lifecycle import Fatal, NotYetPublished
|
|
from tests.support import example_mapping
|
|
|
|
SHA = "a" * 40
|
|
PLATFORM = "b" * 40
|
|
MODULE = "platform_ci.delivery.finalize_version"
|
|
|
|
|
|
def _configuration(*, tag_driven: bool = False, write_back: object = None, central: bool = False, publish: bool = True) -> dict[str, object]:
|
|
"""The Maven publication example with one channel (branch- or tag-driven) that may declare `version_write_back`."""
|
|
configuration = example_mapping("maven-publication.ci.yaml")
|
|
channel: dict[str, object] = {"trigger": {"push_tags": ["v*.*.*"]} if tag_driven else {"push_branches": ["release/**"]}, "versioning": "maven-release"}
|
|
if tag_driven:
|
|
channel["tag_policy"] = "stable"
|
|
if central:
|
|
channel["central_completion"] = True
|
|
if write_back is not None:
|
|
channel["version_write_back"] = write_back
|
|
publications = {"library-packages": {"component": "library", "artifact": "packages", "destination": "company-packages", "channels": ["candidate"]}} if publish else {}
|
|
configuration["release"] = {"channels": {"candidate": channel}, "publications": publications}
|
|
return configuration
|
|
|
|
|
|
def _environment(ref: str = "refs/heads/release/1.6", **overrides: str) -> dict[str, str]:
|
|
values = {
|
|
"CI_EVENT_NAME": "push",
|
|
"CI_EVENT_REF": ref,
|
|
"CI_SOURCE_REVISION": SHA,
|
|
"CI_REPOSITORY": "Org/repo",
|
|
"CI_REPOSITORY_ID": "7",
|
|
"CI_RUN_ID": "50",
|
|
"CI_RUN_ATTEMPT": "1",
|
|
"CI_PLATFORM_REVISION": PLATFORM,
|
|
"CI_SCOPED_SOURCE_ID": "3",
|
|
"CI_WORKFLOW_SHA": "c" * 40,
|
|
"GITHUB_SERVER_URL": "https://git.kof22.com",
|
|
"FORGE_TOKEN": "job-token",
|
|
}
|
|
return values | overrides
|
|
|
|
|
|
def _artifact(version: str | None = "1.6.0-RC.1") -> Any:
|
|
inputs = None if version is None else SimpleNamespace(version=version)
|
|
return SimpleNamespace(manifest=SimpleNamespace(build_inputs=inputs))
|
|
|
|
|
|
class ConfigurationContractTest(unittest.TestCase):
|
|
def test_a_branch_driven_channel_opts_in_and_defaults_are_closed(self) -> None:
|
|
channel = parse_project_configuration(_configuration(write_back={"require_push": True})).channels[0]
|
|
self.assertEqual(channel.version_write_back, WriteBackPolicy(require_push=True))
|
|
self.assertIsNone(parse_project_configuration(_configuration()).channels[0].version_write_back)
|
|
|
|
def test_a_tag_driven_channel_names_the_branch_it_writes_to(self) -> None:
|
|
channel = parse_project_configuration(_configuration(tag_driven=True, write_back={"target_branch": "main"})).channels[0]
|
|
self.assertEqual(channel.version_write_back, WriteBackPolicy(target_branch="main"))
|
|
|
|
def test_each_invalid_declaration_is_refused(self) -> None:
|
|
cases: dict[str, dict[str, object]] = {
|
|
"other file": _configuration(write_back={"file": "build.gradle"}),
|
|
"branch target on a branch channel": _configuration(write_back={"target_branch": "main"}),
|
|
"tag channel without a target": _configuration(tag_driven=True, write_back={}),
|
|
"bad require_push": _configuration(write_back={"require_push": "yes"}),
|
|
"unknown field": _configuration(write_back={"token": "x"}),
|
|
"not a mapping": _configuration(write_back="yes"),
|
|
"no maven publication": _configuration(write_back={}, publish=False),
|
|
}
|
|
for name, configuration in cases.items():
|
|
with self.subTest(name), self.assertRaises(ConfigurationError):
|
|
parse_project_configuration(configuration)
|
|
|
|
def test_a_channel_with_both_trigger_kinds_cannot_write_back(self) -> None:
|
|
configuration = _configuration(write_back={})
|
|
cast("dict[str, dict[str, dict[str, Any]]]", configuration["release"])["channels"]["candidate"]["trigger"]["push_tags"] = ["v*.*.*"]
|
|
with self.assertRaises(ConfigurationError):
|
|
parse_project_configuration(configuration)
|
|
|
|
|
|
def _run(environment: dict[str, str], configuration: dict[str, object] | None, *, artifacts: dict[str, Any] | None = None, trusted: bool = True, select_error: Exception | None = None, **injected: Any) -> tuple[list[str], dict[str, Any]]:
|
|
"""Run the command with the forge boundary patched; return its lines and the keyword arguments `postpublish` received."""
|
|
seen: dict[str, Any] = {}
|
|
|
|
def postpublish(ctx: Any, policy: Any, **keywords: Any) -> dict[str, str]:
|
|
seen.update(keywords, ctx=ctx, policy=policy)
|
|
return {"status": "pushed", "reason": "", "pushed_sha": "d" * 40}
|
|
|
|
content = None if configuration is None else yaml.safe_dump(configuration).encode()
|
|
selector = patch(f"{MODULE}.select_artifacts", side_effect=select_error) if select_error else patch(f"{MODULE}.select_artifacts", return_value=(artifacts or {"library-linux-amd64": _artifact()}, []))
|
|
with (
|
|
patch(f"{MODULE}.require_repository"),
|
|
patch(f"{MODULE}.current_workflow_trust", return_value=object() if trusted else None),
|
|
patch(f"{MODULE}.read_file", return_value=content),
|
|
selector,
|
|
patch(f"{MODULE}.selected_artifact_files", return_value=("file",)),
|
|
patch(f"{MODULE}.postpublish", side_effect=postpublish),
|
|
patch(f"{MODULE}.SourceWriter"),
|
|
patch(f"{MODULE}.VersionGit"),
|
|
patch(f"{MODULE}.legacy.package_client"),
|
|
patch(f"{MODULE}.load_runtime_catalog"),
|
|
):
|
|
lines = finalize_version(environment, forge=cast("Any", object()), **injected)
|
|
return lines, seen
|
|
|
|
|
|
class FinalizeVersionTest(unittest.TestCase):
|
|
def test_a_branch_run_passes_the_recorded_version_and_the_released_branch(self) -> None:
|
|
lines, seen = _run(_environment(), _configuration(write_back={"require_push": True}, central=True))
|
|
self.assertEqual((seen["version"], seen["ctx"].branch, seen["ctx"].tag, seen["central_required"], seen["owner"], seen["repository_id"]), ("1.6.0-RC.1", "release/1.6", "", True, "Org", 7))
|
|
self.assertEqual(seen["policy"], WriteBackPolicy(require_push=True))
|
|
self.assertEqual((seen["ctx"].run, seen["ctx"].attempt, seen["ctx"].sha, seen["ctx"].repository), (50, 1, SHA, "Org/repo"))
|
|
self.assertIn("pushed", lines[0])
|
|
|
|
def test_a_tag_run_writes_to_the_configured_branch_and_binds_the_tag(self) -> None:
|
|
_, seen = _run(_environment("refs/tags/v1.6.0"), _configuration(tag_driven=True, write_back={"target_branch": "main"}))
|
|
self.assertEqual((seen["ctx"].branch, seen["ctx"].tag, seen["central_required"]), ("main", "v1.6.0", False))
|
|
|
|
def test_no_matching_opt_in_writes_nothing(self) -> None:
|
|
for environment, configuration in (
|
|
(_environment(), _configuration()),
|
|
(_environment("refs/heads/develop"), _configuration(write_back={})),
|
|
(_environment(), None),
|
|
):
|
|
lines, seen = _run(environment, configuration)
|
|
self.assertEqual(seen, {})
|
|
self.assertIn("nothing to write back", lines[0])
|
|
|
|
def test_incomplete_identity_and_foreign_forge_are_refused_before_any_read(self) -> None:
|
|
for overrides in ({"CI_EVENT_NAME": "pull_request"}, {"CI_SOURCE_REVISION": "abc"}, {"FORGE_TOKEN": ""}, {"GITHUB_SERVER_URL": "https://example.com"}, {"CI_RUN_ATTEMPT": ""}, {"CI_PLATFORM_REVISION": ""}, {"CI_EVENT_REF": "refs/pull/1/merge"}):
|
|
with self.subTest(overrides), self.assertRaises(DeliveryError):
|
|
finalize_version(_environment(**overrides))
|
|
|
|
def test_an_unproven_workflow_is_refused(self) -> None:
|
|
with self.assertRaises(DeliveryError):
|
|
_run(_environment(), _configuration(write_back={}), trusted=False)
|
|
|
|
def test_a_bundle_without_build_inputs_is_fatal(self) -> None:
|
|
with self.assertRaises(Fatal):
|
|
_run(_environment(), _configuration(write_back={}), artifacts={"library-linux-amd64": _artifact(None)})
|
|
|
|
def test_an_unfinished_release_run_is_not_yet_published(self) -> None:
|
|
with self.assertRaises(NotYetPublished):
|
|
_run(_environment(), _configuration(write_back={}), select_error=RunInProgressError("run 9 has not finished"))
|
|
|
|
|
|
class EntryPointTest(unittest.TestCase):
|
|
def test_the_command_is_registered_and_fails_closed_without_the_workflow_environment(self) -> None:
|
|
self.assertEqual(main(["finalize-version"], environment={}), 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|