b2541c4054
feat: add configurable matrix permalink base, unify user placeholders, and refactor notification rendering
431 lines
16 KiB
Python
Executable File
431 lines
16 KiB
Python
Executable File
"""Tests for bot command handlers."""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
|
from mautrix.types import EventType, UserID, MessageEvent, StateEvent
|
|
from mautrix.errors import MNotFound
|
|
|
|
from community.bot import CommunityBot
|
|
|
|
|
|
class TestBotCommands:
|
|
"""Test cases for bot command handlers."""
|
|
|
|
@pytest.fixture
|
|
def bot(self):
|
|
"""Create a mock bot instance for testing."""
|
|
bot = Mock(spec=CommunityBot)
|
|
bot.client = Mock()
|
|
bot.database = Mock()
|
|
bot.log = Mock()
|
|
bot.config = {
|
|
"parent_room": "!parent:example.com",
|
|
"community_slug": "test",
|
|
"track_users": True,
|
|
"warn_threshold_days": 7,
|
|
"kick_threshold_days": 14,
|
|
"sleep": 0.1,
|
|
"censor_wordlist": [r"badword"],
|
|
"censor_files": False,
|
|
"censor": True,
|
|
"banlists": ["!banlist:example.com"],
|
|
"redact_on_ban": False,
|
|
"admins": [],
|
|
"moderators": []
|
|
}
|
|
return bot
|
|
|
|
@pytest.fixture
|
|
def mock_evt(self):
|
|
"""Create a mock MessageEvent for testing."""
|
|
evt = Mock(spec=MessageEvent)
|
|
evt.sender = "@user:example.com"
|
|
evt.room_id = "!room:example.com"
|
|
evt.reply = AsyncMock()
|
|
evt.respond = AsyncMock()
|
|
return evt
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_parent_room_configured(self, bot, mock_evt):
|
|
"""Test check_parent_room when parent room is configured."""
|
|
# Use the mock bot instance
|
|
bot.config = {"parent_room": "!parent:example.com"}
|
|
|
|
result = await bot.check_parent_room(mock_evt)
|
|
|
|
assert result == True
|
|
mock_evt.reply.assert_not_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_parent_room_not_configured(self, bot, mock_evt):
|
|
"""Test check_parent_room when parent room is not configured."""
|
|
bot.config = {"parent_room": None}
|
|
|
|
result = await bot.check_parent_room(mock_evt)
|
|
|
|
assert result == False
|
|
mock_evt.reply.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_check_banlists_command(self, bot, mock_evt):
|
|
"""Test the check_banlists command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock the check_if_banned method
|
|
with patch.object(real_bot, 'check_if_banned', return_value=True):
|
|
await real_bot.check_banlists(mock_evt, "@test:example.com")
|
|
|
|
mock_evt.reply.assert_called_once_with("user on banlist: True")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sync_space_members_command(self, bot, mock_evt):
|
|
"""Test the sync_space_members command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'do_sync', return_value={"added": [], "dropped": []}):
|
|
|
|
await real_bot.sync_space_members(mock_evt)
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sync_space_members_no_permission(self, bot, mock_evt):
|
|
"""Test sync_space_members command without permission."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
with patch.object(real_bot, 'user_permitted', return_value=False):
|
|
await real_bot.sync_space_members(mock_evt)
|
|
|
|
mock_evt.reply.assert_called_once_with("You don't have permission to use this command")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sync_space_members_tracking_disabled(self, bot, mock_evt):
|
|
"""Test sync_space_members command when tracking is disabled."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = {**bot.config, "track_users": False}
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
with patch.object(real_bot, 'user_permitted', return_value=True):
|
|
await real_bot.sync_space_members(mock_evt)
|
|
|
|
mock_evt.respond.assert_called_once_with("user tracking is disabled")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ignore_command(self, bot, mock_evt):
|
|
"""Test the ignore command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock database operations
|
|
real_bot.database.execute = AsyncMock()
|
|
|
|
with patch.object(real_bot, 'user_permitted', return_value=True):
|
|
await real_bot.ignore_user(mock_evt, "@test:example.com")
|
|
|
|
real_bot.database.execute.assert_called()
|
|
mock_evt.reply.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unignore_command(self, bot, mock_evt):
|
|
"""Test the unignore command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock database operations
|
|
real_bot.database.execute = AsyncMock()
|
|
|
|
with patch.object(real_bot, 'user_permitted', return_value=True):
|
|
await real_bot.unignore_user(mock_evt, "@test:example.com")
|
|
|
|
real_bot.database.execute.assert_called()
|
|
mock_evt.reply.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kick_command(self, bot, mock_evt):
|
|
"""Test the kick command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]), \
|
|
patch.object(real_bot, 'ban_this_user', return_value={"ban_list": {}, "error_list": {}}):
|
|
|
|
await real_bot.kick_user(mock_evt, "@test:example.com")
|
|
|
|
mock_evt.reply.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ban_command(self, bot, mock_evt):
|
|
"""Test the ban command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]), \
|
|
patch.object(real_bot, 'ban_this_user', return_value={"ban_list": {}, "error_list": {}}):
|
|
|
|
await real_bot.ban_user(mock_evt, "@test:example.com")
|
|
|
|
mock_evt.reply.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_doctor_command(self, bot, mock_evt):
|
|
"""Test the doctor command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]):
|
|
|
|
await real_bot.doctor(mock_evt)
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_doctor_room_detail_command(self, bot, mock_evt):
|
|
"""Test the doctor room detail command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, '_doctor_room_detail', return_value=None):
|
|
|
|
await real_bot.doctor_room_detail(mock_evt, "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_initialize_command(self, bot, mock_evt):
|
|
"""Test the initialize command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'create_space', return_value=("!space:example.com", "#space:example.com")):
|
|
|
|
await real_bot.initialize(mock_evt, "Test Community")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_room_command(self, bot, mock_evt):
|
|
"""Test the create_room command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'validate_room_aliases', return_value=(True, [])), \
|
|
patch.object(real_bot, 'create_room', return_value=("!room:example.com", "#room:example.com")):
|
|
|
|
await real_bot.create_room(mock_evt, "Test Room")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_room_command_alias_conflict(self, bot, mock_evt):
|
|
"""Test create_room command with alias conflict."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'validate_room_aliases', return_value=(False, ["#conflict:example.com"])):
|
|
|
|
await real_bot.create_room(mock_evt, "Test Room")
|
|
|
|
mock_evt.respond.assert_called()
|
|
# Should mention the conflict
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_archive_room_command(self, bot, mock_evt):
|
|
"""Test the archive_room command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'do_archive_room', return_value=None):
|
|
|
|
await real_bot.archive_room(mock_evt, "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_room_command(self, bot, mock_evt):
|
|
"""Test the remove_room command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'remove_room_aliases', return_value=[]):
|
|
|
|
await real_bot.remove_room(mock_evt, "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_join_room_command(self, bot, mock_evt):
|
|
"""Test the join_room command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'join_room', return_value="!room:example.com"):
|
|
|
|
await real_bot.join_room(mock_evt, "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_leave_room_command(self, bot, mock_evt):
|
|
"""Test the leave_room command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'leave_room', return_value=None):
|
|
|
|
await real_bot.leave_room(mock_evt, "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_verify_command(self, bot, mock_evt):
|
|
"""Test the verify command."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.client = bot.client
|
|
real_bot.database = bot.database
|
|
real_bot.log = bot.log
|
|
|
|
# Mock required methods
|
|
with patch.object(real_bot, 'user_permitted', return_value=True), \
|
|
patch.object(real_bot, 'create_verification_dm', return_value="!dm:example.com"):
|
|
|
|
await real_bot.verify_user(mock_evt, "@test:example.com", "!room:example.com")
|
|
|
|
mock_evt.respond.assert_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_commands_require_permission(self, bot, mock_evt):
|
|
"""Test that commands require proper permissions."""
|
|
from community.bot import CommunityBot
|
|
|
|
real_bot = CommunityBot()
|
|
real_bot.config = bot.config
|
|
real_bot.log = bot.log
|
|
|
|
# Test various commands that require permission
|
|
commands_to_test = [
|
|
('sync_space_members', []),
|
|
('ignore_user', ['@test:example.com']),
|
|
('unignore_user', ['@test:example.com']),
|
|
('kick_user', ['@test:example.com']),
|
|
('ban_user', ['@test:example.com']),
|
|
('doctor', []),
|
|
('doctor_room_detail', ['!room:example.com']),
|
|
('initialize', ['Test Community']),
|
|
('create_room', ['Test Room']),
|
|
('archive_room', ['!room:example.com']),
|
|
('remove_room', ['!room:example.com']),
|
|
('join_room', ['!room:example.com']),
|
|
('leave_room', ['!room:example.com']),
|
|
('verify_user', ['@test:example.com', '!room:example.com'])
|
|
]
|
|
|
|
for command_name, args in commands_to_test:
|
|
with patch.object(real_bot, 'user_permitted', return_value=False):
|
|
command_func = getattr(real_bot, command_name)
|
|
await command_func(mock_evt, *args)
|
|
|
|
# Should respond with permission denied message
|
|
mock_evt.reply.assert_called()
|
|
mock_evt.reply.reset_mock()
|