"""Tests for hermes_cli.tools_config platform tool persistence.""" from unittest.mock import patch from hermes_cli.tools_config import ( _get_platform_tools, _platform_toolset_summary, _save_platform_tools, _toolset_has_keys, ) def test_get_platform_tools_uses_default_when_platform_not_configured(): config = {} enabled = _get_platform_tools(config, "cli") assert enabled def test_get_platform_tools_preserves_explicit_empty_selection(): config = {"platform_toolsets": {"cli": []}} enabled = _get_platform_tools(config, "cli") assert enabled == set() def test_platform_toolset_summary_uses_explicit_platform_list(): config = {} summary = _platform_toolset_summary(config, platforms=["cli"]) assert set(summary.keys()) == {"cli"} assert summary["cli"] == _get_platform_tools(config, "cli") def test_toolset_has_keys_for_vision_accepts_codex_auth(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path)) (tmp_path / "auth.json").write_text( '{"active_provider":"openai-codex","providers":{"openai-codex":{"tokens":{"access_token": "codex-...oken","refresh_token": "codex-...oken"}}}}' ) monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) monkeypatch.delenv("OPENAI_BASE_URL", raising=False) monkeypatch.delenv("OPENAI_API_KEY", raising=False) monkeypatch.delenv("AUXILIARY_VISION_PROVIDER", raising=False) monkeypatch.delenv("CONTEXT_VISION_PROVIDER", raising=False) assert _toolset_has_keys("vision") is True def test_save_platform_tools_preserves_mcp_server_names(): """Ensure MCP server names are preserved when saving platform tools. Regression test for https://github.com/NousResearch/hermes-agent/issues/1247 """ config = { "platform_toolsets": { "cli": ["web", "terminal", "time", "github", "custom-mcp-server"] } } new_selection = {"web", "browser"} with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "cli", new_selection) saved_toolsets = config["platform_toolsets"]["cli"] assert "time" in saved_toolsets assert "github" in saved_toolsets assert "custom-mcp-server" in saved_toolsets assert "web" in saved_toolsets assert "browser" in saved_toolsets assert "terminal" not in saved_toolsets def test_save_platform_tools_handles_empty_existing_config(): """Saving platform tools works when no existing config exists.""" config = {} with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "telegram", {"web", "terminal"}) saved_toolsets = config["platform_toolsets"]["telegram"] assert "web" in saved_toolsets assert "terminal" in saved_toolsets def test_save_platform_tools_handles_invalid_existing_config(): """Saving platform tools works when existing config is not a list.""" config = { "platform_toolsets": { "cli": "invalid-string-value" } } with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "cli", {"web"}) saved_toolsets = config["platform_toolsets"]["cli"] assert "web" in saved_toolsets def test_save_platform_tools_does_not_preserve_platform_default_toolsets(): """Platform default toolsets (hermes-cli, hermes-telegram, etc.) must NOT be preserved across saves. These "super" toolsets resolve to ALL tools, so if they survive in the config, they silently override any tools the user unchecked. Previously, the preserve filter only excluded configurable toolset keys (web, browser, terminal, etc.) and treated platform defaults as unknown custom entries (like MCP server names), causing them to be kept unconditionally. Regression test: user unchecks image_gen and homeassistant via ``hermes tools``, but hermes-cli stays in the config and re-enables everything on the next read. """ config = { "platform_toolsets": { "cli": [ "browser", "clarify", "code_execution", "cronjob", "delegation", "file", "hermes-cli", # <-- the culprit "memory", "session_search", "skills", "terminal", "todo", "tts", "vision", "web", ] } } # User unchecks image_gen, homeassistant, moa — keeps the rest new_selection = { "browser", "clarify", "code_execution", "cronjob", "delegation", "file", "memory", "session_search", "skills", "terminal", "todo", "tts", "vision", "web", } with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "cli", new_selection) saved = config["platform_toolsets"]["cli"] # hermes-cli must NOT survive — it's a platform default, not an MCP server assert "hermes-cli" not in saved # The individual toolset keys the user selected must be present assert "web" in saved assert "terminal" in saved assert "browser" in saved # Tools the user unchecked must NOT be present assert "image_gen" not in saved assert "homeassistant" not in saved assert "moa" not in saved def test_save_platform_tools_does_not_preserve_hermes_telegram(): """Same bug for Telegram — hermes-telegram must not be preserved.""" config = { "platform_toolsets": { "telegram": [ "browser", "file", "hermes-telegram", "terminal", "web", ] } } new_selection = {"browser", "file", "terminal", "web"} with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "telegram", new_selection) saved = config["platform_toolsets"]["telegram"] assert "hermes-telegram" not in saved assert "web" in saved def test_save_platform_tools_still_preserves_mcp_with_platform_default_present(): """MCP server names must still be preserved even when platform defaults are being stripped out.""" config = { "platform_toolsets": { "cli": [ "web", "terminal", "hermes-cli", "my-mcp-server", "github-tools", ] } } new_selection = {"web", "browser"} with patch("hermes_cli.tools_config.save_config"): _save_platform_tools(config, "cli", new_selection) saved = config["platform_toolsets"]["cli"] # MCP servers preserved assert "my-mcp-server" in saved assert "github-tools" in saved # Platform default stripped assert "hermes-cli" not in saved # User selections present assert "web" in saved assert "browser" in saved # Deselected configurable toolset removed assert "terminal" not in saved