Files
hermes-agent/tests/hermes_cli/test_update_desktop_stale_warning.py
T
Teknium ba030bc0db fix(test-seams): monkeypatch.setattr facade aliases — also patch the defining module (57 files)
Tests did monkeypatch.setattr(<facade module>, name) where name is now defined in a
sibling module and the production path reads the sibling's binding. Where production
reads through BOTH bindings the setattr is duplicated onto the defining module (import
added next to the existing alias import); where only the sibling reads it the target is
repointed. Seams whose production readers go through the facade are left alone.
2026-09-03 19:33:28 -07:00

179 lines
5.7 KiB
Python

"""A failed Desktop pack must not look like a successful update.
#88251: ``hermes update`` treated a failed desktop pack as non-fatal, printed
an early warning, then still ended with ``✓ Update complete!``. The Python
side moved on; the Electron app stayed on the previous build.
``_rebuild_desktop_after_update`` returns False only when a rebuild was
attempted and failed. The final banner then prints ``⚠ Update partially
complete`` instead of the success line, and gateway mode writes ``1`` to
``.update_exit_code``.
"""
import pytest
from hermes_cli import update_cmd
import hermes_cli.update_cmd_maint as update_cmd_maint
from hermes_cli.update_cmd import (
_print_update_summary,
_rebuild_desktop_after_update,
_write_gateway_update_exit_code,
)
class _Result:
def __init__(self, returncode: int, stdout: str = ""):
self.returncode = returncode
self.stdout = stdout
@pytest.fixture()
def desktop_env(tmp_path, monkeypatch):
"""A desktop dir that looks installed and a faked CLI main module."""
desktop_dir = tmp_path / "apps" / "desktop"
desktop_dir.mkdir(parents=True)
(desktop_dir / "package.json").write_text("{}", encoding="utf-8")
calls = {"builds": 0, "build_needed": True}
class _FakeMain:
PROJECT_ROOT = tmp_path
@staticmethod
def _resolve_node_runtime_npm():
return "/fake/npm"
@staticmethod
def _desktop_build_needed(*_a, **_kw):
return calls["build_needed"]
@staticmethod
def _run_logged_subprocess(cmd, cwd=None, env=None):
calls["builds"] += 1
return _Result(1, stdout="Error: [stage-native-deps] boom")
monkeypatch.setattr(update_cmd, "_m", lambda: _FakeMain)
monkeypatch.setattr(
"hermes_constants.with_hermes_node_path", lambda: {}, raising=False
)
monkeypatch.setattr(
"hermes_constants.display_hermes_home", lambda: str(tmp_path), raising=False
)
return desktop_dir, calls
def _run(desktop_dir):
return _rebuild_desktop_after_update(
desktop_dir, had_desktop_app_before_update=True
)
def test_failed_rebuild_returns_false_and_keeps_the_retry_hint(desktop_env, capsys):
desktop_dir, calls = desktop_env
assert _run(desktop_dir) is False
assert calls["builds"] == 2
out = capsys.readouterr().out
assert "Desktop build failed" in out
assert "stage-native-deps" in out
assert "Update complete" not in out
def test_successful_rebuild_returns_true(desktop_env, monkeypatch, capsys):
desktop_dir, _calls = desktop_env
builds = []
monkeypatch.setattr(
update_cmd._m(),
"_run_logged_subprocess",
staticmethod(lambda cmd, cwd=None, env=None: builds.append(cmd) or _Result(0)),
)
assert _run(desktop_dir) is True
assert len(builds) == 1
assert "Desktop app up to date" in capsys.readouterr().out
def test_up_to_date_desktop_returns_true_without_spawning(desktop_env):
desktop_dir, calls = desktop_env
calls["build_needed"] = False
assert _run(desktop_dir) is True
assert calls["builds"] == 0
def test_desktop_never_installed_returns_true(tmp_path, monkeypatch):
spawned = []
monkeypatch.setattr(
update_cmd,
"_m",
lambda: type(
"_M",
(),
{
"PROJECT_ROOT": tmp_path,
"_resolve_node_runtime_npm": staticmethod(lambda: "/fake/npm"),
"_run_logged_subprocess": staticmethod(
lambda *a, **k: spawned.append(1) or _Result(0)
),
},
),
)
missing = tmp_path / "apps" / "desktop"
missing.mkdir(parents=True)
assert _run(missing) is True
assert spawned == []
def test_summary_omits_success_banner_when_desktop_rebuild_failed(capsys):
_print_update_summary(
node_failures=[],
desktop_build_ok=False,
pre_update_version="0.20.1",
)
out = capsys.readouterr().out
assert "Update complete" not in out
assert "partially complete" in out
assert "desktop app was not rebuilt" in out
assert "hermes desktop" in out
def test_summary_keeps_success_banner_when_desktop_ok(capsys, monkeypatch):
monkeypatch.setattr(
update_cmd, "_update_complete_message", lambda _v: "✓ Update complete! (v0.20.2)"
)
monkeypatch.setattr(
update_cmd_maint, "_update_complete_message", lambda _v: "✓ Update complete! (v0.20.2)"
)
monkeypatch.setattr(update_cmd, "_branch_head_suffix", lambda *a, **k: "")
monkeypatch.setattr(
update_cmd, "_post_update_sqlite_runtime_status", lambda: (True, None)
)
monkeypatch.setattr(
update_cmd_maint, "_post_update_sqlite_runtime_status", lambda: (True, None)
)
_print_update_summary(
node_failures=[],
desktop_build_ok=True,
pre_update_version="0.20.1",
)
out = capsys.readouterr().out
assert "✓ Update complete!" in out
assert "partially complete" not in out
def test_summary_combines_node_and_desktop_failures(capsys):
_print_update_summary(
node_failures=["dashboard"],
desktop_build_ok=False,
pre_update_version="0.20.1",
)
out = capsys.readouterr().out
assert "Update complete" not in out
assert "dashboard" in out
assert "desktop app was not rebuilt" in out
def test_gateway_exit_code_file_tracks_desktop_rebuild(tmp_path, monkeypatch):
monkeypatch.setattr(update_cmd, "get_hermes_home", lambda: tmp_path)
_write_gateway_update_exit_code(True)
assert (tmp_path / ".update_exit_code").read_text(encoding="utf-8") == "0"
_write_gateway_update_exit_code(False)
assert (tmp_path / ".update_exit_code").read_text(encoding="utf-8") == "1"