mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-03-29 03:19:51 +00:00
- Updated the _cleanup_web_viewer and _cleanup_mesh_graph methods to avoid logging errors during shutdown, as the logger's stream may be closed at that time. - Modified the shutdown method in MeshGraph to prevent logging of flushing errors, enhancing stability during the atexit process. - Adjusted test configurations to use Path objects for bot_root and local_root, improving path handling in tests.
293 lines
11 KiB
Python
293 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for command prefix functionality
|
|
Tests that all commands properly handle command prefixes when enabled
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, MagicMock, patch
|
|
from configparser import ConfigParser
|
|
|
|
from modules.models import MeshMessage
|
|
from modules.commands.base_command import BaseCommand
|
|
from modules.commands.hello_command import HelloCommand
|
|
from modules.commands.ping_command import PingCommand
|
|
from modules.commands.help_command import HelpCommand
|
|
from modules.command_manager import CommandManager
|
|
|
|
|
|
class MockTestCommand(BaseCommand):
|
|
"""Mock command for testing prefix functionality"""
|
|
name = "test"
|
|
keywords = ['test', 't']
|
|
description = "Test command"
|
|
category = "test"
|
|
|
|
async def execute(self, message: MeshMessage) -> bool:
|
|
"""Execute the test command (required by abstract base class)"""
|
|
return True
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_bot():
|
|
"""Create a mock bot instance"""
|
|
bot = Mock()
|
|
bot.logger = Mock()
|
|
bot.logger.debug = Mock()
|
|
bot.logger.info = Mock()
|
|
bot.logger.warning = Mock()
|
|
bot.logger.error = Mock()
|
|
bot.config = ConfigParser()
|
|
bot.config.add_section('Bot')
|
|
bot.config.add_section('Channels')
|
|
bot.config.set('Channels', 'monitor_channels', 'general')
|
|
bot.config.set('Channels', 'respond_to_dms', 'true')
|
|
bot.meshcore = None
|
|
bot.translator = None
|
|
bot.rate_limiter = Mock()
|
|
bot.rate_limiter.can_send = Mock(return_value=True)
|
|
bot.bot_tx_rate_limiter = Mock()
|
|
bot.bot_tx_rate_limiter.wait_for_tx = Mock()
|
|
bot.tx_delay_ms = 0
|
|
bot.bot_root = Path("/tmp")
|
|
bot._local_root = None # CommandManager uses bot_root / local / commands
|
|
return bot
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_message():
|
|
"""Create a mock message"""
|
|
return MeshMessage(
|
|
content="test",
|
|
sender_id="TestUser",
|
|
channel="general",
|
|
is_dm=False
|
|
)
|
|
|
|
|
|
class TestCommandPrefix:
|
|
"""Test command prefix functionality"""
|
|
|
|
def test_no_prefix_allows_commands(self, mock_bot, mock_message):
|
|
"""Test that without prefix configured, commands work normally"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match without prefix
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should also match with legacy ! prefix
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
def test_prefix_required_when_configured(self, mock_bot, mock_message):
|
|
"""Test that when prefix is configured, it's required"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with prefix
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_dot_prefix(self, mock_bot, mock_message):
|
|
"""Test dot prefix (e.g., .ping)"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '.')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with dot prefix
|
|
mock_message.content = ".test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_single_char_prefix(self, mock_bot, mock_message):
|
|
"""Test single character prefix (e.g., bping)"""
|
|
mock_bot.config.set('Bot', 'command_prefix', 'b')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with 'b' prefix
|
|
mock_message.content = "btest"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_multi_char_prefix(self, mock_bot, mock_message):
|
|
"""Test multi-character prefix (e.g., abcping)"""
|
|
mock_bot.config.set('Bot', 'command_prefix', 'abc')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with 'abc' prefix
|
|
mock_message.content = "abctest"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
# Should NOT match with partial prefix
|
|
mock_message.content = "abtest"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_prefix_with_whitespace(self, mock_bot, mock_message):
|
|
"""Test that prefix works with whitespace after it"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with prefix and space
|
|
mock_message.content = "! test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should match with prefix and no space
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
def test_prefix_with_keyword_variations(self, mock_bot, mock_message):
|
|
"""Test prefix with different keyword variations"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Test first keyword
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Test second keyword
|
|
mock_message.content = "!t"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Test keyword with arguments
|
|
mock_message.content = "!test arg1 arg2"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
def test_hello_command_with_prefix(self, mock_bot, mock_message):
|
|
"""Test hello command specifically with prefix"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
mock_bot.config.set('Bot', 'bot_name', 'TestBot')
|
|
mock_bot.config.add_section('Hello_Command')
|
|
mock_bot.config.set('Hello_Command', 'enabled', 'true')
|
|
command = HelloCommand(mock_bot)
|
|
|
|
# Should match with prefix
|
|
mock_message.content = "!hello"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "hello"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_ping_command_with_prefix(self, mock_bot, mock_message):
|
|
"""Test ping command with prefix"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '.')
|
|
mock_bot.config.add_section('Ping_Command')
|
|
mock_bot.config.set('Ping_Command', 'enabled', 'true')
|
|
command = PingCommand(mock_bot)
|
|
|
|
# Should match with dot prefix
|
|
mock_message.content = ".ping"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match without prefix
|
|
mock_message.content = "ping"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_command_manager_with_prefix(self, mock_bot, mock_message):
|
|
"""Test CommandManager handles prefix correctly"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
mock_bot.config.add_section('Keywords')
|
|
mock_bot.config.set('Keywords', 'keywords', '')
|
|
mock_bot.config.add_section('Custom_Syntax')
|
|
mock_bot.config.set('Custom_Syntax', 'custom_syntax', '')
|
|
|
|
# Mock plugin loader to return empty commands for simplicity
|
|
with patch('modules.command_manager.PluginLoader') as mock_loader_class:
|
|
mock_loader = Mock()
|
|
mock_loader.load_all_plugins = Mock(return_value={})
|
|
mock_loader_class.return_value = mock_loader
|
|
|
|
manager = CommandManager(mock_bot)
|
|
|
|
# Should return empty matches for message without prefix
|
|
mock_message.content = "test"
|
|
matches = manager.check_keywords(mock_message)
|
|
assert matches == []
|
|
|
|
# Should process message with prefix
|
|
mock_message.content = "!test"
|
|
matches = manager.check_keywords(mock_message)
|
|
# Will be empty because no commands loaded, but should process without error
|
|
assert isinstance(matches, list)
|
|
|
|
def test_prefix_with_mentions(self, mock_bot, mock_message):
|
|
"""Test that prefix works correctly with @[username] mentions"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
mock_bot.config.set('Bot', 'bot_name', 'TestBot')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Mock self_info to return bot name
|
|
mock_bot.meshcore = Mock()
|
|
mock_bot.meshcore.self_info = {'name': 'TestBot'}
|
|
|
|
# Should match with prefix and bot mention
|
|
mock_message.content = "!@[TestBot] test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should NOT match with prefix but other user mention
|
|
mock_message.content = "!@[OtherUser] test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_different_prefixes_dont_match(self, mock_bot, mock_message):
|
|
"""Test that wrong prefix doesn't match"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should NOT match with wrong prefix
|
|
mock_message.content = ".test"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
mock_message.content = "btest"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
mock_message.content = "abctest"
|
|
assert command.matches_keyword(mock_message) is False
|
|
|
|
def test_prefix_case_sensitive(self, mock_bot, mock_message):
|
|
"""Test that prefix matching is case-sensitive"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '!')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match with exact prefix
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Prefix matching is case-sensitive, so different case shouldn't match
|
|
# (This tests the actual behavior - prefixes are case-sensitive)
|
|
mock_message.content = "!TEST" # Prefix is still '!', so this should match
|
|
assert command.matches_keyword(mock_message) is True # '!' is same case
|
|
|
|
# But if prefix is lowercase, uppercase shouldn't match
|
|
mock_bot.config.set('Bot', 'command_prefix', 'b')
|
|
command = MockTestCommand(mock_bot)
|
|
mock_message.content = "Btest" # Uppercase B
|
|
assert command.matches_keyword(mock_message) is False # Should not match lowercase 'b'
|
|
|
|
def test_empty_prefix_string(self, mock_bot, mock_message):
|
|
"""Test that empty string prefix means no prefix required"""
|
|
mock_bot.config.set('Bot', 'command_prefix', '')
|
|
command = MockTestCommand(mock_bot)
|
|
|
|
# Should match without prefix
|
|
mock_message.content = "test"
|
|
assert command.matches_keyword(mock_message) is True
|
|
|
|
# Should also match with legacy ! prefix
|
|
mock_message.content = "!test"
|
|
assert command.matches_keyword(mock_message) is True
|