/ tests / hermes_cli / test_banner.py
test_banner.py
  1  """Tests for banner toolset name normalization and skin color usage."""
  2  
  3  from unittest.mock import patch
  4  
  5  from rich.console import Console
  6  
  7  import hermes_cli.banner as banner
  8  import model_tools
  9  import tools.mcp_tool
 10  
 11  
 12  def test_display_toolset_name_strips_legacy_suffix():
 13      assert banner._display_toolset_name("homeassistant_tools") == "homeassistant"
 14      assert banner._display_toolset_name("honcho_tools") == "honcho"
 15      assert banner._display_toolset_name("web_tools") == "web"
 16  
 17  
 18  def test_display_toolset_name_preserves_clean_names():
 19      assert banner._display_toolset_name("browser") == "browser"
 20      assert banner._display_toolset_name("file") == "file"
 21      assert banner._display_toolset_name("terminal") == "terminal"
 22  
 23  
 24  def test_display_toolset_name_handles_empty():
 25      assert banner._display_toolset_name("") == "unknown"
 26      assert banner._display_toolset_name(None) == "unknown"
 27  
 28  
 29  def test_build_welcome_banner_uses_normalized_toolset_names():
 30      """Unavailable toolsets should not have '_tools' appended in banner output."""
 31      with (
 32          patch.object(
 33              model_tools,
 34              "check_tool_availability",
 35              return_value=(
 36                  ["web"],
 37                  [
 38                      {"name": "homeassistant", "tools": ["ha_call_service"]},
 39                      {"name": "honcho", "tools": ["honcho_conclude"]},
 40                  ],
 41              ),
 42          ),
 43          patch.object(banner, "get_available_skills", return_value={}),
 44          patch.object(banner, "get_update_result", return_value=None),
 45          patch.object(tools.mcp_tool, "get_mcp_status", return_value=[]),
 46      ):
 47          console = Console(
 48              record=True, force_terminal=False, color_system=None, width=160
 49          )
 50          banner.build_welcome_banner(
 51              console=console,
 52              model="anthropic/test-model",
 53              cwd="/tmp/project",
 54              tools=[
 55                  {"function": {"name": "web_search"}},
 56                  {"function": {"name": "read_file"}},
 57              ],
 58              get_toolset_for_tool=lambda name: {
 59                  "web_search": "web_tools",
 60                  "read_file": "file",
 61              }.get(name),
 62          )
 63  
 64      output = console.export_text()
 65      assert "homeassistant:" in output
 66      assert "honcho:" in output
 67      assert "web:" in output
 68      assert "homeassistant_tools:" not in output
 69      assert "honcho_tools:" not in output
 70      assert "web_tools:" not in output
 71  
 72  
 73  def test_build_welcome_banner_title_is_hyperlinked_to_release():
 74      """Panel title (version label) is wrapped in an OSC-8 hyperlink to the GitHub release."""
 75      import io
 76      from unittest.mock import patch as _patch
 77      import hermes_cli.banner as _banner
 78      import model_tools as _mt
 79      import tools.mcp_tool as _mcp
 80  
 81      _banner._latest_release_cache = None
 82      tag_url = ("v2026.4.23", "https://github.com/NousResearch/hermes-agent/releases/tag/v2026.4.23")
 83  
 84      buf = io.StringIO()
 85      with (
 86          _patch.object(_mt, "check_tool_availability", return_value=(["web"], [])),
 87          _patch.object(_banner, "get_available_skills", return_value={}),
 88          _patch.object(_banner, "get_update_result", return_value=None),
 89          _patch.object(_mcp, "get_mcp_status", return_value=[]),
 90          _patch.object(_banner, "get_latest_release_tag", return_value=tag_url),
 91      ):
 92          console = Console(file=buf, force_terminal=True, color_system="truecolor", width=160)
 93          _banner.build_welcome_banner(
 94              console=console, model="x", cwd="/tmp",
 95              session_id="abc123",
 96              tools=[{"function": {"name": "read_file"}}],
 97              get_toolset_for_tool=lambda n: "file",
 98          )
 99  
100      raw = buf.getvalue()
101      # The existing version label must still be present in the title
102      assert "Hermes Agent v" in raw, "Version label missing from title"
103      # OSC-8 hyperlink escape sequence present with the release URL
104      assert "\x1b]8;" in raw, "OSC-8 hyperlink not emitted"
105      assert "releases/tag/v2026.4.23" in raw, "Release URL missing from banner output"
106  
107  
108  def test_build_welcome_banner_title_falls_back_when_no_tag():
109      """Without a resolvable tag, the panel title renders as plain text (no hyperlink escape)."""
110      import io
111      from unittest.mock import patch as _patch
112      import hermes_cli.banner as _banner
113      import model_tools as _mt
114      import tools.mcp_tool as _mcp
115  
116      _banner._latest_release_cache = None
117      buf = io.StringIO()
118      with (
119          _patch.object(_mt, "check_tool_availability", return_value=(["web"], [])),
120          _patch.object(_banner, "get_available_skills", return_value={}),
121          _patch.object(_banner, "get_update_result", return_value=None),
122          _patch.object(_mcp, "get_mcp_status", return_value=[]),
123          _patch.object(_banner, "get_latest_release_tag", return_value=None),
124      ):
125          console = Console(file=buf, force_terminal=True, color_system="truecolor", width=160)
126          _banner.build_welcome_banner(
127              console=console, model="x", cwd="/tmp",
128              session_id="abc123",
129              tools=[{"function": {"name": "read_file"}}],
130              get_toolset_for_tool=lambda n: "file",
131          )
132  
133      raw = buf.getvalue()
134      assert "Hermes Agent v" in raw, "Version label missing from title"
135      assert "\x1b]8;" not in raw, "OSC-8 hyperlink should not be emitted without a tag"