/ tests / hermes_cli / test_tools_disable_enable.py
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"]