Files
hermes-agent/tests/hermes_cli/test_passive_update_opt_out.py
Teknium 338bf9ea9a fix(cli): banner/TUI/dashboard update checks go through the GitHub API, cached 24h
The Python passive check (`banner.check_for_updates`, used by the CLI
banner, `hermes --tui`, every `tui_gateway` spawn and the dashboard's
/api/hermes/update/check) also ran `git fetch origin main` on every cache
miss, and never cached an inconclusive result so a flaky line retried on
every start. Same GitHub complaint, same fix:

- remote tip via GET /repos/{slug}/commits/main (vnd.github.sha), local tip
  via rev-parse, exact count + changelog via the compare API when they
  differ. HTTPS `ls-remote` remains only as the fallback when the API is
  unreachable or the origin isn't on GitHub.
- cache TTL 6h -> 24h, failures cached 1h; the cache is keyed on HEAD so
  `hermes update` invalidates it immediately.
- the dashboard's "what's changed" list comes from the memoized compare
  payload (`upstream_commits_behind`) instead of `git log HEAD..origin/main`,
  which was stale without a fetch.

Tests rewritten to the new contract: passive checks must not run
`git fetch`/`ls-remote` for a GitHub origin; the daily cache invalidates
when HEAD moves and re-asks after the failure window.
2026-09-10 18:15:54 -07:00

46 lines
2.0 KiB
Python

"""Passive opt-out keeps explicit update checks available."""
import json
import subprocess
import time
from hermes_constants import get_hermes_home
def test_passive_check_obeys_config_before_using_cached_notice(monkeypatch):
from hermes_cli import banner
home = get_hermes_home()
# The cache is keyed on the checkout's HEAD (an update moving HEAD invalidates it).
repo_dir = banner._resolve_repo_dir()
head = banner._git_stdout(["rev-parse", "HEAD"], cwd=repo_dir) if repo_dir else None
(home / ".update_check").write_text(json.dumps({
"ts": time.time(), "behind": 17, "rev": None, "ver": banner.VERSION, "head": head,
}), encoding="utf-8")
monkeypatch.delenv("HERMES_REVISION", raising=False)
config = home / "config.yaml"
config.write_text("updates:\n check: true\n", encoding="utf-8")
assert banner.check_for_updates(passive=True) == 17
config.write_text("updates:\n check: false\n", encoding="utf-8")
assert banner.check_for_updates(passive=True) is None
assert banner.check_for_updates() == 17
def test_explicit_check_fetches_local_origin_despite_passive_opt_out(tmp_path, monkeypatch, capsys):
from hermes_cli import main
from hermes_cli.update_cmd import _cmd_update_check
remote = tmp_path / "remote"
local = tmp_path / "checkout"
def git(*args):
return subprocess.run(["git", *map(str, args)], check=True, capture_output=True, text=True)
git("init", "-b", "main", remote)
git("-C", remote, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--allow-empty", "-m", "initial")
git("clone", remote, local)
git("-C", remote, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--allow-empty", "-m", "next")
monkeypatch.setattr(main, "PROJECT_ROOT", local)
(get_hermes_home() / "config.yaml").write_text("updates:\n check: false\n", encoding="utf-8")
_cmd_update_check()
output = capsys.readouterr().out
assert "Fetching from origin" in output
assert "1 commit" in output