/ tests / hermes_cli / test_setup_irc.py
test_setup_irc.py
  1  """Tests for IRC gateway configuration via `hermes setup gateway` UI.
  2  
  3  Covers the full plugin-platform discovery → status → configure flow so that
  4  a fresh Hermes install (no state, no env vars) can set up IRC through the
  5  interactive setup menus.
  6  """
  7  
  8  import os
  9  import pytest
 10  
 11  from gateway.platform_registry import PlatformEntry, platform_registry
 12  
 13  
 14  def _register_irc_platform(**overrides):
 15      """Manually register the IRC platform entry as if discover_plugins() found it.
 16  
 17      Tests run outside the normal plugin-discovery path, so we inject the entry
 18      directly into the singleton registry and yield its dict shape.
 19      """
 20      defaults = dict(
 21          name="irc",
 22          label="IRC",
 23          adapter_factory=lambda cfg: None,
 24          check_fn=lambda: bool(os.getenv("IRC_SERVER", "") and os.getenv("IRC_CHANNEL", "")),
 25          validate_config=None,
 26          required_env=["IRC_SERVER", "IRC_CHANNEL", "IRC_NICKNAME"],
 27          install_hint="No extra packages needed (stdlib only)",
 28          setup_fn=lambda: None,
 29          source="plugin",
 30          plugin_name="irc_platform",
 31          allowed_users_env="IRC_ALLOWED_USERS",
 32          allow_all_env="IRC_ALLOW_ALL_USERS",
 33          max_message_length=450,
 34          pii_safe=False,
 35          emoji="💬",
 36          allow_update_command=True,
 37          platform_hint="You are chatting via IRC.",
 38      )
 39      defaults.update(overrides)
 40      entry = PlatformEntry(**defaults)
 41      platform_registry.register(entry)
 42      return {
 43          "key": entry.name,
 44          "label": entry.label,
 45          "emoji": entry.emoji,
 46          "token_var": entry.required_env[0] if entry.required_env else "",
 47          "install_hint": entry.install_hint,
 48          "_registry_entry": entry,
 49      }
 50  
 51  
 52  def _unregister_irc_platform():
 53      platform_registry.unregister("irc")
 54  
 55  
 56  # ── Fresh-install discovery ─────────────────────────────────────────────────
 57  
 58  
 59  class TestIRCFreshInstallDiscovery:
 60      """IRC appears in the setup menu on a brand-new Hermes install."""
 61  
 62      def test_irc_appears_in_all_platforms(self, monkeypatch):
 63          """When the IRC plugin is registered, _all_platforms() surfaces it."""
 64          import hermes_cli.gateway as gateway_mod
 65  
 66          _register_irc_platform()
 67          try:
 68              # Ensure no stale env vars leak in
 69              for key in ("IRC_SERVER", "IRC_CHANNEL", "IRC_NICKNAME"):
 70                  monkeypatch.delenv(key, raising=False)
 71  
 72              platforms = gateway_mod._all_platforms()
 73              keys = {p["key"] for p in platforms}
 74              assert "irc" in keys
 75  
 76              irc_plat = next(p for p in platforms if p["key"] == "irc")
 77              assert irc_plat["label"] == "IRC"
 78              assert irc_plat["emoji"] == "💬"
 79          finally:
 80              _unregister_irc_platform()
 81  
 82      def test_irc_status_not_configured_when_fresh(self, monkeypatch):
 83          """On a fresh install with no env vars, IRC shows 'not configured'."""
 84          import hermes_cli.gateway as gateway_mod
 85  
 86          plat = _register_irc_platform()
 87          try:
 88              for key in ("IRC_SERVER", "IRC_CHANNEL", "IRC_NICKNAME"):
 89                  monkeypatch.delenv(key, raising=False)
 90  
 91              status = gateway_mod._platform_status(plat)
 92              assert status == "not configured"
 93          finally:
 94              _unregister_irc_platform()
 95  
 96      def test_irc_status_configured_when_env_set(self, monkeypatch):
 97          """After the user sets IRC_SERVER and IRC_CHANNEL, status is 'configured'."""
 98          import hermes_cli.gateway as gateway_mod
 99  
