test_cli_tools_command.py
1 """Tests for /tools slash command handler in the interactive CLI.""" 2 3 from unittest.mock import MagicMock, patch, call 4 5 from cli import HermesCLI 6 7 8 def _make_cli(enabled_toolsets=None): 9 """Build a minimal HermesCLI stub without running __init__.""" 10 cli_obj = HermesCLI.__new__(HermesCLI) 11 cli_obj.enabled_toolsets = set(enabled_toolsets or ["web", "memory"]) 12 cli_obj._command_running = False 13 cli_obj.console = MagicMock() 14 return cli_obj 15 16 17 # ── /tools (no subcommand) ────────────────────────────────────────────────── 18 19 20 class TestToolsSlashNoSubcommand: 21 22 def test_bare_tools_shows_tool_list(self): 23 cli_obj = _make_cli() 24 with patch.object(cli_obj, "show_tools") as mock_show: 25 cli_obj._handle_tools_command("/tools") 26 mock_show.assert_called_once() 27 28 def test_unknown_subcommand_falls_back_to_show_tools(self): 29 cli_obj = _make_cli() 30 with patch.object(cli_obj, "show_tools") as mock_show: 31 cli_obj._handle_tools_command("/tools foobar") 32 mock_show.assert_called_once() 33 34 35 # ── /tools list ───────────────────────────────────────────────────────────── 36 37 38 class TestToolsSlashList: 39 40 def test_list_calls_backend(self, capsys): 41 cli_obj = _make_cli() 42 with patch("hermes_cli.tools_config.load_config", 43 return_value={"platform_toolsets": {"cli": ["web"]}}), \ 44 patch("hermes_cli.tools_config.save_config"): 45 cli_obj._handle_tools_command("/tools list") 46 out = capsys.readouterr().out 47 assert "web" in out 48 49 def test_list_does_not_modify_enabled_toolsets(self): 50 """List is read-only — self.enabled_toolsets must not change.""" 51 cli_obj = _make_cli(["web", "memory"]) 52 with patch("hermes_cli.tools_config.load_config", 53 return_value={"platform_toolsets": {"cli": ["web"]}}): 54 cli_obj._handle_tools_command("/tools list") 55 assert cli_obj.enabled_toolsets == {"web", "memory"} 56 57 58 # ── /tools disable (session reset) ────────────────────────────────────────── 59 60 61 class TestToolsSlashDisableWithReset: 62 63 def test_disable_applies_directly_and_resets_session(self): 64 """Disable applies immediately (no confirmation prompt) and resets session.""" 65 cli_obj = _make_cli(["web", "memory"]) 66 with patch("hermes_cli.tools_config.load_config", 67 return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \ 68 patch("hermes_cli.tools_config.save_config"), \ 69 patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \ 70 patch("hermes_cli.config.load_config", return_value={}), \ 71 patch.object(cli_obj, "new_session") as mock_reset: 72 cli_obj._handle_tools_command("/tools disable web") 73 mock_reset.assert_called_once() 74 assert "web" not in cli_obj.enabled_toolsets 75 76 def test_disable_does_not_prompt_for_confirmation(self): 77 """Disable no longer uses input() — it applies directly.""" 78 cli_obj = _make_cli(["web", "memory"]) 79 with patch("hermes_cli.tools_config.load_config", 80 return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \ 81 patch("hermes_cli.tools_config.save_config"), \ 82 patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \ 83 patch("hermes_cli.config.load_config", return_value={}), \ 84 patch.object(cli_obj, "new_session"), \ 85 patch("builtins.input") as mock_input: 86 cli_obj._handle_tools_command("/tools disable web") 87 mock_input.assert_not_called() 88 89 def test_disable_always_resets_session(self): 90 """Even without a confirmation prompt, disable always resets the session.""" 91 cli_obj = _make_cli(["web", "memory"]) 92 with patch("hermes_cli.tools_config.load_config", 93 return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \ 94 patch("hermes_cli.tools_config.save_config"), \ 95 patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \ 96 patch("hermes_cli.config.load_config", return_value={}), \ 97 patch.object(cli_obj, "new_session") as mock_reset: 98 cli_obj._handle_tools_command("/tools disable web") 99 mock_reset.assert_called_once() 100 101 def test_disable_missing_name_prints_usage(self, capsys): 102 cli_obj = _make_cli() 103 cli_obj._handle_tools_command("/tools disable") 104 out = capsys.readouterr().out 105 assert "Usage" in out 106 107 108 # ── /tools enable (session reset) ─────────────────────────────────────────── 109 110 111 class TestToolsSlashEnableWithReset: 112 113 def test_enable_applies_directly_and_resets_session(self): 114 """Enable applies immediately (no confirmation prompt) and resets session.""" 115 cli_obj = _make_cli(["memory"]) 116 with patch("hermes_cli.tools_config.load_config", 117 return_value={"platform_toolsets": {"cli": ["memory"]}}), \ 118 patch("hermes_cli.tools_config.save_config"), \ 119 patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory", "web"}), \ 120 patch("hermes_cli.config.load_config", return_value={}), \ 121 patch.object(cli_obj, "new_session") as mock_reset: 122 cli_obj._handle_tools_command("/tools enable web") 123 mock_reset.assert_called_once() 124 assert "web" in cli_obj.enabled_toolsets 125 126 def test_enable_missing_name_prints_usage(self, capsys): 127 cli_obj = _make_cli() 128 cli_obj._handle_tools_command("/tools enable") 129 out = capsys.readouterr().out 130 assert "Usage" in out