Files
meshcore-bot/tests/test_command_prefix.py
agessaman 2178a80dca Refactor cleanup methods in core.py and mesh_graph.py to suppress logging during shutdown
- 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.
2026-03-11 20:41:44 -07:00

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