Files
hermes-agent/tests/hermes_cli/test_commit_build_updates.py
ethernet f6a30447de refactor(pm): one install_root() in pm.paths replaces six HERMES_INSTALL_ROOT ladders
post_update._install_root, boot_bootstrap.default_project_root,
update_channel._default_root, version_info._CODE_ROOT + _resolve_stamp_file,
_launchers.publish_launchers and pm.runtime each re-derived "HERMES_INSTALL_ROOT
or the code root". pm.paths.install_root() is the one place now; pm.store reuses
the downloader's User-Agent instead of carrying a second one.
2026-09-18 20:08:16 -04:00

96 lines
4.1 KiB
Python

"""A one-commit artifact cannot enter any self-update path."""
import json
from unittest.mock import Mock
import pytest
from hermes_cli.update_contract import evaluate_update_admission
MESSAGE = "This build doesn't get updates. Ask the developer who gave it to you for a new build."
@pytest.fixture
def commit_build(tmp_path, monkeypatch):
root = tmp_path / "app"
root.mkdir()
(root / "install-stamp.json").write_text(json.dumps({
"source": "commit-build", "distribution": "desktop-app", "payload": "bundled",
"updateMechanism": "external", "commit": "a" * 40,
"displayVersion": "1.2.3+gabcdef12", "baseVersion": "1.2.3", "channel": "canary",
}))
monkeypatch.setattr("hermes_cli.image_provenance.IMAGE_PROVENANCE_PATH", tmp_path / "absent")
monkeypatch.setattr("hermes_cli.config.get_project_root", lambda: root)
monkeypatch.setattr("pm.paths.repo_root", lambda: root)
monkeypatch.delenv("HERMES_INSTALL_ROOT", raising=False)
from hermes_cli.version_info import _reset_version_info_cache
_reset_version_info_cache()
yield root
_reset_version_info_cache()
@pytest.mark.parametrize("git_present", [False, True])
def test_commit_build_refuses_without_gui_advice(commit_build, git_present):
if git_present:
(commit_build / ".git").mkdir()
refusal = evaluate_update_admission(commit_build)
assert refusal is not None
assert refusal.code == "commit-build"
assert refusal.message == MESSAGE
assert refusal.update_command == ""
@pytest.mark.parametrize("passive", [False, True])
def test_commit_build_never_checks_upstream_or_reuses_source_cache(commit_build, monkeypatch, passive):
from hermes_cli import banner, source_check
# Even a shared home's source-checkout cache and an embedded SHA cannot turn this into an update.
monkeypatch.setenv("HERMES_REVISION", "b" * 40)
check = Mock(side_effect=AssertionError("must not probe"))
monkeypatch.setattr(source_check, "_branch_tip", check)
assert source_check.check_for_updates(passive=passive)["behind"] is None
check.assert_not_called()
def test_commit_version_banner_uses_stamp_not_shared_checkout(commit_build, monkeypatch):
from hermes_cli import banner, source_check
from hermes_cli.version_info import get_version_info
git = Mock(side_effect=AssertionError("version must not inspect another checkout"))
monkeypatch.setattr(banner, "get_git_banner_state", git)
label = banner.format_banner_version_label()
assert "1.2.3+gabcdef12" in label
assert "commit-build" in label
assert get_version_info().source == "commit-build"
git.assert_not_called()
@pytest.mark.parametrize("managed", [False, True])
def test_commit_backend_update_routes_refuse_before_checks_or_spawns(commit_build, monkeypatch, tmp_path, managed):
from starlette.testclient import TestClient
import hermes_cli.web_server as server
import hermes_cli.web_server_gateway as gateway
from hermes_cli import banner, source_check
from hermes_constants import get_hermes_home
monkeypatch.setattr(server, "PROJECT_ROOT", commit_build)
monkeypatch.setattr(gateway, "_ACTION_LOG_DIR", tmp_path / "logs")
monkeypatch.setattr("hermes_cli.web_server_files._dashboard_local_update_managed_externally", lambda: managed)
spawn = Mock(side_effect=AssertionError("no updater process"))
monkeypatch.setattr(gateway, "_spawn_hermes_action", spawn)
check = Mock(side_effect=AssertionError("no update check"))
monkeypatch.setattr(source_check, "check_for_updates", check)
cache = get_hermes_home() / ".update_check"
cache.write_text("source checkout cache")
client = TestClient(server.app)
client.headers[server._SESSION_HEADER_NAME] = server._SESSION_TOKEN
result = client.get("/api/hermes/update/check?force=true")
assert result.status_code == 200
assert result.json()["message"] == MESSAGE
assert result.json()["can_apply"] is False
assert result.json()["current_version"] == "1.2.3+gabcdef12"
assert client.post("/api/hermes/update").json()["message"] == MESSAGE
assert cache.read_text() == "source checkout cache"
check.assert_not_called()
spawn.assert_not_called()