Files
derp/tests/test_core.py
user 20c1d738be
Some checks failed
CI / gitleaks (push) Failing after 4s
CI / lint (push) Failing after 23s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
fix: 3-level hierarchy in help paste output
Plugin name at column 0, command at indent 4, docstring at indent 8.
Single-command paste keeps command at 0, docstring at 4.
Only paste when actual docstring content exists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:21:10 +01:00

289 lines
9.7 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", 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 <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]
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}"