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.
96 lines
4.1 KiB
Python
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()
|