!help <cmd> now pastes the command's docstring and appends the URL. !help <plugin> pastes detail for all plugin commands. !help (no args) pastes a full reference grouped by plugin. Falls back gracefully when flaskpaste is not loaded or paste fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
230 lines
7.5 KiB
Python
230 lines
7.5 KiB
Python
"""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 <name>
|
|
!widget del <name>
|
|
|
|
Examples:
|
|
!widget add foo
|
|
"""
|
|
|
|
|
|
def _cmd_no_doc():
|
|
pass
|
|
|
|
|
|
def _make_fp_module(url="https://paste.example.com/abc/raw"):
|
|
"""Create a fake flaskpaste module that returns a fixed URL."""
|
|
mod = types.ModuleType("flaskpaste")
|
|
mod.create_paste = lambda bot, text: url
|
|
return mod
|
|
|
|
|
|
class TestHelpCommand:
|
|
def test_help_cmd_with_paste(self):
|
|
"""!help <cmd> 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 <cmd> 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 <plugin> 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]
|