Files
hermes-agent/tests/test_termux_deb_version.py
ethernet b0ab0162b0 feat(release): gate stable promotion through the full release pipeline
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.
2026-09-07 14:40:10 -04:00

139 lines
4.9 KiB
Python

"""Unit tests for scripts/termux/deb_version.py (Task 4 of the termux-deb plan)."""
import subprocess
import sys
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
SCRIPT = HERE.parent / "scripts" / "termux" / "deb_version.py"
from scripts.termux.deb_version import channel_for_tag, deb_version_for_tag # noqa: E402
def test_canary_tag_shape_matches_canonical():
"""Invariant: the deb versioner accepts EXACTLY the canary tags the
canonical release tooling mints. The canonical shape lives in
hermes_cli/update_channel.py:_CANARY_TAG_RE (8-or-14-digit, 20-prefixed
timestamps); scripts/releases/r2.py:channel_for_tag parses the same shape.
A tag this module accepts but the release flow would never mint (or vice
versa) is version-drift between the .deb channel and the feed channel.
"""
from hermes_cli.update_channel import _CANARY_TAG_RE as _NIGHTLY_TAG_RE
from scripts.termux import deb_version as dv
samples = [
"v0.20.6-canary.20260831120000", # canonical canary (14-digit)
"v0.20.6-canary.20260831", # canonical canary (8-digit)
"v1.2.3", # stable
]
for tag in samples:
assert dv._TAG_RE.match(tag), f"deb versioner rejects canonical tag {tag}"
never_minted = [
"v1.2.3-canary.202608311", # 9 digits -- canonical rejects
"v1.2.3-canary.12345678", # non-20 prefix -- canonical rejects
"v1.2.3-canary.202608311200001", # 15 digits -- canonical rejects
]
for tag in never_minted:
assert not _NIGHTLY_TAG_RE.match(tag), f"sample is actually canonical: {tag}"
assert not dv._TAG_RE.match(tag), f"deb versioner accepts never-minted tag {tag}"
def test_stable_tag_maps_to_revision_1():
assert deb_version_for_tag("v1.2.3") == "1.2.3-1"
def _dpkg_key(v: str) -> str:
# Approximate dpkg ordering for these versions: '~' sorts before everything
# (even the empty string / '-'), so map it low.
return v.replace("~", "\x00")
def test_stable_tag_multi_digit():
assert deb_version_for_tag("v26.8.31") == "26.8.31-1"
def test_major_can_be_three_digits():
assert deb_version_for_tag("v126.8.31") == "126.8.31-1"
def test_canary_tag_ranks_below_stable():
got = deb_version_for_tag("v1.2.3-canary.20260831120000")
assert got == "1.2.3~canary.20260831120000-1"
assert _dpkg_key(got) < _dpkg_key(deb_version_for_tag("v1.2.3")) # dpkg ordering
def test_canary_canary_ranking_among_nightlies():
earlier = deb_version_for_tag("v1.2.3-canary.20260831000000")
later = deb_version_for_tag("v1.2.3-canary.20260831235959")
assert _dpkg_key(earlier) < _dpkg_key(later) < _dpkg_key(deb_version_for_tag("v1.2.3"))
@pytest.mark.parametrize(
"bad",
[
"",
"1.2.3", # missing v prefix
"v1.2", # not three components
"v1.2.3.4", # four components
"v1.2.3-", # empty suffix
"v1.2.3-canary", # canary without timestamp
"v1.2.3-canary.abc", # non-numeric timestamp
"v1.2.3-beta.1", # unknown suffix channel
"v1.2.x",
"v-1.2.3",
],
)
def test_malformed_tags_raise(bad):
with pytest.raises(ValueError):
deb_version_for_tag(bad)
@pytest.mark.parametrize("bad", ["v1234.1.2", "v99999.0.0"])
def test_major_above_three_digits_rejected(bad):
with pytest.raises(ValueError):
deb_version_for_tag(bad)
def test_minor_patch_can_be_three_digits():
# Cap applies to major only; minor/patch may be wide.
assert deb_version_for_tag("v1.234.567") == "1.234.567-1"
def test_cli_invocation(capsys):
r = subprocess.run(
[sys.executable, str(SCRIPT), "v9.8.7"], capture_output=True, text=True
)
assert r.returncode == 0, r.stderr
assert r.stdout.strip() == "9.8.7-1"
def test_channel_matches_canary_shape():
"""--channel derives from the SAME _TAG_RE as the deb version: any tag
that yields a '~canary' version is canary, everything else stable."""
assert channel_for_tag("v1.2.3") == "stable"
assert channel_for_tag("v26.8.31") == "stable"
assert channel_for_tag("v0.20.6-canary.20260831120000") == "canary"
assert channel_for_tag("v0.20.6-canary.20260831") == "canary"
def test_channel_agrees_with_deb_version():
for tag in ("v1.2.3", "v126.8.31", "v1.2.3-canary.20260831120000"):
assert ("~canary" in deb_version_for_tag(tag)) == (channel_for_tag(tag) == "canary")
def test_channel_malformed_tag_raises():
with pytest.raises(ValueError):
channel_for_tag("v1.2")
def test_channel_cli_invocation():
for tag, expected in [("v9.8.7", "stable"), ("v9.8.7-canary.20260831120000", "canary")]:
r = subprocess.run(
[sys.executable, str(SCRIPT), "--channel", tag], capture_output=True, text=True
)
assert r.returncode == 0, r.stderr
assert r.stdout.strip() == expected