get_mcp_status() treated every non-connected server as a failure, so a server configured with enabled: false rendered as red '— failed' in the startup banner even though it was intentionally off. Add a 'disabled' field derived from the enabled flag and render disabled servers dim as '— disabled' instead.
170 lines
6.6 KiB
Python
170 lines
6.6 KiB
Python
"""Tests for banner toolset name normalization and skin color usage."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from rich.console import Console
|
|
|
|
import hermes_cli.banner as banner
|
|
import model_tools
|
|
import tools.mcp_tool
|
|
|
|
|
|
def test_display_toolset_name_strips_legacy_suffix():
|
|
assert banner._display_toolset_name("homeassistant_tools") == "homeassistant"
|
|
assert banner._display_toolset_name("honcho_tools") == "honcho"
|
|
assert banner._display_toolset_name("web_tools") == "web"
|
|
|
|
|
|
def test_display_toolset_name_preserves_clean_names():
|
|
assert banner._display_toolset_name("browser") == "browser"
|
|
assert banner._display_toolset_name("file") == "file"
|
|
assert banner._display_toolset_name("terminal") == "terminal"
|
|
|
|
|
|
def test_display_toolset_name_handles_empty():
|
|
assert banner._display_toolset_name("") == "unknown"
|
|
assert banner._display_toolset_name(None) == "unknown"
|
|
|
|
|
|
def test_build_welcome_banner_uses_normalized_toolset_names():
|
|
"""Unavailable toolsets should not have '_tools' appended in banner output."""
|
|
with (
|
|
patch.object(
|
|
model_tools,
|
|
"check_tool_availability",
|
|
return_value=(
|
|
["web"],
|
|
[
|
|
{"name": "homeassistant", "tools": ["ha_call_service"]},
|
|
{"name": "honcho", "tools": ["honcho_conclude"]},
|
|
],
|
|
),
|
|
),
|
|
patch.object(banner, "get_available_skills", return_value={}),
|
|
patch.object(banner, "get_update_result", return_value=None),
|
|
patch.object(tools.mcp_tool, "get_mcp_status", return_value=[]),
|
|
):
|
|
console = Console(
|
|
record=True, force_terminal=False, color_system=None, width=160
|
|
)
|
|
banner.build_welcome_banner(
|
|
console=console,
|
|
model="anthropic/test-model",
|
|
cwd="/tmp/project",
|
|
tools=[
|
|
{"function": {"name": "web_search"}},
|
|
{"function": {"name": "read_file"}},
|
|
],
|
|
get_toolset_for_tool=lambda name: {
|
|
"web_search": "web_tools",
|
|
"read_file": "file",
|
|
}.get(name),
|
|
)
|
|
|
|
output = console.export_text()
|
|
assert "homeassistant:" in output
|
|
assert "honcho:" in output
|
|
assert "web:" in output
|
|
assert "homeassistant_tools:" not in output
|
|
assert "honcho_tools:" not in output
|
|
assert "web_tools:" not in output
|
|
|
|
|
|
def test_build_welcome_banner_title_is_hyperlinked_to_release():
|
|
"""Panel title (version label) is wrapped in an OSC-8 hyperlink to the GitHub release."""
|
|
import io
|
|
from unittest.mock import patch as _patch
|
|
import hermes_cli.banner as _banner
|
|
import model_tools as _mt
|
|
import tools.mcp_tool as _mcp
|
|
|
|
_banner._latest_release_cache = None
|
|
tag_url = ("v2026.4.23", "https://github.com/NousResearch/hermes-agent/releases/tag/v2026.4.23")
|
|
|
|
buf = io.StringIO()
|
|
with (
|
|
_patch.object(_mt, "check_tool_availability", return_value=(["web"], [])),
|
|
_patch.object(_banner, "get_available_skills", return_value={}),
|
|
_patch.object(_banner, "get_update_result", return_value=None),
|
|
_patch.object(_mcp, "get_mcp_status", return_value=[]),
|
|
_patch.object(_banner, "get_latest_release_tag", return_value=tag_url),
|
|
):
|
|
console = Console(file=buf, force_terminal=True, color_system="truecolor", width=160)
|
|
_banner.build_welcome_banner(
|
|
console=console, model="x", cwd="/tmp",
|
|
session_id="abc123",
|
|
tools=[{"function": {"name": "read_file"}}],
|
|
get_toolset_for_tool=lambda n: "file",
|
|
)
|
|
|
|
raw = buf.getvalue()
|
|
# The existing version label must still be present in the title
|
|
assert "Hermes Agent v" in raw, "Version label missing from title"
|
|
# OSC-8 hyperlink escape sequence present with the release URL
|
|
assert "\x1b]8;" in raw, "OSC-8 hyperlink not emitted"
|
|
assert "releases/tag/v2026.4.23" in raw, "Release URL missing from banner output"
|
|
|
|
|
|
def test_build_welcome_banner_title_falls_back_when_no_tag():
|
|
"""Without a resolvable tag, the panel title renders as plain text (no hyperlink escape)."""
|
|
import io
|
|
from unittest.mock import patch as _patch
|
|
import hermes_cli.banner as _banner
|
|
import model_tools as _mt
|
|
import tools.mcp_tool as _mcp
|
|
|
|
_banner._latest_release_cache = None
|
|
buf = io.StringIO()
|
|
with (
|
|
_patch.object(_mt, "check_tool_availability", return_value=(["web"], [])),
|
|
_patch.object(_banner, "get_available_skills", return_value={}),
|
|
_patch.object(_banner, "get_update_result", return_value=None),
|
|
_patch.object(_mcp, "get_mcp_status", return_value=[]),
|
|
_patch.object(_banner, "get_latest_release_tag", return_value=None),
|
|
):
|
|
console = Console(file=buf, force_terminal=True, color_system="truecolor", width=160)
|
|
_banner.build_welcome_banner(
|
|
console=console, model="x", cwd="/tmp",
|
|
session_id="abc123",
|
|
tools=[{"function": {"name": "read_file"}}],
|
|
get_toolset_for_tool=lambda n: "file",
|
|
)
|
|
|
|
raw = buf.getvalue()
|
|
assert "Hermes Agent v" in raw, "Version label missing from title"
|
|
assert "\x1b]8;" not in raw, "OSC-8 hyperlink should not be emitted without a tag"
|
|
|
|
|
|
def test_build_welcome_banner_disabled_mcp_shows_disabled_not_failed():
|
|
"""A disabled MCP server renders '— disabled' (dim), not '— failed' (red)."""
|
|
with (
|
|
patch.object(model_tools, "check_tool_availability", return_value=(["web"], [])),
|
|
patch.object(banner, "get_available_skills", return_value={}),
|
|
patch.object(banner, "get_update_result", return_value=None),
|
|
patch.object(
|
|
tools.mcp_tool,
|
|
"get_mcp_status",
|
|
return_value=[
|
|
{"name": "linear", "transport": "http", "tools": 0,
|
|
"connected": False, "disabled": True},
|
|
{"name": "broken", "transport": "stdio", "tools": 0,
|
|
"connected": False, "disabled": False},
|
|
],
|
|
),
|
|
):
|
|
console = Console(record=True, force_terminal=False, color_system=None, width=160)
|
|
banner.build_welcome_banner(
|
|
console=console, model="anthropic/test-model", cwd="/tmp/project",
|
|
tools=[{"function": {"name": "read_file"}}],
|
|
get_toolset_for_tool=lambda n: "file",
|
|
)
|
|
|
|
output = console.export_text()
|
|
# Disabled server is labeled "disabled", not "failed"
|
|
assert "linear" in output
|
|
assert "disabled" in output
|
|
# A genuinely unreachable server still reads "failed"
|
|
assert "broken" in output
|
|
assert "failed" in output
|
|
|