Run the entire CI workflow before Docker build and tests. Require Nix, native payload smoke tests, install/update E2E and signed-package upgrade acceptance before publishing. Keep Desktop Playwright E2E deferred. Archive tested Docker images and signed bundle candidates with provenance and hashes. Publishers consume those exact artifacts without rebuilding. Advance stable channels only after all required publications succeed. Keep canaries on their separate path and reject direct stable-builder publication that bypasses the gate. Move shared release transport, manifests and gates to Python. Keep native Electron adapters in JS and share feed/MIME facts as JSON. Replace the R2/feed JS implementation and move its protocol tests to Python. Verified targeted Python and JS tests, real loopback transport and CLI execution, temporary Git admission, workflow graph lint, and typechecks. No live stable release was run. Native signing, package upgrades and real registry/Store promotion still need their release-run receipts. Separate services cannot promote atomically. A promotion failure keeps the run red.
92 lines
3.3 KiB
Python
92 lines
3.3 KiB
Python
"""Tests for scripts/releases/docker.py — the staged Docker release contract."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
docker = importlib.import_module("scripts.releases.docker")
|
|
|
|
TAG = "v1.2.3"
|
|
COMMIT = "a" * 40
|
|
DIGESTS = {"amd64": "b" * 64, "arm64": "c" * 64}
|
|
|
|
|
|
@pytest.fixture
|
|
def module_root() -> Path:
|
|
return Path(docker.__file__).resolve().parent.parent.parent
|
|
|
|
|
|
def test_build_manifest_roundtrip_and_identity_rejection() -> None:
|
|
manifest = docker.build_manifest(TAG, COMMIT, DIGESTS)
|
|
parsed = docker.parse_manifest(json.dumps(manifest).encode())
|
|
docker.verify_manifest(parsed, TAG, COMMIT)
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.verify_manifest(parsed, "v1.2.4", COMMIT)
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.verify_manifest(parsed, TAG, "b" * 40)
|
|
|
|
|
|
def test_manifest_requires_both_arches() -> None:
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.build_manifest(TAG, COMMIT, {"amd64": DIGESTS["amd64"]})
|
|
bad = dict(DIGESTS)
|
|
bad["arm64"] = "z" * 64
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.build_manifest(TAG, COMMIT, bad)
|
|
|
|
|
|
def test_manifest_archive_hashes_optional_but_covering() -> None:
|
|
manifest = docker.build_manifest(TAG, COMMIT, DIGESTS, {"amd64": "d" * 64, "arm64": "e" * 64})
|
|
assert manifest["archives"] == {"amd64": "d" * 64, "arm64": "e" * 64}
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.build_manifest(TAG, COMMIT, DIGESTS, {"amd64": "d" * 64})
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.build_manifest(TAG, COMMIT, DIGESTS, {"amd64": "d" * 64, "riscv64": "e" * 64})
|
|
|
|
|
|
def test_parse_manifest_rejects_garbage() -> None:
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.parse_manifest(b"not json")
|
|
with pytest.raises(docker.DockerReleaseError):
|
|
docker.parse_manifest(json.dumps({"schema": 2}).encode())
|
|
|
|
|
|
def test_sha256_file(tmp_path: Path) -> None:
|
|
blob = tmp_path / "image.tar"
|
|
blob.write_bytes(b"payload")
|
|
assert docker.sha256_file(str(blob)) == __import__("hashlib").sha256(b"payload").hexdigest()
|
|
|
|
|
|
def test_cli_manifest_and_verify(tmp_path: Path, module_root: Path) -> None:
|
|
out = tmp_path / "manifest.json"
|
|
proc = subprocess.run(
|
|
[sys.executable, "-m", "scripts.releases.docker", "manifest",
|
|
"--tag", TAG, "--commit", COMMIT,
|
|
"--digest-amd64", DIGESTS["amd64"], "--digest-arm64", DIGESTS["arm64"]],
|
|
cwd=module_root, capture_output=True, text=True,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
out.write_text(proc.stdout)
|
|
parsed = json.loads(proc.stdout)
|
|
assert parsed["digests"] == DIGESTS
|
|
|
|
verify = subprocess.run(
|
|
[sys.executable, "-m", "scripts.releases.docker", "verify",
|
|
"--tag", TAG, "--commit", COMMIT, str(out)],
|
|
cwd=module_root, capture_output=True, text=True,
|
|
)
|
|
assert verify.returncode == 0, verify.stderr
|
|
|
|
bad = subprocess.run(
|
|
[sys.executable, "-m", "scripts.releases.docker", "verify",
|
|
"--tag", "v9.9.9", "--commit", COMMIT, str(out)],
|
|
cwd=module_root, capture_output=True, text=True,
|
|
)
|
|
assert bad.returncode == 1
|
|
assert "::error::" in bad.stderr
|