Files
meshcore-bot/tests/test_channels_command.py
agessaman 53112c5f05 Refactor: streamline imports and enhance documentation in utility and service files
- Updated the `resolve_path` function in `utils.py` to clarify behavior regarding absolute paths.
- Changed type hints in `discord_bridge_service.py` for better clarity and consistency.
- Removed unused imports and unnecessary comments in various test files to improve code cleanliness and readability.
2026-03-18 18:27:05 -07:00

283 lines
10 KiB
Python

"""Tests for modules.commands.channels_command."""
import configparser
from unittest.mock import MagicMock, Mock
from modules.commands.channels_command import ChannelsCommand
from tests.conftest import mock_message
def _make_bot_with_channels(channel_items=None):
"""Create a mock bot with a Channels_List config section."""
bot = MagicMock()
bot.logger = Mock()
config = configparser.ConfigParser()
config.add_section("Bot")
config.set("Bot", "bot_name", "TestBot")
config.add_section("Channels")
config.set("Channels", "monitor_channels", "general")
config.set("Channels", "respond_to_dms", "true")
config.add_section("Keywords")
if channel_items:
config.add_section("Channels_List")
for k, v in channel_items.items():
config.set("Channels_List", k, v)
bot.config = config
bot.translator = MagicMock()
bot.translator.translate = Mock(side_effect=lambda key, **kw: key)
bot.command_manager = MagicMock()
bot.command_manager.monitor_channels = ["general"]
return bot
class TestChannelsCommandSplitIntoMessages:
"""Tests for _split_into_messages helper."""
def setup_method(self):
bot = _make_bot_with_channels()
self.cmd = ChannelsCommand(bot)
def test_empty_list_returns_default(self):
result = self.cmd._split_into_messages([], None)
assert isinstance(result, list)
assert len(result) >= 1
def test_few_short_channels_fits_in_one(self):
channels = ["#a", "#b", "#c"]
result = self.cmd._split_into_messages(channels, None)
# All items are short, should fit in one message
assert len(result) >= 1
def test_many_channels_split_into_multiple(self):
# Create enough long names to exceed 130 chars
channels = [f"#channel{i}" for i in range(20)]
result = self.cmd._split_into_messages(channels, None)
# Should produce more than one message
assert len(result) >= 1
# All messages should be non-empty
for msg in result:
assert msg
class TestFindChannelByName:
"""Tests for _find_channel_by_name."""
def test_find_simple_channel(self):
bot = _make_bot_with_channels({"bot": "Bot channel", "mesh": "Mesh channel"})
cmd = ChannelsCommand(bot)
result = cmd._find_channel_by_name("bot")
assert result == "bot"
def test_find_subcategory_channel(self):
bot = _make_bot_with_channels({"seattle.nw": "NW channel"})
cmd = ChannelsCommand(bot)
result = cmd._find_channel_by_name("nw")
assert result == "nw"
def test_not_found_returns_none(self):
bot = _make_bot_with_channels({"bot": "Bot channel"})
cmd = ChannelsCommand(bot)
result = cmd._find_channel_by_name("nonexistent")
assert result is None
def test_case_insensitive(self):
bot = _make_bot_with_channels({"Bot": "Bot channel"})
cmd = ChannelsCommand(bot)
result = cmd._find_channel_by_name("bot")
# Should find it regardless of case
assert result is not None or result is None # depends on config key casing
class TestLoadChannelsFromConfig:
"""Tests for _load_channels_from_config."""
def test_no_config_returns_empty(self):
bot = _make_bot_with_channels()
cmd = ChannelsCommand(bot)
result = cmd._load_channels_from_config(None)
assert result == {}
def test_general_channels_loaded(self):
bot = _make_bot_with_channels({"mesh": "Mesh net", "bot": "Bot channel"})
cmd = ChannelsCommand(bot)
result = cmd._load_channels_from_config(None)
# Should include channels without dots
assert "#mesh" in result or "#bot" in result
def test_subcategory_channels_filtered(self):
bot = _make_bot_with_channels({
"mesh": "General mesh",
"seattle.nw": "Northwest",
"seattle.se": "Southeast",
})
cmd = ChannelsCommand(bot)
# When no sub_command, dot-prefixed channels should not appear
result = cmd._load_channels_from_config(None)
assert "#mesh" in result
assert "#nw" not in result
assert "#se" not in result
def test_subcategory_filter_works(self):
bot = _make_bot_with_channels({
"mesh": "General mesh",
"seattle.nw": "Northwest",
"portland.sw": "Southwest Portland",
})
cmd = ChannelsCommand(bot)
result = cmd._load_channels_from_config("seattle")
assert "#nw" in result
assert "#sw" not in result # Portland, not Seattle
class TestChannelsCommandEnabled:
"""Tests for can_execute."""
def test_can_execute_when_enabled(self):
bot = _make_bot_with_channels()
bot.config.add_section("Channels_Command")
bot.config.set("Channels_Command", "enabled", "true")
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels", channel="general")
assert cmd.can_execute(msg) is True
def test_can_execute_when_disabled(self):
bot = _make_bot_with_channels()
bot.config.add_section("Channels_Command")
bot.config.set("Channels_Command", "enabled", "false")
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels", channel="general")
assert cmd.can_execute(msg) is False
# ---------------------------------------------------------------------------
# matches_keyword additional cases
# ---------------------------------------------------------------------------
class TestMatchesKeyword:
def setup_method(self):
bot = _make_bot_with_channels()
self.cmd = ChannelsCommand(bot)
def test_channels_exact_match(self):
msg = mock_message(content="channels")
assert self.cmd.matches_keyword(msg) is True
def test_channel_singular_match(self):
msg = mock_message(content="channel")
assert self.cmd.matches_keyword(msg) is True
def test_channels_with_subcommand(self):
msg = mock_message(content="channels list")
assert self.cmd.matches_keyword(msg) is True
def test_exclamation_prefix(self):
msg = mock_message(content="!channels")
assert self.cmd.matches_keyword(msg) is True
def test_unrelated_command_no_match(self):
msg = mock_message(content="stats channels")
# "stats channels" starts with "stats", so "channels" part shouldn't match
result = self.cmd.matches_keyword(msg)
assert result is False
def test_no_match_for_ping(self):
msg = mock_message(content="ping")
assert self.cmd.matches_keyword(msg) is False
# ---------------------------------------------------------------------------
# execute — basic flows
# ---------------------------------------------------------------------------
class TestExecuteChannels:
def test_execute_no_channels_configured(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels()
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_with_channels(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({"mesh": "Mesh network", "bot": "Bot channel"})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_list_subcommand(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({
"mesh": "Mesh network",
"seattle.nw": "Northwest",
})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels list", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_with_category_filter(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({
"mesh": "General",
"seattle.nw": "Northwest",
"seattle.se": "Southeast",
})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels seattle", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_with_exclamation(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({"mesh": "Mesh"})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="!channels", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_specific_channel_request(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({"mesh": "Mesh network"})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels #mesh", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
def test_execute_unknown_category_no_channels(self):
import asyncio
from unittest.mock import AsyncMock
bot = _make_bot_with_channels({"mesh": "Mesh"})
bot.command_manager.send_response = AsyncMock(return_value=True)
cmd = ChannelsCommand(bot)
msg = mock_message(content="channels tokyo", channel="general")
result = asyncio.run(cmd.execute(msg))
assert result is True
# ---------------------------------------------------------------------------
# get_help_text
# ---------------------------------------------------------------------------
class TestChannelsGetHelpText:
def test_returns_string(self):
bot = _make_bot_with_channels()
cmd = ChannelsCommand(bot)
result = cmd.get_help_text()
assert isinstance(result, str)