test_tools_disable_enable.py
1 """Tests for hermes tools disable/enable/list command (backend).""" 2 from argparse import Namespace 3 from unittest.mock import patch 4 5 from hermes_cli.tools_config import tools_disable_enable_command 6 7 8 # ── Built-in toolset disable ──────────────────────────────────────────────── 9 10 11 class TestToolsDisableBuiltin: 12 13 def test_disable_removes_toolset_from_platform(self): 14 config = {"platform_toolsets": {"cli": ["web", "memory", "terminal"]}} 15 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 16 patch("hermes_cli.tools_config.save_config") as mock_save: 17 tools_disable_enable_command(Namespace(tools_action="disable", names=["web"], platform="cli")) 18 saved = mock_save.call_args[0][0] 19 assert "web" not in saved["platform_toolsets"]["cli"] 20 assert "memory" in saved["platform_toolsets"]["cli"] 21 22 def test_disable_multiple_toolsets(self): 23 config = {"platform_toolsets": {"cli": ["web", "memory", "terminal"]}} 24 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 25 patch("hermes_cli.tools_config.save_config") as mock_save: 26 tools_disable_enable_command(Namespace(tools_action="disable", names=["web", "memory"], platform="cli")) 27 saved = mock_save.call_args[0][0] 28 assert "web" not in saved["platform_toolsets"]["cli"] 29 assert "memory" not in saved["platform_toolsets"]["cli"] 30 assert "terminal" in saved["platform_toolsets"]["cli"] 31 32 def test_disable_already_absent_is_idempotent(self): 33 config = {"platform_toolsets": {"cli": ["memory"]}} 34 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 35 patch("hermes_cli.tools_config.save_config") as mock_save: 36 tools_disable_enable_command(Namespace(tools_action="disable", names=["web"], platform="cli")) 37 saved = mock_save.call_args[0][0] 38 assert "web" not in saved["platform_toolsets"]["cli"] 39 40 41 # ── Built-in toolset enable ───────────────────────────────────────────────── 42 43 44 class TestToolsEnableBuiltin: 45 46 def test_enable_adds_toolset_to_platform(self): 47 config = {"platform_toolsets": {"cli": ["memory"]}} 48 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 49 patch("hermes_cli.tools_config.save_config") as mock_save: 50 tools_disable_enable_command(Namespace(tools_action="enable", names=["web"], platform="cli")) 51 saved = mock_save.call_args[0][0] 52 assert "web" in saved["platform_toolsets"]["cli"] 53 54 def test_enable_already_present_is_idempotent(self): 55 config = {"platform_toolsets": {"cli": ["web"]}} 56 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 57 patch("hermes_cli.tools_config.save_config") as mock_save: 58 tools_disable_enable_command(Namespace(tools_action="enable", names=["web"], platform="cli")) 59 saved = mock_save.call_args[0][0] 60 assert saved["platform_toolsets"]["cli"].count("web") == 1 61 62 63 # ── MCP tool disable ──────────────────────────────────────────────────────── 64 65 66 class TestToolsDisableMcp: 67 68 def test_disable_adds_to_exclude_list(self): 69 config = {"mcp_servers": {"github": {"command": "npx"}}} 70 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 71 patch("hermes_cli.tools_config.save_config") as mock_save: 72 tools_disable_enable_command( 73 Namespace(tools_action="disable", names=["github:create_issue"], platform="cli") 74 ) 75 saved = mock_save.call_args[0][0] 76 assert "create_issue" in saved["mcp_servers"]["github"]["tools"]["exclude"] 77 78 def test_disable_already_excluded_is_idempotent(self): 79 config = {"mcp_servers": {"github": {"tools": {"exclude": ["create_issue"]}}}} 80 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 81 patch("hermes_cli.tools_config.save_config") as mock_save: 82 tools_disable_enable_command( 83 Namespace(tools_action="disable", names=["github:create_issue"], platform="cli") 84 ) 85 saved = mock_save.call_args[0][0] 86 assert saved["mcp_servers"]["github"]["tools"]["exclude"].count("create_issue") == 1 87 88 def test_disable_unknown_server_prints_error(self, capsys): 89 config = {"mcp_servers": {}} 90 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 91 patch("hermes_cli.tools_config.save_config"): 92 tools_disable_enable_command( 93 Namespace(tools_action="disable", names=["unknown:tool"], platform="cli") 94 ) 95 out = capsys.readouterr().out 96 assert "MCP server 'unknown' not found in config" in out 97 98 99 # ── MCP tool enable ────────────────────────────────────────────────────────── 100 101 102 class TestToolsEnableMcp: 103 104 def test_enable_removes_from_exclude_list(self): 105 config = {"mcp_servers": {"github": {"tools": {"exclude": ["create_issue", "delete_branch"]}}}} 106 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 107 patch("hermes_cli.tools_config.save_config") as mock_save: 108 tools_disable_enable_command( 109 Namespace(tools_action="enable", names=["github:create_issue"], platform="cli") 110 ) 111 saved = mock_save.call_args[0][0] 112 assert "create_issue" not in saved["mcp_servers"]["github"]["tools"]["exclude"] 113 assert "delete_branch" in saved["mcp_servers"]["github"]["tools"]["exclude"] 114 115 116 # ── Mixed targets ──────────────────────────────────────────────────────────── 117 118 119 class TestToolsMixedTargets: 120 121 def test_disable_builtin_and_mcp_together(self): 122 config = { 123 "platform_toolsets": {"cli": ["web", "memory"]}, 124 "mcp_servers": {"github": {"command": "npx"}}, 125 } 126 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 127 patch("hermes_cli.tools_config.save_config") as mock_save: 128 tools_disable_enable_command(Namespace( 129 tools_action="disable", 130 names=["web", "github:create_issue"], 131 platform="cli", 132 )) 133 saved = mock_save.call_args[0][0] 134 assert "web" not in saved["platform_toolsets"]["cli"] 135 assert "create_issue" in saved["mcp_servers"]["github"]["tools"]["exclude"] 136 137 def test_builtin_toggle_does_not_persist_implicit_mcp_defaults(self): 138 config = { 139 "platform_toolsets": {"cli": ["web", "memory"]}, 140 "mcp_servers": {"exa": {"url": "https://mcp.exa.ai/mcp"}}, 141 } 142 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 143 patch("hermes_cli.tools_config.save_config") as mock_save: 144 tools_disable_enable_command(Namespace( 145 tools_action="disable", 146 names=["web"], 147 platform="cli", 148 )) 149 saved = mock_save.call_args[0][0] 150 assert "web" not in saved["platform_toolsets"]["cli"] 151 assert "memory" in saved["platform_toolsets"]["cli"] 152 assert "exa" not in saved["platform_toolsets"]["cli"] 153 154 155 # ── List output ────────────────────────────────────────────────────────────── 156 157 158 class TestToolsList: 159 160 def test_list_shows_enabled_toolsets(self, capsys): 161 config = {"platform_toolsets": {"cli": ["web", "memory"]}} 162 with patch("hermes_cli.tools_config.load_config", return_value=config): 163 tools_disable_enable_command(Namespace(tools_action="list", platform="cli")) 164 out = capsys.readouterr().out 165 assert "web" in out 166 assert "memory" in out 167 168 def test_list_shows_mcp_excluded_tools(self, capsys): 169 config = { 170 "mcp_servers": {"github": {"tools": {"exclude": ["create_issue"]}}}, 171 } 172 with patch("hermes_cli.tools_config.load_config", return_value=config): 173 tools_disable_enable_command(Namespace(tools_action="list", platform="cli")) 174 out = capsys.readouterr().out 175 assert "github" in out 176 assert "create_issue" in out 177 178 179 # ── Validation ─────────────────────────────────────────────────────────────── 180 181 182 class TestToolsValidation: 183 184 def test_unknown_platform_prints_error(self, capsys): 185 config = {} 186 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 187 patch("hermes_cli.tools_config.save_config"): 188 tools_disable_enable_command( 189 Namespace(tools_action="disable", names=["web"], platform="invalid_platform") 190 ) 191 out = capsys.readouterr().out 192 assert "Unknown platform 'invalid_platform'" in out 193 194 def test_unknown_toolset_prints_error(self, capsys): 195 config = {"platform_toolsets": {"cli": ["web"]}} 196 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 197 patch("hermes_cli.tools_config.save_config"): 198 tools_disable_enable_command( 199 Namespace(tools_action="disable", names=["nonexistent_toolset"], platform="cli") 200 ) 201 out = capsys.readouterr().out 202 assert "Unknown toolset 'nonexistent_toolset'" in out 203 204 def test_unknown_toolset_does_not_corrupt_config(self): 205 config = {"platform_toolsets": {"cli": ["web", "memory"]}} 206 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 207 patch("hermes_cli.tools_config.save_config") as mock_save: 208 tools_disable_enable_command( 209 Namespace(tools_action="disable", names=["nonexistent_toolset"], platform="cli") 210 ) 211 saved = mock_save.call_args[0][0] 212 assert "web" in saved["platform_toolsets"]["cli"] 213 assert "memory" in saved["platform_toolsets"]["cli"] 214 215 def test_mixed_valid_and_invalid_applies_valid_only(self): 216 config = {"platform_toolsets": {"cli": ["web", "memory"]}} 217 with patch("hermes_cli.tools_config.load_config", return_value=config), \ 218 patch("hermes_cli.tools_config.save_config") as mock_save: 219 tools_disable_enable_command( 220 Namespace(tools_action="disable", names=["web", "bad_toolset"], platform="cli") 221 ) 222 saved = mock_save.call_args[0][0] 223 assert "web" not in saved["platform_toolsets"]["cli"] 224 assert "memory" in saved["platform_toolsets"]["cli"]