feat(release): publish immutable commit bundles
prd-number-check / require-numbered-prds (pull_request) Successful in 10s
tracker-policy-pr / check-pr (pull_request) Successful in 20s
lint / lint (push) Successful in 1m1s
test / unit (pull_request) Successful in 56s
test / integration-docker (pull_request) Successful in 1m0s
test / coverage (pull_request) Failing after 23s
test / image-input-builds (pull_request) Successful in 2m31s

This commit is contained in:
2026-07-27 17:09:13 +00:00
parent 9f65ab016d
commit 249eaff53d
5 changed files with 279 additions and 10 deletions
+98
View File
@@ -0,0 +1,98 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from bot_bottle.release_bundle import build_bundle_index, canonical_bytes
from bot_bottle.release_manifest import ReleaseManifestError
from bot_bottle.release_publish import bundle_url, publish_bundle
from tests.unit.test_release_manifest import manifest
class TestReleasePublish(unittest.TestCase):
def setUp(self) -> None:
env = {
"BOT_BOTTLE_RELEASE_BASE": "https://packages.example",
"BOT_BOTTLE_RELEASE_OWNER": "owner",
"BOT_BOTTLE_RELEASE_TOKEN": "token",
}
self.env = patch.dict("os.environ", env, clear=True)
self.env.start()
self.addCleanup(self.env.stop)
def bundle(self, root: Path) -> tuple[dict[str, object], Path]:
wheel = root / "bot_bottle-0.1.0-py3-none-any.whl"
wheel.write_bytes(b"wheel")
index = build_bundle_index(
manifest(),
wheel=wheel,
wheel_url=bundle_url("1" * 40, wheel.name),
workflow_run="actions/123",
published_at="2026-07-27T00:00:00Z",
)
return index, wheel
@staticmethod
def wheel_sha(index: dict[str, object]) -> str:
wheel = index["wheel"]
assert isinstance(wheel, dict)
digest = wheel["sha256"]
assert isinstance(digest, str)
return digest
def test_publishes_wheel_then_index(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
index, wheel = self.bundle(Path(tmp))
with patch(
"bot_bottle.release_publish.remote_bytes", return_value=None,
), patch(
"bot_bottle.release_publish.remote_sha256", return_value=None,
), patch("bot_bottle.release_publish.upload") as upload:
result = publish_bundle(index, wheel)
self.assertTrue(result.endswith("/bundle-index.json"))
self.assertEqual(2, upload.call_count)
self.assertIs(wheel, upload.call_args_list[0].args[1])
self.assertEqual(canonical_bytes(index), upload.call_args_list[1].args[1])
def test_exact_existing_bundle_is_idempotent(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
index, wheel = self.bundle(Path(tmp))
with patch(
"bot_bottle.release_publish.remote_bytes",
return_value=canonical_bytes(index),
), patch(
"bot_bottle.release_publish.remote_sha256",
return_value=self.wheel_sha(index),
), patch("bot_bottle.release_publish.upload") as upload:
publish_bundle(index, wheel)
upload.assert_not_called()
def test_rejects_partial_publication(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
index, wheel = self.bundle(Path(tmp))
with patch(
"bot_bottle.release_publish.remote_bytes", return_value=None,
), patch(
"bot_bottle.release_publish.remote_sha256",
return_value=self.wheel_sha(index),
):
with self.assertRaisesRegex(
ReleaseManifestError, "administrative cleanup",
):
publish_bundle(index, wheel)
def test_rejects_existing_bundle_with_different_index(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
index, wheel = self.bundle(Path(tmp))
with patch(
"bot_bottle.release_publish.remote_bytes", return_value=b"other",
), patch(
"bot_bottle.release_publish.remote_sha256",
return_value=self.wheel_sha(index),
):
with self.assertRaisesRegex(
ReleaseManifestError, "already differs",
):
publish_bundle(index, wheel)