hermes-agent/tests/hermes_cli/test_update_channel_stable.py

92 lines
3.5 KiB
Python

"""Tests for the stable update channel (tag-tracking) in hermes_cli/update_cmd.py."""
from unittest.mock import patch
from hermes_cli.update_cmd import (
_latest_release_tag_from_ls_remote,
_parse_release_tag,
_stable_channel_active,
)
class TestParseReleaseTag:
def test_final_releases_parse(self):
assert _parse_release_tag("v0.17.0") == (0, 17, 0)
assert _parse_release_tag("v10.2.33") == (10, 2, 33)
assert _parse_release_tag(" v1.2.3 ") == (1, 2, 3)
def test_prereleases_and_garbage_rejected(self):
for tag in ("v1.2.3-rc1", "v1.2.3-beta.1", "v1.2", "1.2.3", "release-1", "vv1.2.3", ""):
assert _parse_release_tag(tag) is None, tag
def test_calver_tags_rejected(self):
"""Historical CalVer tags (v2026.7.20) must not win a numeric sort.
The major component is capped at three digits, the same rule as
_SEMVER_TAG_RE in scripts/write_install_stamp.py and
latestReleaseFromLsRemote in apps/desktop. A four-digit year would
rank above every SemVer release forever.
"""
assert _parse_release_tag("v2026.7.20") is None
assert _parse_release_tag("v1000.0.0") is None
assert _parse_release_tag("v999.0.0") == (999, 0, 0)
def test_numeric_ordering_not_lexicographic(self):
"""v0.10.0 must sort above v0.9.0 — the whole point of tuple parsing."""
newer, older = _parse_release_tag("v0.10.0"), _parse_release_tag("v0.9.0")
assert newer is not None and older is not None
assert newer > older
class TestLatestReleaseTagFromLsRemote:
def test_picks_newest_final_release(self):
output = (
"aaa1\trefs/tags/v0.9.0\n"
"bbb2\trefs/tags/v0.10.0\n"
"ccc3\trefs/tags/v0.10.1-rc1\n"
"ddd4\trefs/tags/some-other-tag\n"
)
tag, sha = _latest_release_tag_from_ls_remote(output)
assert tag == "v0.10.0"
assert sha == "bbb2"
def test_peeled_sha_wins_for_annotated_tags(self):
output = (
"tagobj\trefs/tags/v1.0.0\n"
"commitsha\trefs/tags/v1.0.0^{}\n"
)
tag, sha = _latest_release_tag_from_ls_remote(output)
assert tag == "v1.0.0"
assert sha == "commitsha"
def test_no_release_tags(self):
assert _latest_release_tag_from_ls_remote("aaa\trefs/tags/nightly\n") == (None, None)
assert _latest_release_tag_from_ls_remote("") == (None, None)
def test_malformed_lines_ignored(self):
output = "garbage line no tab\naaa\trefs/heads/main\nbbb\trefs/tags/v2.0.0\n"
assert _latest_release_tag_from_ls_remote(output) == ("v2.0.0", "bbb")
class _Args:
def __init__(self, branch=None):
self.branch = branch
class TestStableChannelActive:
def test_explicit_branch_always_wins(self):
"""--branch means main-style behavior regardless of channel config."""
assert _stable_channel_active(_Args(branch="bb/gui")) is False
def test_config_stable_activates(self):
with patch("hermes_cli.config.load_config", return_value={"update": {"channel": "stable"}}):
assert _stable_channel_active(_Args()) is True
def test_default_config_stays_main(self):
with patch("hermes_cli.config.load_config", return_value={"update": {"channel": "auto"}}):
assert _stable_channel_active(_Args()) is False
def test_config_failure_defaults_to_main(self):
with patch("hermes_cli.config.load_config", side_effect=RuntimeError("boom")):
assert _stable_channel_active(_Args()) is False