"""Tests for the mumble_admin plugin.""" import asyncio import importlib.util from dataclasses import dataclass, field from unittest.mock import MagicMock # -- Load plugin module directly --------------------------------------------- _spec = importlib.util.spec_from_file_location( "mumble_admin", "plugins/mumble_admin.py", ) _mod = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(_mod) cmd_mu = _mod.cmd_mu _find_user = _mod._find_user _find_channel = _mod._find_channel _channel_name = _mod._channel_name # -- Fakes ------------------------------------------------------------------- @dataclass class _FakeMessage: text: str = "" nick: str = "admin" prefix: str = "admin" target: str = "0" is_channel: bool = True params: list[str] = field(default_factory=list) class _FakeRegistry: _bots: dict = field(default_factory=dict) def __init__(self): self._bots = {} class _FakeBot: def __init__(self, users=None, channels=None): self.registry = _FakeRegistry() self._mumble = MagicMock() if users is not None: self._mumble.users = users else: self._mumble.users = {} if channels is not None: self._mumble.channels = channels self._replies: list[str] = [] async def reply(self, message, text): self._replies.append(text) async def send(self, target, text): self._replies.append(text) def _make_user(name, channel_id=0, mute=False, deaf=False, self_mute=False, self_deaf=False): """Create a fake pymumble user (dict with methods).""" u = MagicMock() u.__getitem__ = lambda s, k: { "name": name, "channel_id": channel_id, "mute": mute, "deaf": deaf, "self_mute": self_mute, "self_deaf": self_deaf, }[k] u.get = lambda k, d=None: { "name": name, "channel_id": channel_id, "mute": mute, "deaf": deaf, "self_mute": self_mute, "self_deaf": self_deaf, }.get(k, d) return u def _make_channel(name, channel_id=0, parent=0): """Create a fake pymumble channel (dict with methods).""" c = MagicMock() c.__getitem__ = lambda s, k: { "name": name, "channel_id": channel_id, "parent": parent, }[k] c.get = lambda k, d=None: { "name": name, "channel_id": channel_id, "parent": parent, }.get(k, d) return c # -- TestFindUser ------------------------------------------------------------ class TestFindUser: def test_case_insensitive(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) assert _find_user(bot, "alice") is alice assert _find_user(bot, "ALICE") is alice assert _find_user(bot, "Alice") is alice def test_not_found(self): bot = _FakeBot(users={1: _make_user("Alice")}) assert _find_user(bot, "Bob") is None def test_no_mumble(self): bot = _FakeBot() bot._mumble = None assert _find_user(bot, "anyone") is None # -- TestFindChannel --------------------------------------------------------- class TestFindChannel: def test_case_insensitive(self): lobby = _make_channel("Lobby", channel_id=0) bot = _FakeBot(channels={0: lobby}) assert _find_channel(bot, "lobby") is lobby assert _find_channel(bot, "LOBBY") is lobby def test_not_found(self): bot = _FakeBot(channels={0: _make_channel("Lobby")}) assert _find_channel(bot, "AFK") is None def test_no_mumble(self): bot = _FakeBot() bot._mumble = None assert _find_channel(bot, "any") is None # -- TestChannelName --------------------------------------------------------- class TestChannelName: def test_resolves(self): lobby = _make_channel("Lobby", channel_id=0) bot = _FakeBot(channels={0: lobby}) assert _channel_name(bot, 0) == "Lobby" def test_missing_returns_id(self): bot = _FakeBot(channels={}) assert _channel_name(bot, 42) == "42" def test_no_mumble(self): bot = _FakeBot() bot._mumble = None assert _channel_name(bot, 5) == "5" # -- TestDispatch ------------------------------------------------------------ class TestDispatch: def test_no_args_shows_usage(self): bot = _FakeBot() msg = _FakeMessage(text="!mu") asyncio.run(cmd_mu(bot, msg)) assert len(bot._replies) == 1 assert "Usage" in bot._replies[0] def test_unknown_sub_shows_usage(self): bot = _FakeBot() msg = _FakeMessage(text="!mu bogus") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] def test_valid_sub_routes(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu kick Alice") asyncio.run(cmd_mu(bot, msg)) alice.kick.assert_called_once_with("") assert "Kicked" in bot._replies[0] # -- TestKick ---------------------------------------------------------------- class TestKick: def test_kick_user(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu kick Alice") asyncio.run(cmd_mu(bot, msg)) alice.kick.assert_called_once_with("") assert "Kicked Alice" in bot._replies[0] def test_kick_with_reason(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu kick Alice being rude") asyncio.run(cmd_mu(bot, msg)) alice.kick.assert_called_once_with("being rude") def test_kick_user_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu kick Ghost") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_kick_no_args(self): bot = _FakeBot() msg = _FakeMessage(text="!mu kick") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] # -- TestBan ----------------------------------------------------------------- class TestBan: def test_ban_user(self): bob = _make_user("Bob") bot = _FakeBot(users={1: bob}) msg = _FakeMessage(text="!mu ban Bob") asyncio.run(cmd_mu(bot, msg)) bob.ban.assert_called_once_with("") assert "Banned Bob" in bot._replies[0] def test_ban_with_reason(self): bob = _make_user("Bob") bot = _FakeBot(users={1: bob}) msg = _FakeMessage(text="!mu ban Bob spamming") asyncio.run(cmd_mu(bot, msg)) bob.ban.assert_called_once_with("spamming") def test_ban_user_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu ban Ghost") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() # -- TestMuteUnmute ---------------------------------------------------------- class TestMuteUnmute: def test_mute(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu mute Alice") asyncio.run(cmd_mu(bot, msg)) alice.mute.assert_called_once() assert "Muted" in bot._replies[0] def test_unmute(self): alice = _make_user("Alice", mute=True) bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu unmute Alice") asyncio.run(cmd_mu(bot, msg)) alice.unmute.assert_called_once() assert "Unmuted" in bot._replies[0] def test_mute_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu mute Nobody") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_unmute_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu unmute Nobody") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() # -- TestDeafenUndeafen ------------------------------------------------------ class TestDeafenUndeafen: def test_deafen(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu deafen Alice") asyncio.run(cmd_mu(bot, msg)) alice.deafen.assert_called_once() assert "Deafened" in bot._replies[0] def test_undeafen(self): alice = _make_user("Alice", deaf=True) bot = _FakeBot(users={1: alice}) msg = _FakeMessage(text="!mu undeafen Alice") asyncio.run(cmd_mu(bot, msg)) alice.undeafen.assert_called_once() assert "Undeafened" in bot._replies[0] def test_deafen_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu deafen Nobody") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_undeafen_not_found(self): bot = _FakeBot(users={}) msg = _FakeMessage(text="!mu undeafen Nobody") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() # -- TestMove ---------------------------------------------------------------- class TestMove: def test_move_user(self): alice = _make_user("Alice", channel_id=0) afk = _make_channel("AFK", channel_id=5) bot = _FakeBot(users={1: alice}, channels={0: _make_channel("Root"), 5: afk}) msg = _FakeMessage(text="!mu move Alice AFK") asyncio.run(cmd_mu(bot, msg)) alice.move_in.assert_called_once_with(5) assert "Moved Alice to AFK" in bot._replies[0] def test_move_user_not_found(self): bot = _FakeBot(users={}, channels={5: _make_channel("AFK", channel_id=5)}) msg = _FakeMessage(text="!mu move Ghost AFK") asyncio.run(cmd_mu(bot, msg)) assert "user not found" in bot._replies[0].lower() def test_move_channel_not_found(self): alice = _make_user("Alice") bot = _FakeBot(users={1: alice}, channels={}) msg = _FakeMessage(text="!mu move Alice Nowhere") asyncio.run(cmd_mu(bot, msg)) assert "channel not found" in bot._replies[0].lower() def test_move_missing_args(self): bot = _FakeBot() msg = _FakeMessage(text="!mu move Alice") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] # -- TestUsers --------------------------------------------------------------- class TestUsers: def test_list_users(self): alice = _make_user("Alice", channel_id=0) bob = _make_user("Bob", channel_id=0, self_mute=True) lobby = _make_channel("Lobby", channel_id=0) bot = _FakeBot(users={1: alice, 2: bob}, channels={0: lobby}) msg = _FakeMessage(text="!mu users") asyncio.run(cmd_mu(bot, msg)) reply = bot._replies[0] assert "2 user(s)" in reply assert "Alice" in reply assert "Bob" in reply assert "muted" in reply def test_list_with_bots(self): alice = _make_user("Alice", channel_id=0) derp = _make_user("derp", channel_id=0) lobby = _make_channel("Lobby", channel_id=0) bot = _FakeBot(users={1: alice, 2: derp}, channels={0: lobby}) bot.registry._bots = {"derp": MagicMock()} msg = _FakeMessage(text="!mu users") asyncio.run(cmd_mu(bot, msg)) reply = bot._replies[0] assert "bot" in reply assert "2 user(s)" in reply def test_deaf_flag(self): alice = _make_user("Alice", channel_id=0, self_deaf=True) lobby = _make_channel("Lobby", channel_id=0) bot = _FakeBot(users={1: alice}, channels={0: lobby}) msg = _FakeMessage(text="!mu users") asyncio.run(cmd_mu(bot, msg)) assert "deaf" in bot._replies[0] # -- TestChannels ------------------------------------------------------------ class TestChannels: def test_list_channels(self): lobby = _make_channel("Lobby", channel_id=0) afk = _make_channel("AFK", channel_id=1) alice = _make_user("Alice", channel_id=0) bot = _FakeBot(users={1: alice}, channels={0: lobby, 1: afk}) msg = _FakeMessage(text="!mu channels") asyncio.run(cmd_mu(bot, msg)) reply = bot._replies[0] assert "Lobby (1)" in reply assert "AFK (0)" in reply # -- TestMkchan -------------------------------------------------------------- class TestMkchan: def test_create_channel(self): bot = _FakeBot() msg = _FakeMessage(text="!mu mkchan Gaming") asyncio.run(cmd_mu(bot, msg)) bot._mumble.channels.new_channel.assert_called_once_with( 0, "Gaming", temporary=False, ) assert "Created" in bot._replies[0] def test_create_temp_channel(self): bot = _FakeBot() msg = _FakeMessage(text="!mu mkchan Gaming temp") asyncio.run(cmd_mu(bot, msg)) bot._mumble.channels.new_channel.assert_called_once_with( 0, "Gaming", temporary=True, ) assert "temporary" in bot._replies[0] def test_missing_name(self): bot = _FakeBot() msg = _FakeMessage(text="!mu mkchan") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] # -- TestRmchan -------------------------------------------------------------- class TestRmchan: def test_remove_channel(self): afk = _make_channel("AFK", channel_id=5) bot = _FakeBot(channels={5: afk}) msg = _FakeMessage(text="!mu rmchan AFK") asyncio.run(cmd_mu(bot, msg)) afk.remove.assert_called_once() assert "Removed" in bot._replies[0] def test_channel_not_found(self): bot = _FakeBot(channels={}) msg = _FakeMessage(text="!mu rmchan Nowhere") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_missing_args(self): bot = _FakeBot() msg = _FakeMessage(text="!mu rmchan") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] # -- TestRename -------------------------------------------------------------- class TestRename: def test_rename_channel(self): afk = _make_channel("AFK", channel_id=5) bot = _FakeBot(channels={5: afk}) msg = _FakeMessage(text="!mu rename AFK Chill") asyncio.run(cmd_mu(bot, msg)) afk.rename_channel.assert_called_once_with("Chill") assert "Renamed" in bot._replies[0] def test_channel_not_found(self): bot = _FakeBot(channels={}) msg = _FakeMessage(text="!mu rename Nowhere New") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_missing_args(self): bot = _FakeBot() msg = _FakeMessage(text="!mu rename AFK") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0] # -- TestDesc ---------------------------------------------------------------- class TestDesc: def test_set_description(self): afk = _make_channel("AFK", channel_id=5) bot = _FakeBot(channels={5: afk}) msg = _FakeMessage(text="!mu desc AFK Away from keyboard") asyncio.run(cmd_mu(bot, msg)) afk.set_channel_description.assert_called_once_with("Away from keyboard") assert "description" in bot._replies[0].lower() def test_channel_not_found(self): bot = _FakeBot(channels={}) msg = _FakeMessage(text="!mu desc Nowhere some text") asyncio.run(cmd_mu(bot, msg)) assert "not found" in bot._replies[0].lower() def test_missing_args(self): bot = _FakeBot() msg = _FakeMessage(text="!mu desc AFK") asyncio.run(cmd_mu(bot, msg)) assert "Usage" in bot._replies[0]