"""Tests for ``hermes update`` / ``--check`` inside the Docker container. Background: ``.dockerignore`` excludes ``.git``, so the existing git-pull update path can never succeed inside the published image. Before this fix, ``hermes update`` would fall through to ``"✗ Not a git repository. Please reinstall: curl ... install.sh"`` — that script installs a *new* host-side Hermes, not an update to the running container, so the message was actively misleading. These tests pin the new behaviour: when ``detect_install_method`` reports ``"docker"`` (stamped by ``docker/stage2-hook.sh``), both the apply path (``cmd_update``) and the check path (``_cmd_update_check``) print the ``docker pull`` guidance from ``format_docker_update_message`` and exit with status 1, without running ``git fetch`` / ``subprocess.run``. """ from __future__ import annotations from types import SimpleNamespace from unittest.mock import patch import pytest from hermes_cli.main import _cmd_update_check, cmd_update # ---------- cmd_update (apply path) ---------- @patch("hermes_cli.config.is_managed", return_value=False) @patch("hermes_cli.config.detect_install_method", return_value="docker") @patch("subprocess.run") def test_cmd_update_in_docker_prints_guidance_and_exits( mock_run, _mock_method, _mock_managed, capsys ): """``hermes update`` inside Docker → friendly message + exit 1, no git calls.""" with pytest.raises(SystemExit) as excinfo: cmd_update(SimpleNamespace(check=False)) assert excinfo.value.code == 1 out = capsys.readouterr().out # Spot-check the key guidance — exhaustive wording is locked in by the # config-module test below to keep these CLI tests resilient to copy edits. assert "doesn't apply inside the Docker container" in out assert "docker pull nousresearch/hermes-agent:latest" in out # No git invocations — the early-return must beat every git command. git_calls = [c for c in mock_run.call_args_list if c.args and c.args[0] and "git" in str(c.args[0][0])] assert git_calls == [], f"expected no git calls, got: {git_calls}" # ---------- _cmd_update_check (check path, direct entry) ---------- # ---------- Non-Docker installs unaffected ---------- # ---------- format_docker_update_message — content lock ---------- def test_format_docker_update_message_contents(): """Lock in the high-value content of the Docker update message. These are the bits a user actually needs to act on; if any of them disappear in a copy edit, the message has lost its value. Specific wording around them is free to evolve (we don't assert full text). """ from hermes_cli.config import format_docker_update_message msg = format_docker_update_message() # Primary command — the entire reason this message exists. assert "docker pull nousresearch/hermes-agent:latest" in msg # The four key concepts the message must cover: assert "restart" in msg.lower(), "must explain that a restart is required" assert "--version" in msg, "must show how to verify the new version" assert ":latest" in msg, "must mention tag pinning caveat" assert "HERMES_HOME" in msg or "/opt/data" in msg, ( "must address config persistence across upgrades" ) # Acknowledges that forks exist (build-your-own-image escape hatch). assert "fork" in msg.lower() or "Dockerfile" in msg