test_skills_skip_confirm.py
1 """ 2 Tests for skip_confirm and invalidate_cache behavior in /skills install 3 and /skills uninstall slash commands. 4 5 Slash commands always skip confirmation (input() hangs in TUI). 6 Cache invalidation is deferred by default; --now opts into immediate 7 invalidation (at the cost of breaking prompt cache mid-session). 8 9 Based on PR #1595 by 333Alden333 (salvaged). 10 Updated for PR #3586 (cache-aware install/uninstall). 11 """ 12 13 from unittest.mock import patch, MagicMock 14 15 import pytest 16 17 18 class TestHandleSkillsSlashInstallFlags: 19 """Test flag parsing in handle_skills_slash for install.""" 20 21 def test_yes_flag_sets_skip_confirm(self): 22 from hermes_cli.skills_hub import handle_skills_slash 23 with patch("hermes_cli.skills_hub.do_install") as mock_install: 24 handle_skills_slash("/skills install test/skill --yes") 25 mock_install.assert_called_once() 26 _, kwargs = mock_install.call_args 27 assert kwargs.get("skip_confirm") is True 28 assert kwargs.get("force") is False 29 30 def test_y_flag_sets_skip_confirm(self): 31 from hermes_cli.skills_hub import handle_skills_slash 32 with patch("hermes_cli.skills_hub.do_install") as mock_install: 33 handle_skills_slash("/skills install test/skill -y") 34 mock_install.assert_called_once() 35 _, kwargs = mock_install.call_args 36 assert kwargs.get("skip_confirm") is True 37 38 def test_force_flag_sets_force(self): 39 from hermes_cli.skills_hub import handle_skills_slash 40 with patch("hermes_cli.skills_hub.do_install") as mock_install: 41 handle_skills_slash("/skills install test/skill --force") 42 mock_install.assert_called_once() 43 _, kwargs = mock_install.call_args 44 assert kwargs.get("force") is True 45 # Slash commands always skip confirmation (input() hangs in TUI) 46 assert kwargs.get("skip_confirm") is True 47 48 def test_no_flags_still_skips_confirm(self): 49 """Slash commands always skip confirmation — input() hangs in TUI.""" 50 from hermes_cli.skills_hub import handle_skills_slash 51 with patch("hermes_cli.skills_hub.do_install") as mock_install: 52 handle_skills_slash("/skills install test/skill") 53 mock_install.assert_called_once() 54 _, kwargs = mock_install.call_args 55 assert kwargs.get("force") is False 56 assert kwargs.get("skip_confirm") is True 57 58 def test_default_defers_cache_invalidation(self): 59 """Without --now, cache invalidation is deferred to next session.""" 60 from hermes_cli.skills_hub import handle_skills_slash 61 with patch("hermes_cli.skills_hub.do_install") as mock_install: 62 handle_skills_slash("/skills install test/skill") 63 mock_install.assert_called_once() 64 _, kwargs = mock_install.call_args 65 assert kwargs.get("invalidate_cache") is False 66 67 def test_now_flag_invalidates_cache(self): 68 """--now opts into immediate cache invalidation.""" 69 from hermes_cli.skills_hub import handle_skills_slash 70 with patch("hermes_cli.skills_hub.do_install") as mock_install: 71 handle_skills_slash("/skills install test/skill --now") 72 mock_install.assert_called_once() 73 _, kwargs = mock_install.call_args 74 assert kwargs.get("invalidate_cache") is True 75 76 77 class TestHandleSkillsSlashUninstallFlags: 78 """Test flag parsing in handle_skills_slash for uninstall.""" 79 80 def test_yes_flag_sets_skip_confirm(self): 81 from hermes_cli.skills_hub import handle_skills_slash 82 with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: 83 handle_skills_slash("/skills uninstall test-skill --yes") 84 mock_uninstall.assert_called_once() 85 _, kwargs = mock_uninstall.call_args 86 assert kwargs.get("skip_confirm") is True 87 88 def test_y_flag_sets_skip_confirm(self): 89 from hermes_cli.skills_hub import handle_skills_slash 90 with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: 91 handle_skills_slash("/skills uninstall test-skill -y") 92 mock_uninstall.assert_called_once() 93 _, kwargs = mock_uninstall.call_args 94 assert kwargs.get("skip_confirm") is True 95 96 def test_no_flags_still_skips_confirm(self): 97 """Slash commands always skip confirmation — input() hangs in TUI.""" 98 from hermes_cli.skills_hub import handle_skills_slash 99 with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: 100 handle_skills_slash("/skills uninstall test-skill") 101 mock_uninstall.assert_called_once() 102 _, kwargs = mock_uninstall.call_args 103 assert kwargs.get("skip_confirm") is True 104 105 def test_default_defers_cache_invalidation(self): 106 """Without --now, cache invalidation is deferred to next session.""" 107 from hermes_cli.skills_hub import handle_skills_slash 108 with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: 109 handle_skills_slash("/skills uninstall test-skill") 110 mock_uninstall.assert_called_once() 111 _, kwargs = mock_uninstall.call_args 112 assert kwargs.get("invalidate_cache") is False 113 114 def test_now_flag_invalidates_cache(self): 115 """--now opts into immediate cache invalidation.""" 116 from hermes_cli.skills_hub import handle_skills_slash 117 with patch("hermes_cli.skills_hub.do_uninstall") as mock_uninstall: 118 handle_skills_slash("/skills uninstall test-skill --now") 119 mock_uninstall.assert_called_once() 120 _, kwargs = mock_uninstall.call_args 121 assert kwargs.get("invalidate_cache") is True 122 123 124 class TestDoInstallSkipConfirm: 125 """Test that do_install respects skip_confirm parameter.""" 126 127 @patch("hermes_cli.skills_hub.input", return_value="n") 128 def test_without_skip_confirm_prompts_user(self, mock_input): 129 """Without skip_confirm, input() is called for confirmation.""" 130 from hermes_cli.skills_hub import do_install 131 with patch("hermes_cli.skills_hub._console"), \ 132 patch("tools.skills_hub.ensure_hub_dirs"), \ 133 patch("tools.skills_hub.GitHubAuth"), \ 134 patch("tools.skills_hub.create_source_router") as mock_router, \ 135 patch("hermes_cli.skills_hub._resolve_short_name", return_value="test/skill"), \ 136 patch("hermes_cli.skills_hub._resolve_source_meta_and_bundle") as mock_resolve: 137 138 # Make it return None so we exit early 139 mock_resolve.return_value = (None, None, None) 140 do_install("test-skill", skip_confirm=False) 141 # We don't get to the input() call because resolve returns None, 142 # but the parameter wiring is correct 143 144 145 class TestDoUninstallSkipConfirm: 146 """Test that do_uninstall respects skip_confirm parameter.""" 147 148 def test_skip_confirm_bypasses_input(self): 149 """With skip_confirm=True, input() should not be called.""" 150 from hermes_cli.skills_hub import do_uninstall 151 with patch("hermes_cli.skills_hub._console") as mock_console, \ 152 patch("tools.skills_hub.uninstall_skill", return_value=(True, "Removed")) as mock_uninstall, \ 153 patch("builtins.input") as mock_input: 154 do_uninstall("test-skill", skip_confirm=True) 155 mock_input.assert_not_called() 156 mock_uninstall.assert_called_once_with("test-skill") 157 158 def test_without_skip_confirm_calls_input(self): 159 """Without skip_confirm, input() should be called.""" 160 from hermes_cli.skills_hub import do_uninstall 161 with patch("hermes_cli.skills_hub._console"), \ 162 patch("tools.skills_hub.uninstall_skill", return_value=(True, "Removed")), \ 163 patch("builtins.input", return_value="y") as mock_input: 164 do_uninstall("test-skill", skip_confirm=False) 165 mock_input.assert_called_once() 166 167 def test_without_skip_confirm_cancel(self): 168 """Without skip_confirm, answering 'n' should cancel.""" 169 from hermes_cli.skills_hub import do_uninstall 170 with patch("hermes_cli.skills_hub._console"), \ 171 patch("tools.skills_hub.uninstall_skill") as mock_uninstall, \ 172 patch("builtins.input", return_value="n"): 173 do_uninstall("test-skill", skip_confirm=False) 174 mock_uninstall.assert_not_called()