100          plat = _register_irc_platform()
101          try:
102              monkeypatch.setenv("IRC_SERVER", "irc.libera.chat")
103              monkeypatch.setenv("IRC_CHANNEL", "#hermes")
104              monkeypatch.setenv("IRC_NICKNAME", "hermes-bot")
105  
106              status = gateway_mod._platform_status(plat)
107              assert status == "configured"
108          finally:
109              _unregister_irc_platform()
110  
111      def test_irc_status_partial_when_only_server_set(self, monkeypatch):
112          """If only IRC_SERVER is set, the platform is still not configured."""
113          import hermes_cli.gateway as gateway_mod
114  
115          plat = _register_irc_platform()
116          try:
117              monkeypatch.delenv("IRC_CHANNEL", raising=False)
118              monkeypatch.delenv("IRC_NICKNAME", raising=False)
119              monkeypatch.setenv("IRC_SERVER", "irc.libera.chat")
120  
121              status = gateway_mod._platform_status(plat)
122              assert status == "not configured"
123          finally:
124              _unregister_irc_platform()
125  
126  
127  # ── Interactive setup dispatch ──────────────────────────────────────────────
128  
129  
130  class TestIRCInteractiveSetup:
131      """The setup UI dispatches to IRC's interactive_setup() correctly."""
132  
133      def test_configure_platform_dispatches_to_irc_setup_fn(self, monkeypatch, capsys):
134          """_configure_platform() calls the IRC plugin's setup_fn when selected."""
135          import hermes_cli.gateway as gateway_mod
136  
137          calls = []
138  
139          def fake_setup():
140              calls.append("setup_called")
141              print("IRC setup complete!")
142  
143          plat = _register_irc_platform(setup_fn=fake_setup)
144          try:
145              gateway_mod._configure_platform(plat)
146          finally:
147              _unregister_irc_platform()
148  
149          assert "setup_called" in calls
150          out = capsys.readouterr().out
151          assert "IRC setup complete!" in out
152  
153  
154      def test_configure_platform_fallback_when_no_setup_fn(self, monkeypatch, capsys):
155          """A plugin with no setup_fn falls back to env-var instructions."""
156          import hermes_cli.gateway as gateway_mod
157  
158          plat = _register_irc_platform(setup_fn=None)
159          try:
160              gateway_mod._configure_platform(plat)
161          finally:
162              _unregister_irc_platform()
163  
164          out = capsys.readouterr().out
165          assert "IRC" in out
166          assert "IRC_SERVER" in out
167  
168  
169  # ── End-to-end fresh-install gateway setup ──────────────────────────────────
170  
171  
172  class TestIRCGatewaySetupFreshInstall:
173      """Simulate the full `hermes setup gateway` experience with IRC present."""
174  
175      def test_setup_gateway_shows_irc_in_platform_menu(self, monkeypatch, capsys, tmp_path):
176          """The gateway setup menu lists IRC among the available platforms."""
177          import hermes_cli.gateway as gateway_mod
178          from hermes_cli import setup as setup_mod
179  
180          monkeypatch.setenv("HERMES_HOME", str(tmp_path))
181          _register_irc_platform()
182          try:
183              for key in ("IRC_SERVER", "IRC_CHANNEL", "IRC_NICKNAME"):
184                  monkeypatch.delenv(key, raising=False)
185  
186              # Sanity-check: IRC must be visible to _all_platforms()
187              platforms = gateway_mod._all_platforms()
188              assert any(p["key"] == "irc" for p in platforms), \
189                  f"IRC not in platforms: {[p['key'] for p in platforms]}"
190  
191              # Capture what prompt_checklist is asked to display
192              checklist_calls = []
193  
194              def capture_prompt_checklist(question, choices, pre_selected=None):
195                  checklist_calls.append({"question": question, "choices": choices})
196                  return []  # nothing selected → clean exit
197  
198              monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *a, **kw: False)
199              monkeypatch.setattr(setup_mod, "prompt_checklist", capture_prompt_checklist)
200              monkeypatch.setattr(gateway_mod, "supports_systemd_services", lambda: False)
201              monkeypatch.setattr(gateway_mod, "is_macos", lambda: False)
202              monkeypatch.setattr(gateway_mod, "_is_service_installed", lambda: False)
203              monkeypatch.setattr(gateway_mod, "_is_service_running", lambda: False)
204  
205              setup_mod.setup_gateway({})
206  
207              # Find the platform-selection prompt
208              platform_prompt = next(
209                  (c for c in checklist_calls if "platform" in c["question"].lower()),
210                  None,
211              )
212              assert platform_prompt is not None, \
213                  f"No platform prompt found in {checklist_calls}"
214              choices_text = "\n".join(platform_prompt["choices"])
215              assert "IRC" in choices_text
216              assert "💬" in choices_text
217              assert "not configured" in choices_text.lower()
218          finally:
219              _unregister_irc_platform()
220  
221      def test_setup_gateway_irc_counts_as_messaging_platform(self, monkeypatch, capsys, tmp_path):
222          """When IRC is configured, setup_gateway counts it as a messaging platform."""
223          import hermes_cli.gateway as gateway_mod
224          from hermes_cli import setup as setup_mod
225  
226          monkeypatch.setenv("HERMES_HOME", str(tmp_path))
227          _register_irc_platform()
228          try:
229              monkeypatch.setenv("IRC_SERVER", "irc.libera.chat")
230              monkeypatch.setenv("IRC_CHANNEL", "#hermes")
231              monkeypatch.setenv("IRC_NICKNAME", "hermes-bot")
232  
233              monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *a, **kw: False)
234              monkeypatch.setattr(setup_mod, "prompt_choice", lambda *a, **kw: 0)
235              monkeypatch.setattr(gateway_mod, "supports_systemd_services", lambda: False)
236              monkeypatch.setattr(gateway_mod, "is_macos", lambda: False)
237              monkeypatch.setattr(gateway_mod, "_is_service_installed", lambda: False)
238              monkeypatch.setattr(gateway_mod, "_is_service_running", lambda: False)
239  
240              setup_mod.setup_gateway({})
241  
242              out = capsys.readouterr().out
243              assert "Messaging platforms configured!" in out
244          finally:
245              _unregister_irc_platform()