/ tests / hermes_cli / test_skills_skip_confirm.py
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()