"""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()