"""Tests for the alias plugin.""" import asyncio import importlib.util import sys from derp.plugin import PluginRegistry # -- Load plugin module directly --------------------------------------------- _spec = importlib.util.spec_from_file_location("alias", "plugins/alias.py") _mod = importlib.util.module_from_spec(_spec) sys.modules["alias"] = _mod _spec.loader.exec_module(_mod) # -- Fakes ------------------------------------------------------------------- class _FakeState: def __init__(self): self._store: dict[str, dict[str, str]] = {} def get(self, ns: str, key: str) -> str | None: return self._store.get(ns, {}).get(key) def set(self, ns: str, key: str, value: str) -> None: self._store.setdefault(ns, {})[key] = value def delete(self, ns: str, key: str) -> bool: if ns in self._store and key in self._store[ns]: del self._store[ns][key] return True return False def keys(self, ns: str) -> list[str]: return list(self._store.get(ns, {}).keys()) def clear(self, ns: str) -> int: count = len(self._store.get(ns, {})) self._store.pop(ns, None) return count class _FakeBot: def __init__(self, *, admin: bool = False): self.replied: list[str] = [] self.state = _FakeState() self.registry = PluginRegistry() self._admin = admin async def reply(self, message, text: str) -> None: self.replied.append(text) def _is_admin(self, message) -> bool: return self._admin class _Msg: def __init__(self, text="!alias"): self.text = text self.nick = "Alice" self.target = "#test" self.is_channel = True self.prefix = "Alice!~alice@host" # --------------------------------------------------------------------------- # TestAliasAdd # --------------------------------------------------------------------------- class TestAliasAdd: def test_add_creates_alias(self): bot = _FakeBot() # Register a target command async def _noop(b, m): pass bot.registry.register_command("skip", _noop, plugin="music") msg = _Msg(text="!alias add s skip") asyncio.run(_mod.cmd_alias(bot, msg)) assert bot.state.get("alias", "s") == "skip" assert any("s -> skip" in r for r in bot.replied) def test_add_rejects_existing_command(self): bot = _FakeBot() async def _noop(b, m): pass bot.registry.register_command("skip", _noop, plugin="music") msg = _Msg(text="!alias add skip stop") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("already a registered command" in r for r in bot.replied) assert bot.state.get("alias", "skip") is None def test_add_rejects_chaining(self): bot = _FakeBot() async def _noop(b, m): pass bot.registry.register_command("skip", _noop, plugin="music") bot.state.set("alias", "sk", "skip") msg = _Msg(text="!alias add x sk") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("no chaining" in r for r in bot.replied) def test_add_rejects_unknown_target(self): bot = _FakeBot() msg = _Msg(text="!alias add s nonexistent") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("unknown command" in r for r in bot.replied) def test_add_lowercases_name(self): bot = _FakeBot() async def _noop(b, m): pass bot.registry.register_command("skip", _noop, plugin="music") msg = _Msg(text="!alias add S skip") asyncio.run(_mod.cmd_alias(bot, msg)) assert bot.state.get("alias", "s") == "skip" def test_add_missing_args(self): bot = _FakeBot() msg = _Msg(text="!alias add s") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Usage" in r for r in bot.replied) # --------------------------------------------------------------------------- # TestAliasDel # --------------------------------------------------------------------------- class TestAliasDel: def test_del_removes_alias(self): bot = _FakeBot() bot.state.set("alias", "s", "skip") msg = _Msg(text="!alias del s") asyncio.run(_mod.cmd_alias(bot, msg)) assert bot.state.get("alias", "s") is None assert any("removed" in r for r in bot.replied) def test_del_nonexistent(self): bot = _FakeBot() msg = _Msg(text="!alias del x") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("no alias" in r for r in bot.replied) def test_del_missing_name(self): bot = _FakeBot() msg = _Msg(text="!alias del") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Usage" in r for r in bot.replied) # --------------------------------------------------------------------------- # TestAliasList # --------------------------------------------------------------------------- class TestAliasList: def test_list_empty(self): bot = _FakeBot() msg = _Msg(text="!alias list") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("No aliases" in r for r in bot.replied) def test_list_shows_entries(self): bot = _FakeBot() bot.state.set("alias", "s", "skip") bot.state.set("alias", "np", "nowplaying") msg = _Msg(text="!alias list") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("s -> skip" in r for r in bot.replied) assert any("np -> nowplaying" in r for r in bot.replied) # --------------------------------------------------------------------------- # TestAliasClear # --------------------------------------------------------------------------- class TestAliasClear: def test_clear_as_admin(self): bot = _FakeBot(admin=True) bot.state.set("alias", "s", "skip") bot.state.set("alias", "np", "nowplaying") msg = _Msg(text="!alias clear") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Cleared 2" in r for r in bot.replied) assert bot.state.keys("alias") == [] def test_clear_denied_non_admin(self): bot = _FakeBot(admin=False) bot.state.set("alias", "s", "skip") msg = _Msg(text="!alias clear") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Permission denied" in r for r in bot.replied) assert bot.state.get("alias", "s") == "skip" # --------------------------------------------------------------------------- # TestAliasUsage # --------------------------------------------------------------------------- class TestAliasUsage: def test_no_subcommand(self): bot = _FakeBot() msg = _Msg(text="!alias") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Usage" in r for r in bot.replied) def test_unknown_subcommand(self): bot = _FakeBot() msg = _Msg(text="!alias foo") asyncio.run(_mod.cmd_alias(bot, msg)) assert any("Usage" in r for r in bot.replied)