"""Tests for the core plugin.""" import asyncio import importlib.util import sys import types from dataclasses import dataclass from unittest.mock import MagicMock # -- Load plugin module directly --------------------------------------------- _spec = importlib.util.spec_from_file_location("core", "plugins/core.py") _mod = importlib.util.module_from_spec(_spec) sys.modules["core"] = _mod _spec.loader.exec_module(_mod) # -- Fakes ------------------------------------------------------------------- @dataclass class _FakeHandler: name: str callback: object help: str = "" plugin: str = "" admin: bool = False tier: str = "user" class _FakeRegistry: def __init__(self): self._bots: dict = {} self.commands: dict = {} self._modules: dict = {} self.events: dict = {} class _FakeBot: def __init__(self, *, mumble: bool = False): self.replied: list[str] = [] self.registry = _FakeRegistry() self.nick = "derp" self.prefix = "!" self._receive_sound = False if mumble: self._mumble = MagicMock() def _plugin_allowed(self, plugin: str, channel) -> bool: return True async def reply(self, message, text: str) -> None: self.replied.append(text) def _make_listener(): """Create a fake listener bot (merlin) with _receive_sound=True.""" listener = _FakeBot(mumble=True) listener.nick = "merlin" listener._receive_sound = True return listener class _Msg: def __init__(self, text="!deaf"): self.text = text self.nick = "Alice" self.target = "0" self.is_channel = True self.prefix = "Alice" # -- Tests ------------------------------------------------------------------- class TestDeafCommand: def test_deaf_targets_listener(self): """!deaf toggles the listener bot (merlin), not the calling bot.""" bot = _FakeBot(mumble=True) listener = _make_listener() bot.registry._bots = {"derp": bot, "merlin": listener} listener._mumble.users.myself.get.return_value = False msg = _Msg(text="!deaf") asyncio.run(_mod.cmd_deaf(bot, msg)) listener._mumble.users.myself.deafen.assert_called_once() assert any("merlin" in r and "deafened" in r for r in bot.replied) def test_deaf_toggle_off(self): bot = _FakeBot(mumble=True) listener = _make_listener() bot.registry._bots = {"derp": bot, "merlin": listener} listener._mumble.users.myself.get.return_value = True msg = _Msg(text="!deaf") asyncio.run(_mod.cmd_deaf(bot, msg)) listener._mumble.users.myself.undeafen.assert_called_once() listener._mumble.users.myself.unmute.assert_called_once() assert any("merlin" in r and "undeafened" in r for r in bot.replied) def test_deaf_non_mumble_silent(self): bot = _FakeBot(mumble=False) msg = _Msg(text="!deaf") asyncio.run(_mod.cmd_deaf(bot, msg)) assert bot.replied == [] def test_deaf_fallback_no_listener(self): """Falls back to calling bot when no listener is registered.""" bot = _FakeBot(mumble=True) bot._mumble.users.myself.get.return_value = False msg = _Msg(text="!deaf") asyncio.run(_mod.cmd_deaf(bot, msg)) bot._mumble.users.myself.deafen.assert_called_once() # -- Help command tests ------------------------------------------------------ def _cmd_with_doc(): """Manage widgets. Usage: !widget add !widget del Examples: !widget add foo """ def _cmd_no_doc(): pass def _make_fp_module(url="https://paste.example.com/abc/raw", capture=None): """Create a fake flaskpaste module that returns a fixed URL. If capture is a list, appended paste content is stored there. """ mod = types.ModuleType("flaskpaste") def _create(bot, text): if capture is not None: capture.append(text) return url mod.create_paste = _create return mod class TestHelpCommand: def test_help_cmd_with_paste(self): """!help with docstring pastes detail, appends URL.""" bot = _FakeBot() handler = _FakeHandler( name="widget", callback=_cmd_with_doc, help="Manage widgets", plugin="widgets", ) bot.registry.commands["widget"] = handler bot.registry._modules["flaskpaste"] = _make_fp_module() msg = _Msg(text="!help widget") asyncio.run(_mod.cmd_help(bot, msg)) assert len(bot.replied) == 1 assert "!widget -- Manage widgets" in bot.replied[0] assert "https://paste.example.com/abc/raw" in bot.replied[0] def test_help_cmd_no_docstring(self): """!help without docstring skips paste.""" bot = _FakeBot() handler = _FakeHandler( name="noop", callback=_cmd_no_doc, help="Does nothing", plugin="misc", ) bot.registry.commands["noop"] = handler bot.registry._modules["flaskpaste"] = _make_fp_module() msg = _Msg(text="!help noop") asyncio.run(_mod.cmd_help(bot, msg)) assert len(bot.replied) == 1 assert "!noop -- Does nothing" in bot.replied[0] assert "paste.example.com" not in bot.replied[0] def test_help_plugin_with_paste(self): """!help pastes detail for all plugin commands.""" bot = _FakeBot() mod = types.ModuleType("widgets") mod.__doc__ = "Widget management plugin." bot.registry._modules["widgets"] = mod bot.registry._modules["flaskpaste"] = _make_fp_module() bot.registry.commands["widget"] = _FakeHandler( name="widget", callback=_cmd_with_doc, help="Manage widgets", plugin="widgets", ) bot.registry.commands["wstat"] = _FakeHandler( name="wstat", callback=_cmd_no_doc, help="Widget stats", plugin="widgets", ) msg = _Msg(text="!help widgets") asyncio.run(_mod.cmd_help(bot, msg)) assert len(bot.replied) == 1 reply = bot.replied[0] assert "widgets -- Widget management plugin." in reply assert "!widget, !wstat" in reply # Only widget has a docstring, so paste should still happen assert "https://paste.example.com/abc/raw" in reply def test_help_list_with_paste(self): """!help (no args) pastes full reference.""" bot = _FakeBot() bot.registry._modules["flaskpaste"] = _make_fp_module() mod = types.ModuleType("core") mod.__doc__ = "Core plugin." bot.registry._modules["core"] = mod bot.registry.commands["ping"] = _FakeHandler( name="ping", callback=_cmd_with_doc, help="Check alive", plugin="core", ) bot.registry.commands["help"] = _FakeHandler( name="help", callback=_cmd_no_doc, help="Show help", plugin="core", ) msg = _Msg(text="!help") asyncio.run(_mod.cmd_help(bot, msg)) assert len(bot.replied) == 1 assert "help, ping" in bot.replied[0] assert "https://paste.example.com/abc/raw" in bot.replied[0] def test_help_no_flaskpaste(self): """Without flaskpaste loaded, help still works (no URL).""" bot = _FakeBot() handler = _FakeHandler( name="widget", callback=_cmd_with_doc, help="Manage widgets", plugin="widgets", ) bot.registry.commands["widget"] = handler # No flaskpaste in _modules msg = _Msg(text="!help widget") asyncio.run(_mod.cmd_help(bot, msg)) assert len(bot.replied) == 1 assert "!widget -- Manage widgets" in bot.replied[0] assert "https://" not in bot.replied[0] def test_help_cmd_paste_hierarchy(self): """Single-command paste: header at 0, docstring at 4.""" bot = _FakeBot() pastes: list[str] = [] bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes) bot.registry.commands["widget"] = _FakeHandler( name="widget", callback=_cmd_with_doc, help="Manage widgets", plugin="widgets", ) msg = _Msg(text="!help widget") asyncio.run(_mod.cmd_help(bot, msg)) assert len(pastes) == 1 lines = pastes[0].split("\n") # Level 0: command header flush-left assert lines[0] == "!widget -- Manage widgets" # Level 1: docstring lines indented 4 spaces for line in lines[1:]: if line.strip(): assert line.startswith(" "), f"not indented: {line!r}" def test_help_list_paste_hierarchy(self): """Full reference paste: plugin at 0, command at 4, doc at 8.""" bot = _FakeBot() pastes: list[str] = [] bot.registry._modules["flaskpaste"] = _make_fp_module(capture=pastes) mod = types.ModuleType("core") mod.__doc__ = "Core plugin." bot.registry._modules["core"] = mod bot.registry.commands["state"] = _FakeHandler( name="state", callback=_cmd_with_doc, help="Inspect state", plugin="core", ) msg = _Msg(text="!help") asyncio.run(_mod.cmd_help(bot, msg)) assert len(pastes) == 1 text = pastes[0] lines = text.split("\n") # Level 0: plugin header assert lines[0] == "[core]" # Level 1: plugin description assert lines[1] == " Core plugin." # Blank separator assert lines[2] == "" # Level 1: command header at indent 4 assert lines[3] == " !state -- Inspect state" # Level 2: docstring at indent 8 for line in lines[4:]: if line.strip(): assert line.startswith(" "), f"not at indent 8: {line!r}"