159 lines
5.9 KiB
Python
159 lines
5.9 KiB
Python
"""Exercise source cleanliness, monotonic codes and fail-closed signing prerequisites."""
|
|
|
|
import contextlib
|
|
import io
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import tv_release
|
|
|
|
|
|
class ReleaseInputTest(unittest.TestCase):
|
|
"""Use a disposable Git fixture, never a build or a real signing key."""
|
|
|
|
def setUp(self) -> None:
|
|
self.temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temporary.cleanup)
|
|
self.root = Path(self.temporary.name) / "source"
|
|
self.root.mkdir()
|
|
(self.root / "docker").mkdir()
|
|
(self.root / "docker/tv-version-codes").write_text("65 installed-opt\n")
|
|
self.git("init", "--quiet")
|
|
self.git("add", "docker/tv-version-codes")
|
|
self.git(
|
|
"-c",
|
|
"user.name=Fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"commit",
|
|
"--quiet",
|
|
"-m",
|
|
"Record used code",
|
|
)
|
|
self.remote = Path(self.temporary.name) / "origin.git"
|
|
subprocess.run(
|
|
["git", "init", "--bare", "--quiet", str(self.remote)], check=True
|
|
)
|
|
self.git("remote", "add", "origin", str(self.remote))
|
|
self.git("push", "--quiet", "origin", "HEAD:main")
|
|
self.previous_root = tv_release.ROOT
|
|
tv_release.ROOT = self.root
|
|
self.addCleanup(setattr, tv_release, "ROOT", self.previous_root)
|
|
|
|
def git(self, *args: str) -> str:
|
|
"""Run the isolated source fixture's actual Git commands."""
|
|
return subprocess.check_output(["git", *args], cwd=self.root, text=True).strip()
|
|
|
|
def test_new_code_identifies_clean_source(self) -> None:
|
|
self.assertEqual(
|
|
self.git("rev-parse", "HEAD"), tv_release.validate_inputs("1.1.1", 100001)
|
|
)
|
|
|
|
def test_reused_and_lower_codes_are_rejected(self) -> None:
|
|
for code in (1, 64, 65):
|
|
with self.subTest(code=code), self.assertRaises(ValueError):
|
|
tv_release.validate_inputs("1.1.1", code)
|
|
|
|
def test_dirty_source_is_rejected(self) -> None:
|
|
(self.root / "untracked").write_text("not part of the source commit")
|
|
with self.assertRaises(ValueError):
|
|
tv_release.validate_inputs("1.1.1", 100001)
|
|
|
|
def test_unparseable_version_and_invalid_codes_are_rejected(self) -> None:
|
|
for version, code in (
|
|
("1.1.1-opt", 100001),
|
|
("1.1.1", 0),
|
|
("1.1.1", 2100000001),
|
|
):
|
|
with (
|
|
self.subTest(version=version, code=code),
|
|
self.assertRaises(ValueError),
|
|
):
|
|
tv_release.validate_inputs(version, code)
|
|
|
|
def test_missing_signing_key_never_generates_identity(self) -> None:
|
|
key = Path(self.temporary.name) / "absent-key"
|
|
with (
|
|
patch.dict(os.environ, {"WOLFSKIN_DEBUG_KEYSTORE": str(key)}),
|
|
self.assertRaises(ValueError),
|
|
):
|
|
tv_release.release(
|
|
"1.1.1", 100001, Path(self.temporary.name) / "output", True
|
|
)
|
|
self.assertFalse(key.exists())
|
|
|
|
def test_dry_run_never_builds_tags_or_mutates_ledger(self) -> None:
|
|
key = Path(self.temporary.name) / "fixture-key-presence-only"
|
|
key.write_bytes(b"not an actual keystore; dry run must not open it")
|
|
output = Path(self.temporary.name) / "output"
|
|
with (
|
|
patch.dict(os.environ, {"WOLFSKIN_DEBUG_KEYSTORE": str(key)}),
|
|
contextlib.redirect_stdout(io.StringIO()),
|
|
):
|
|
tv_release.release("1.1.1", 100001, output, True)
|
|
self.assertFalse(output.exists())
|
|
self.assertEqual("", self.git("tag", "--list"))
|
|
self.assertEqual(
|
|
"65 installed-opt\n", (self.root / "docker/tv-version-codes").read_text()
|
|
)
|
|
self.assertEqual(
|
|
"", self.git("for-each-ref", "refs/wolfskin-tv/version-codes/")
|
|
)
|
|
|
|
def test_stale_branch_uses_fetched_main_ledger(self) -> None:
|
|
stale = self.git("rev-parse", "HEAD")
|
|
(self.root / "docker/tv-version-codes").write_text(
|
|
"65 installed-opt\n100001 released\n"
|
|
)
|
|
self.git("add", "docker/tv-version-codes")
|
|
self.git(
|
|
"-c",
|
|
"user.name=Fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"commit",
|
|
"--quiet",
|
|
"-m",
|
|
"Reserve published code",
|
|
)
|
|
self.git("push", "--quiet", "origin", "HEAD:main")
|
|
self.git("checkout", "--quiet", "--detach", stale)
|
|
with self.assertRaises(ValueError):
|
|
tv_release.validate_inputs("1.1.2", 100001)
|
|
self.assertEqual(stale, tv_release.validate_inputs("1.1.2", 100002))
|
|
|
|
def test_discarding_ledger_append_cannot_reuse_reserved_code(self) -> None:
|
|
commit = self.git("rev-parse", "HEAD")
|
|
tv_release.reserve_code(100001, commit)
|
|
with (self.root / "docker/tv-version-codes").open("a") as ledger:
|
|
ledger.write("100001 1.1.1 consumed\n")
|
|
self.git("restore", "docker/tv-version-codes")
|
|
with self.assertRaises(ValueError):
|
|
tv_release.validate_inputs("1.1.2", 100001)
|
|
self.assertEqual(commit, tv_release.validate_inputs("1.1.2", 100002))
|
|
|
|
def test_head_reservation_wins_over_older_main(self) -> None:
|
|
(self.root / "docker/tv-version-codes").write_text(
|
|
"65 installed-opt\n100002 released\n"
|
|
)
|
|
self.git("add", "docker/tv-version-codes")
|
|
self.git(
|
|
"-c",
|
|
"user.name=Fixture",
|
|
"-c",
|
|
"user.email=fixture@example.invalid",
|
|
"commit",
|
|
"--quiet",
|
|
"-m",
|
|
"Reserve newer branch code",
|
|
)
|
|
with self.assertRaises(ValueError):
|
|
tv_release.validate_inputs("1.1.3", 100002)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|