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()