more refactoring

This commit is contained in:
William Kray
2025-09-09 14:49:45 -07:00
parent 6582112dfb
commit 87e02b7ea6
28 changed files with 4664 additions and 894 deletions
+1
View File
@@ -0,0 +1 @@
# Test package for community bot
+430
View File
@@ -0,0 +1,430 @@
"""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()
+452
View File
@@ -0,0 +1,452 @@
"""Tests for bot event handlers."""
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from mautrix.types import EventType, UserID, MessageEvent, StateEvent, ReactionEvent
from mautrix.errors import MNotFound
from community.bot import CommunityBot
class TestBotEvents:
"""Test cases for bot event 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,
"track_messages": True,
"track_reactions": 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,
"proactive_banning": True,
"check_if_human": True,
"verification_phrases": ["test phrase"],
"verification_attempts": 3,
"verification_message": "Please verify",
"invite_power_level": 50,
"uncensor_pl": 50
}
return bot
@pytest.fixture
def mock_message_evt(self):
"""Create a mock MessageEvent for testing."""
evt = Mock(spec=MessageEvent)
evt.sender = "@user:example.com"
evt.room_id = "!room:example.com"
evt.timestamp = 1234567890
evt.content = Mock()
evt.content.body = "test message"
evt.content.msgtype = "m.text"
evt.reply = AsyncMock()
evt.respond = AsyncMock()
evt.react = AsyncMock()
return evt
@pytest.fixture
def mock_state_evt(self):
"""Create a mock StateEvent for testing."""
evt = Mock(spec=StateEvent)
evt.sender = "@user:example.com"
evt.room_id = "!room:example.com"
evt.state_key = "@user:example.com"
evt.content = {
"entity": "@banned:example.com",
"recommendation": "ban"
}
evt.prev_content = {}
return evt
@pytest.fixture
def mock_reaction_evt(self):
"""Create a mock ReactionEvent for testing."""
evt = Mock(spec=ReactionEvent)
evt.sender = "@user:example.com"
evt.room_id = "!room:example.com"
evt.content = Mock()
evt.content.relates_to = Mock()
evt.content.relates_to.event_id = "!msg:example.com"
evt.content.relates_to.key = "👍"
return evt
@pytest.mark.asyncio
async def test_check_ban_event_proactive_banning_enabled(self, bot, mock_state_evt):
"""Test ban event handler with proactive banning enabled."""
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, 'get_banlist_roomids', return_value=["!banlist:example.com"]), \
patch.object(real_bot, 'ban_this_user', return_value={"ban_list": {}, "error_list": {}}):
await real_bot.check_ban_event(mock_state_evt)
# Should call ban_this_user
real_bot.ban_this_user.assert_called_once_with("@banned:example.com")
@pytest.mark.asyncio
async def test_check_ban_event_proactive_banning_disabled(self, bot, mock_state_evt):
"""Test ban event handler with proactive banning disabled."""
from community.bot import CommunityBot
real_bot = CommunityBot()
real_bot.config = {**bot.config, "proactive_banning": False}
real_bot.client = bot.client
real_bot.log = bot.log
with patch.object(real_bot, 'get_banlist_roomids', return_value=["!banlist:example.com"]):
await real_bot.check_ban_event(mock_state_evt)
# Should not call ban_this_user
real_bot.ban_this_user.assert_not_called()
@pytest.mark.asyncio
async def test_check_ban_event_wrong_room(self, bot, mock_state_evt):
"""Test ban event handler with wrong room."""
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, 'get_banlist_roomids', return_value=["!other:example.com"]):
await real_bot.check_ban_event(mock_state_evt)
# Should not call ban_this_user
real_bot.ban_this_user.assert_not_called()
@pytest.mark.asyncio
async def test_sync_power_levels(self, bot, mock_state_evt):
"""Test power levels sync event handler."""
from community.bot import CommunityBot
real_bot = CommunityBot()
real_bot.config = bot.config
real_bot.client = bot.client
real_bot.log = bot.log
# Mock power level changes
mock_state_evt.prev_content = {"users": {"@user:example.com": 25}}
mock_state_evt.content = {"users": {"@user:example.com": 50}}
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com", "!room2:example.com"]), \
patch.object(real_bot, 'sync_power_levels_to_room', return_value=None):
await real_bot.sync_power_levels(mock_state_evt)
# Should sync to all rooms
assert real_bot.sync_power_levels_to_room.call_count == 2
@pytest.mark.asyncio
async def test_sync_power_levels_wrong_room(self, bot, mock_state_evt):
"""Test power levels sync with wrong room."""
from community.bot import CommunityBot
real_bot = CommunityBot()
real_bot.config = bot.config
real_bot.client = bot.client
real_bot.log = bot.log
mock_state_evt.room_id = "!other:example.com"
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]):
await real_bot.sync_power_levels(mock_state_evt)
# Should not sync to any rooms
real_bot.sync_power_levels_to_room.assert_not_called()
@pytest.mark.asyncio
async def test_handle_leave_events(self, bot, mock_state_evt):
"""Test leave events handler."""
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 database operations
real_bot.database.execute = AsyncMock()
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com", "!room2:example.com"]):
await real_bot.handle_leave_events(mock_state_evt)
# Should delete user from database
real_bot.database.execute.assert_called()
@pytest.mark.asyncio
async def test_handle_leave(self, bot, mock_state_evt):
"""Test leave event handler."""
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
with patch.object(real_bot, 'handle_leave_events', return_value=None):
await real_bot.handle_leave(mock_state_evt)
real_bot.handle_leave_events.assert_called_once_with(mock_state_evt)
@pytest.mark.asyncio
async def test_handle_kick(self, bot, mock_state_evt):
"""Test kick event handler."""
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
with patch.object(real_bot, 'handle_leave_events', return_value=None):
await real_bot.handle_kick(mock_state_evt)
real_bot.handle_leave_events.assert_called_once_with(mock_state_evt)
@pytest.mark.asyncio
async def test_handle_ban(self, bot, mock_state_evt):
"""Test ban event handler."""
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
with patch.object(real_bot, 'handle_leave_events', return_value=None):
await real_bot.handle_ban(mock_state_evt)
real_bot.handle_leave_events.assert_called_once_with(mock_state_evt)
@pytest.mark.asyncio
async def test_newjoin_event(self, bot, mock_state_evt):
"""Test new join event handler."""
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 database operations
real_bot.database.execute = AsyncMock()
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com", "!room2:example.com"]), \
patch.object(real_bot, 'upsert_user_timestamp', return_value=None):
await real_bot.newjoin(mock_state_evt)
# Should update user timestamp
real_bot.upsert_user_timestamp.assert_called()
@pytest.mark.asyncio
async def test_update_message_timestamp_tracking_enabled(self, bot, mock_message_evt):
"""Test message timestamp update with tracking enabled."""
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 power levels
power_levels = Mock()
power_levels.get_user_level.return_value = 25
real_bot.client.get_state_event = AsyncMock(return_value=power_levels)
real_bot.database.execute = AsyncMock()
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]), \
patch.object(real_bot, 'upsert_user_timestamp', return_value=None):
await real_bot.update_message_timestamp(mock_message_evt)
# Should update user timestamp
real_bot.upsert_user_timestamp.assert_called()
@pytest.mark.asyncio
async def test_update_message_timestamp_tracking_disabled(self, bot, mock_message_evt):
"""Test message timestamp update with tracking disabled."""
from community.bot import CommunityBot
real_bot = CommunityBot()
real_bot.config = {**bot.config, "track_messages": False}
real_bot.client = bot.client
real_bot.database = bot.database
real_bot.log = bot.log
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]):
await real_bot.update_message_timestamp(mock_message_evt)
# Should not update user timestamp
real_bot.upsert_user_timestamp.assert_not_called()
@pytest.mark.asyncio
async def test_handle_verification(self, bot, mock_message_evt):
"""Test verification message handler."""
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 verification state
verification_state = {
"user_id": "@user:example.com",
"target_room_id": "!room:example.com",
"verification_phrase": "test phrase",
"attempts_remaining": 3,
"required_power_level": 50
}
real_bot.database.fetchrow = AsyncMock(return_value=verification_state)
real_bot.database.execute = AsyncMock()
# Mock message content
mock_message_evt.content.body = "test phrase"
with patch.object(real_bot, 'user_permitted', return_value=True), \
patch.object(real_bot, 'join_room', return_value="!room:example.com"):
await real_bot.handle_verification(mock_message_evt)
# Should process verification
real_bot.database.execute.assert_called()
@pytest.mark.asyncio
async def test_handle_verification_wrong_phrase(self, bot, mock_message_evt):
"""Test verification with wrong phrase."""
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 verification state
verification_state = {
"user_id": "@user:example.com",
"target_room_id": "!room:example.com",
"verification_phrase": "correct phrase",
"attempts_remaining": 3,
"required_power_level": 50
}
real_bot.database.fetchrow = AsyncMock(return_value=verification_state)
real_bot.database.execute = AsyncMock()
# Mock message content with wrong phrase
mock_message_evt.content.body = "wrong phrase"
await real_bot.handle_verification(mock_message_evt)
# Should decrement attempts
real_bot.database.execute.assert_called()
@pytest.mark.asyncio
async def test_handle_verification_no_state(self, bot, mock_message_evt):
"""Test verification with no verification state."""
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 no verification state
real_bot.database.fetchrow = AsyncMock(return_value=None)
await real_bot.handle_verification(mock_message_evt)
# Should not process verification
real_bot.database.execute.assert_not_called()
@pytest.mark.asyncio
async def test_handle_reaction_tracking_enabled(self, bot, mock_reaction_evt):
"""Test reaction event handler with tracking enabled."""
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 power levels
power_levels = Mock()
power_levels.get_user_level.return_value = 25
real_bot.client.get_state_event = AsyncMock(return_value=power_levels)
real_bot.database.execute = AsyncMock()
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]), \
patch.object(real_bot, 'upsert_user_timestamp', return_value=None):
await real_bot.handle_reaction(mock_reaction_evt)
# Should update user timestamp
real_bot.upsert_user_timestamp.assert_called()
@pytest.mark.asyncio
async def test_handle_reaction_tracking_disabled(self, bot, mock_reaction_evt):
"""Test reaction event handler with tracking disabled."""
from community.bot import CommunityBot
real_bot = CommunityBot()
real_bot.config = {**bot.config, "track_reactions": False}
real_bot.client = bot.client
real_bot.database = bot.database
real_bot.log = bot.log
with patch.object(real_bot, 'get_space_roomlist', return_value=["!room1:example.com"]):
await real_bot.handle_reaction(mock_reaction_evt)
# Should not update user timestamp
real_bot.upsert_user_timestamp.assert_not_called()
@pytest.mark.asyncio
async def test_handle_reaction_wrong_room(self, bot, mock_reaction_evt):
"""Test reaction event handler with wrong room."""
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
with patch.object(real_bot, 'get_space_roomlist', return_value=["!other:example.com"]):
await real_bot.handle_reaction(mock_reaction_evt)
# Should not update user timestamp
real_bot.upsert_user_timestamp.assert_not_called()
+253
View File
@@ -0,0 +1,253 @@
"""Tests for database utility functions."""
import pytest
from unittest.mock import Mock, AsyncMock, patch
import asyncio
from community.helpers.database_utils import (
get_messages_to_redact, redact_messages, upsert_user_timestamp,
get_inactive_users, cleanup_stale_verification_states,
get_verification_state, create_verification_state,
update_verification_attempts, delete_verification_state
)
class TestDatabaseUtils:
"""Test cases for database utility functions."""
@pytest.mark.asyncio
async def test_get_messages_to_redact_success(self):
"""Test getting messages to redact successfully."""
client = Mock()
# Mock message events
msg1 = Mock()
msg1.content = Mock()
msg1.content.serialize.return_value = {"body": "test"}
msg2 = Mock()
msg2.content = None
msg3 = Mock()
msg3.content = Mock()
msg3.content.serialize.return_value = {"body": "test2"}
messages = Mock()
messages.events = [msg1, msg2, msg3]
client.get_messages = AsyncMock(return_value=messages)
logger = Mock()
result = await get_messages_to_redact(client, "!room:example.com", "@user:example.com", logger)
assert len(result) == 2 # Only msg1 and msg3 have content
assert msg1 in result
assert msg3 in result
assert msg2 not in result
@pytest.mark.asyncio
async def test_get_messages_to_redact_error(self):
"""Test getting messages to redact with error."""
client = Mock()
client.get_messages = AsyncMock(side_effect=Exception("Network error"))
logger = Mock()
result = await get_messages_to_redact(client, "!room:example.com", "@user:example.com", logger)
assert result == []
logger.error.assert_called()
@pytest.mark.asyncio
async def test_redact_messages_success(self):
"""Test redacting messages successfully."""
client = Mock()
client.redact = AsyncMock()
database = Mock()
database.fetch = AsyncMock(return_value=[
{"event_id": "!msg1:example.com"},
{"event_id": "!msg2:example.com"}
])
database.execute = AsyncMock()
logger = Mock()
with patch('asyncio.sleep', new_callable=AsyncMock):
result = await redact_messages(client, database, "!room:example.com", 0.1, logger)
assert result["success"] == 2
assert result["failure"] == 0
assert client.redact.call_count == 2
assert database.execute.call_count == 2
@pytest.mark.asyncio
async def test_redact_messages_rate_limited(self):
"""Test redacting messages with rate limiting."""
client = Mock()
client.redact = AsyncMock(side_effect=Exception("Too Many Requests"))
database = Mock()
database.fetch = AsyncMock(return_value=[
{"event_id": "!msg1:example.com"}
])
logger = Mock()
with patch('asyncio.sleep', new_callable=AsyncMock):
result = await redact_messages(client, database, "!room:example.com", 0.1, logger)
assert result["success"] == 0
assert result["failure"] == 0 # Rate limited, so no failure count
logger.warning.assert_called()
@pytest.mark.asyncio
async def test_upsert_user_timestamp_success(self):
"""Test upserting user timestamp successfully."""
database = Mock()
database.execute = AsyncMock()
logger = Mock()
await upsert_user_timestamp(database, "@user:example.com", 1234567890, logger)
database.execute.assert_called_once()
@pytest.mark.asyncio
async def test_get_inactive_users_success(self):
"""Test getting inactive users successfully."""
database = Mock()
database.fetch = AsyncMock(side_effect=[
[{"mxid": "@user1:example.com"}, {"mxid": "@user2:example.com"}], # warn results
[{"mxid": "@user3:example.com"}] # kick results
])
logger = Mock()
with patch('time.time', return_value=1234567890):
result = await get_inactive_users(database, 7, 14, logger)
assert len(result["warn"]) == 2
assert len(result["kick"]) == 1
assert "@user1:example.com" in result["warn"]
assert "@user3:example.com" in result["kick"]
@pytest.mark.asyncio
async def test_get_inactive_users_error(self):
"""Test getting inactive users with error."""
database = Mock()
database.fetch = AsyncMock(side_effect=Exception("Database error"))
logger = Mock()
result = await get_inactive_users(database, 7, 14, logger)
assert result == {"warn": [], "kick": []}
logger.error.assert_called()
@pytest.mark.asyncio
async def test_cleanup_stale_verification_states_success(self):
"""Test cleaning up stale verification states successfully."""
database = Mock()
database.execute = AsyncMock()
logger = Mock()
await cleanup_stale_verification_states(database, logger)
database.execute.assert_called_once()
@pytest.mark.asyncio
async def test_cleanup_stale_verification_states_error(self):
"""Test cleaning up stale verification states with error."""
database = Mock()
database.execute = AsyncMock(side_effect=Exception("Database error"))
logger = Mock()
await cleanup_stale_verification_states(database, logger)
logger.error.assert_called()
@pytest.mark.asyncio
async def test_get_verification_state_success(self):
"""Test getting verification state successfully."""
database = Mock()
database.fetchrow = AsyncMock(return_value={
"dm_room_id": "!dm:example.com",
"user_id": "@user:example.com",
"target_room_id": "!room:example.com",
"verification_phrase": "test phrase",
"attempts_remaining": 3,
"required_power_level": 50
})
result = await get_verification_state(database, "!dm:example.com")
assert result is not None
assert result["dm_room_id"] == "!dm:example.com"
assert result["user_id"] == "@user:example.com"
@pytest.mark.asyncio
async def test_get_verification_state_not_found(self):
"""Test getting verification state when not found."""
database = Mock()
database.fetchrow = AsyncMock(return_value=None)
result = await get_verification_state(database, "!dm:example.com")
assert result is None
@pytest.mark.asyncio
async def test_get_verification_state_error(self):
"""Test getting verification state with error."""
database = Mock()
database.fetchrow = AsyncMock(side_effect=Exception("Database error"))
result = await get_verification_state(database, "!dm:example.com")
assert result is None
@pytest.mark.asyncio
async def test_create_verification_state_success(self):
"""Test creating verification state successfully."""
database = Mock()
database.execute = AsyncMock()
await create_verification_state(
database, "!dm:example.com", "@user:example.com",
"!room:example.com", "test phrase", 3, 50
)
database.execute.assert_called_once()
@pytest.mark.asyncio
async def test_create_verification_state_error(self):
"""Test creating verification state with error (should not raise)."""
database = Mock()
database.execute = AsyncMock(side_effect=Exception("Database error"))
# Should not raise exception
await create_verification_state(
database, "!dm:example.com", "@user:example.com",
"!room:example.com", "test phrase", 3, 50
)
@pytest.mark.asyncio
async def test_update_verification_attempts_success(self):
"""Test updating verification attempts successfully."""
database = Mock()
database.execute = AsyncMock()
await update_verification_attempts(database, "!dm:example.com", 2)
database.execute.assert_called_once()
@pytest.mark.asyncio
async def test_delete_verification_state_success(self):
"""Test deleting verification state successfully."""
database = Mock()
database.execute = AsyncMock()
await delete_verification_state(database, "!dm:example.com")
database.execute.assert_called_once()
+106
View File
@@ -0,0 +1,106 @@
"""Tests for message utility functions."""
import pytest
from unittest.mock import Mock
from mautrix.types import MessageType, MediaMessageEventContent
from community.helpers.message_utils import (
flag_message, flag_instaban, censor_room,
sanitize_room_name, generate_community_slug
)
class TestMessageUtils:
"""Test cases for message utility functions."""
def test_flag_message_file_types(self):
"""Test that file messages are flagged when censor_files is True."""
msg = Mock()
msg.content.msgtype = MessageType.FILE
msg.content.body = "test file"
assert flag_message(msg, [], True) == True
assert flag_message(msg, [], False) == False
def test_flag_message_wordlist(self):
"""Test that messages are flagged based on wordlist patterns."""
msg = Mock()
msg.content.msgtype = MessageType.TEXT
msg.content.body = "This is a test message with badword"
wordlist = [r"badword", r"another.*pattern"]
assert flag_message(msg, wordlist, False) == True
msg.content.body = "This is a clean message"
assert flag_message(msg, wordlist, False) == False
def test_flag_message_invalid_regex(self):
"""Test that invalid regex patterns are handled gracefully."""
msg = Mock()
msg.content.msgtype = MessageType.TEXT
msg.content.body = "test message"
wordlist = [r"valid.*pattern", r"[invalid", r"another.*pattern"]
# Should not raise exception and should work with valid patterns
result = flag_message(msg, wordlist, False)
assert isinstance(result, bool)
def test_flag_instaban(self):
"""Test instant ban flagging."""
msg = Mock()
msg.content.msgtype = MessageType.TEXT
msg.content.body = "This contains instaban_word"
instaban_list = [r"instaban_word", r"another.*instaban"]
assert flag_instaban(msg, instaban_list) == True
msg.content.body = "This is clean"
assert flag_instaban(msg, instaban_list) == False
def test_censor_room_boolean_config(self):
"""Test room censoring with boolean configuration."""
msg = Mock()
msg.room_id = "!room123:example.com"
assert censor_room(msg, True) == True
assert censor_room(msg, False) == False
def test_censor_room_list_config(self):
"""Test room censoring with list configuration."""
msg = Mock()
msg.room_id = "!room123:example.com"
censor_list = ["!room123:example.com", "!room456:example.com"]
assert censor_room(msg, censor_list) == True
msg.room_id = "!room789:example.com"
assert censor_room(msg, censor_list) == False
def test_censor_room_invalid_config(self):
"""Test room censoring with invalid configuration."""
msg = Mock()
msg.room_id = "!room123:example.com"
assert censor_room(msg, "invalid") == False
assert censor_room(msg, None) == False
def test_sanitize_room_name(self):
"""Test room name sanitization."""
assert sanitize_room_name("Test Room 123") == "testroom123"
assert sanitize_room_name("Special@#$%Characters") == "specialcharacters"
assert sanitize_room_name("UPPERCASE") == "uppercase"
assert sanitize_room_name("123 Numbers") == "123numbers"
assert sanitize_room_name("") == ""
def test_generate_community_slug(self):
"""Test community slug generation."""
assert generate_community_slug("Test Community") == "tc"
assert generate_community_slug("My Awesome Community") == "mac"
assert generate_community_slug("Single") == "s"
assert generate_community_slug("Multiple Spaces") == "ms"
assert generate_community_slug("") == ""
assert generate_community_slug(" ") == ""
+163
View File
@@ -0,0 +1,163 @@
"""Tests for report utility functions."""
import pytest
from community.helpers.report_utils import (
generate_activity_report, split_doctor_report, format_ban_results,
format_sync_results
)
class TestReportUtils:
"""Test cases for report utility functions."""
def test_generate_activity_report_success(self):
"""Test generating activity report successfully."""
database_results = {
"active": [{"mxid": "@user1:example.com"}, {"mxid": "@user2:example.com"}],
"inactive": [{"mxid": "@user3:example.com"}],
"ignored": [{"mxid": "@user4:example.com"}]
}
result = generate_activity_report(database_results)
assert result["active"] == ["@user1:example.com", "@user2:example.com"]
assert result["inactive"] == ["@user3:example.com"]
assert result["ignored"] == ["@user4:example.com"]
def test_generate_activity_report_empty(self):
"""Test generating activity report with empty results."""
database_results = {
"active": [],
"inactive": [],
"ignored": []
}
result = generate_activity_report(database_results)
assert result["active"] == ["none"]
assert result["inactive"] == ["none"]
assert result["ignored"] == ["none"]
def test_generate_activity_report_missing_keys(self):
"""Test generating activity report with missing keys."""
database_results = {}
result = generate_activity_report(database_results)
assert result["active"] == ["none"]
assert result["inactive"] == ["none"]
assert result["ignored"] == ["none"]
def test_split_doctor_report_small(self):
"""Test splitting small report that doesn't need splitting."""
report_text = "This is a small report that fits in one chunk."
result = split_doctor_report(report_text, 1000)
assert len(result) == 1
assert result[0] == report_text
def test_split_doctor_report_large(self):
"""Test splitting large report into chunks."""
# Create a large report
lines = [f"Line {i}: This is a test line for splitting" for i in range(100)]
report_text = "\n".join(lines)
result = split_doctor_report(report_text, 100) # Small chunk size
assert len(result) > 1
assert all(len(chunk) <= 100 for chunk in result)
# Verify all content is preserved (account for newlines)
combined = "\n".join(result)
assert combined == report_text
def test_split_doctor_report_with_sections(self):
"""Test splitting report with section headers."""
report_text = """<h3>Section 1</h3>
This is section 1 content.
<h3>Section 2</h3>
This is section 2 content.
<h3>Section 3</h3>
This is section 3 content."""
result = split_doctor_report(report_text, 50) # Small chunk size
assert len(result) > 1
assert all(len(chunk) <= 50 for chunk in result)
def test_format_ban_results_success(self):
"""Test formatting ban results with successful bans."""
ban_event_map = {
"ban_list": {
"@user1:example.com": ["Room 1", "Room 2"],
"@user2:example.com": ["Room 3"]
},
"error_list": {}
}
result = format_ban_results(ban_event_map)
assert "Banned @user1:example.com from: Room 1, Room 2" in result
assert "Banned @user2:example.com from: Room 3" in result
def test_format_ban_results_with_errors(self):
"""Test formatting ban results with errors."""
ban_event_map = {
"ban_list": {
"@user1:example.com": ["Room 1"]
},
"error_list": {
"@user2:example.com": ["Room 2", "Room 3"]
}
}
result = format_ban_results(ban_event_map)
assert "Banned @user1:example.com from: Room 1" in result
assert "Failed to ban @user2:example.com from: Room 2, Room 3" in result
def test_format_ban_results_empty(self):
"""Test formatting empty ban results."""
ban_event_map = {
"ban_list": {},
"error_list": {}
}
result = format_ban_results(ban_event_map)
assert result == "No ban operations performed"
def test_format_sync_results_success(self):
"""Test formatting sync results with data."""
sync_results = {
"added": ["@user1:example.com", "@user2:example.com"],
"dropped": ["@user3:example.com"]
}
result = format_sync_results(sync_results)
assert "Added: @user1:example.com<br />@user2:example.com" in result
assert "Dropped: @user3:example.com" in result
def test_format_sync_results_empty(self):
"""Test formatting empty sync results."""
sync_results = {
"added": [],
"dropped": []
}
result = format_sync_results(sync_results)
assert "Added: none" in result
assert "Dropped: none" in result
def test_format_sync_results_missing_keys(self):
"""Test formatting sync results with missing keys."""
sync_results = {}
result = format_sync_results(sync_results)
assert "Added: none" in result
assert "Dropped: none" in result
+203
View File
@@ -0,0 +1,203 @@
"""Tests for room utility functions."""
import pytest
from unittest.mock import Mock, AsyncMock, patch
from mautrix.types import EventType, PowerLevelStateEventContent
from mautrix.errors import MNotFound
from community.helpers.room_utils import (
validate_room_alias, validate_room_aliases, get_room_version_and_creators,
is_modern_room_version, user_has_unlimited_power, get_moderators_and_above
)
class TestRoomUtils:
"""Test cases for room utility functions."""
@pytest.mark.asyncio
async def test_validate_room_alias_exists(self):
"""Test alias validation when alias exists."""
client = Mock()
client.resolve_room_alias = AsyncMock()
# Alias exists - should return False
result = await validate_room_alias(client, "test", "example.com")
assert result == False
client.resolve_room_alias.assert_called_once_with("#test:example.com")
@pytest.mark.asyncio
async def test_validate_room_alias_not_exists(self):
"""Test alias validation when alias doesn't exist."""
client = Mock()
client.resolve_room_alias = AsyncMock(side_effect=MNotFound("Room not found", 404))
# Alias doesn't exist - should return True
result = await validate_room_alias(client, "test", "example.com")
assert result == True
@pytest.mark.asyncio
async def test_validate_room_alias_error(self):
"""Test alias validation with error."""
client = Mock()
client.resolve_room_alias = AsyncMock(side_effect=Exception("Network error"))
# Error should return True (assume available)
result = await validate_room_alias(client, "test", "example.com")
assert result == True
@pytest.mark.asyncio
async def test_validate_room_aliases_no_slug(self):
"""Test alias validation without community slug."""
client = Mock()
result = await validate_room_aliases(client, ["room1", "room2"], "", "example.com")
assert result == (False, [])
@pytest.mark.asyncio
async def test_validate_room_aliases_success(self):
"""Test successful alias validation."""
client = Mock()
client.resolve_room_alias = AsyncMock(side_effect=MNotFound("Room not found", 404))
result = await validate_room_aliases(client, ["room1", "room2"], "test", "example.com")
assert result == (True, [])
@pytest.mark.asyncio
async def test_validate_room_aliases_conflicts(self):
"""Test alias validation with conflicts."""
client = Mock()
def resolve_side_effect(alias):
if "room1" in alias:
return {"room_id": "!room1:example.com"} # Exists
else:
raise MNotFound() # Doesn't exist
client.resolve_room_alias = AsyncMock(side_effect=resolve_side_effect)
result = await validate_room_aliases(client, ["room1", "room2"], "test", "example.com")
assert result == (False, ["#room1-test:example.com"])
@pytest.mark.asyncio
async def test_get_room_version_and_creators_success(self):
"""Test getting room version and creators successfully."""
client = Mock()
# Mock state events
create_event = Mock()
create_event.type = EventType.ROOM_CREATE
create_event.sender = "@creator:example.com"
create_event.content = {
"room_version": "12",
"additional_creators": ["@creator2:example.com"]
}
other_event = Mock()
other_event.type = EventType.ROOM_POWER_LEVELS
client.get_state = AsyncMock(return_value=[create_event, other_event])
version, creators = await get_room_version_and_creators(client, "!room:example.com")
assert version == "12"
assert "@creator:example.com" in creators
assert "@creator2:example.com" in creators
@pytest.mark.asyncio
async def test_get_room_version_and_creators_no_create_event(self):
"""Test getting room version when no create event exists."""
client = Mock()
client.get_state = AsyncMock(return_value=[])
version, creators = await get_room_version_and_creators(client, "!room:example.com")
assert version == "1"
assert creators == []
@pytest.mark.asyncio
async def test_get_room_version_and_creators_error(self):
"""Test getting room version with error."""
client = Mock()
client.get_state = AsyncMock(side_effect=Exception("Network error"))
version, creators = await get_room_version_and_creators(client, "!room:example.com")
assert version == "1"
assert creators == []
def test_is_modern_room_version(self):
"""Test modern room version detection."""
assert is_modern_room_version("12") == True
assert is_modern_room_version("13") == True
assert is_modern_room_version("11") == False
assert is_modern_room_version("1") == False
assert is_modern_room_version("invalid") == False
assert is_modern_room_version("") == False
@pytest.mark.asyncio
async def test_user_has_unlimited_power_modern_room(self):
"""Test unlimited power check in modern room."""
client = Mock()
with patch('community.helpers.room_utils.get_room_version_and_creators') as mock_get_version:
mock_get_version.return_value = ("12", ["@user:example.com"])
result = await user_has_unlimited_power(client, "@user:example.com", "!room:example.com")
assert result == True
result = await user_has_unlimited_power(client, "@other:example.com", "!room:example.com")
assert result == False
@pytest.mark.asyncio
async def test_user_has_unlimited_power_old_room(self):
"""Test unlimited power check in old room."""
client = Mock()
with patch('community.helpers.room_utils.get_room_version_and_creators') as mock_get_version:
mock_get_version.return_value = ("11", ["@user:example.com"])
result = await user_has_unlimited_power(client, "@user:example.com", "!room:example.com")
assert result == False
@pytest.mark.asyncio
async def test_user_has_unlimited_power_error(self):
"""Test unlimited power check with error."""
client = Mock()
with patch('community.helpers.room_utils.get_room_version_and_creators') as mock_get_version:
mock_get_version.side_effect = Exception("Network error")
result = await user_has_unlimited_power(client, "@user:example.com", "!room:example.com")
assert result == False
@pytest.mark.asyncio
async def test_get_moderators_and_above_success(self):
"""Test getting moderators successfully."""
client = Mock()
power_levels = Mock()
power_levels.users = {
"@user1:example.com": 50, # Moderator
"@user2:example.com": 100, # Admin
"@user3:example.com": 25, # Regular user
"@user4:example.com": 75, # Above moderator
}
client.get_state_event = AsyncMock(return_value=power_levels)
moderators = await get_moderators_and_above(client, "!room:example.com")
assert "@user1:example.com" in moderators
assert "@user2:example.com" in moderators
assert "@user4:example.com" in moderators
assert "@user3:example.com" not in moderators
@pytest.mark.asyncio
async def test_get_moderators_and_above_error(self):
"""Test getting moderators with error."""
client = Mock()
client.get_state_event = AsyncMock(side_effect=Exception("Network error"))
moderators = await get_moderators_and_above(client, "!room:example.com")
assert moderators == []
+223
View File
@@ -0,0 +1,223 @@
"""Tests for user utility functions."""
import pytest
from unittest.mock import Mock, AsyncMock, patch
from mautrix.types import EventType, UserID
from mautrix.errors import MNotFound
from community.helpers.user_utils import (
check_if_banned, get_banlist_roomids, ban_user_from_rooms, user_permitted
)
class TestUserUtils:
"""Test cases for user utility functions."""
@pytest.mark.asyncio
async def test_check_if_banned_success(self):
"""Test successful ban check."""
client = Mock()
client.get_joined_rooms = AsyncMock(return_value=["!room1:example.com", "!room2:example.com"])
# Mock state events
ban_rule = Mock()
ban_rule.type.t = "m.policy.rule.user"
ban_rule.content = {
"entity": "@banned:example.com",
"recommendation": "ban"
}
client.get_state = AsyncMock(return_value=[ban_rule])
# Mock get_banlist_roomids to return the room ID directly
with patch('community.helpers.user_utils.get_banlist_roomids') as mock_get_banlists:
mock_get_banlists.return_value = ["!room1:example.com"]
logger = Mock()
result = await check_if_banned(client, "@banned:example.com", ["!room1:example.com"], logger)
assert result == True
@pytest.mark.asyncio
async def test_check_if_banned_not_banned(self):
"""Test ban check when user is not banned."""
client = Mock()
client.get_joined_rooms = AsyncMock(return_value=["!room1:example.com"])
with patch('community.helpers.user_utils.get_banlist_roomids') as mock_get_banlists:
mock_get_banlists.return_value = ["!room1:example.com"]
# Mock state events with no ban rules
client.get_state = AsyncMock(return_value=[])
logger = Mock()
result = await check_if_banned(client, "@user:example.com", ["!room1:example.com"], logger)
assert result == False
@pytest.mark.asyncio
async def test_check_if_banned_room_not_joined(self):
"""Test ban check when bot is not in banlist room."""
client = Mock()
client.get_joined_rooms = AsyncMock(return_value=["!room2:example.com"])
with patch('community.helpers.user_utils.get_banlist_roomids') as mock_get_banlists:
mock_get_banlists.return_value = ["!room1:example.com"]
logger = Mock()
result = await check_if_banned(client, "@user:example.com", ["!room1:example.com"], logger)
assert result == False
logger.error.assert_called()
@pytest.mark.asyncio
async def test_get_banlist_roomids_aliases(self):
"""Test getting banlist room IDs with aliases."""
client = Mock()
client.resolve_room_alias = AsyncMock(return_value={"room_id": "!room1:example.com"})
banlists = ["#banlist1:example.com", "!room2:example.com"]
logger = Mock()
result = await get_banlist_roomids(client, banlists, logger)
assert "!room1:example.com" in result
assert "!room2:example.com" in result
client.resolve_room_alias.assert_called_once_with("#banlist1:example.com")
@pytest.mark.asyncio
async def test_get_banlist_roomids_alias_error(self):
"""Test getting banlist room IDs with alias resolution error."""
client = Mock()
client.resolve_room_alias = AsyncMock(side_effect=Exception("Network error"))
banlists = ["#banlist1:example.com", "!room2:example.com"]
logger = Mock()
result = await get_banlist_roomids(client, banlists, logger)
assert "!room2:example.com" in result
assert "!room1:example.com" not in result
logger.error.assert_called()
@pytest.mark.asyncio
async def test_ban_user_from_rooms_success(self):
"""Test successful user banning from rooms."""
client = Mock()
client.ban_user = AsyncMock()
client.get_state_event = AsyncMock(return_value={"name": "Test Room"})
roomlist = ["!room1:example.com", "!room2:example.com"]
logger = Mock()
result = await ban_user_from_rooms(
client, "@user:example.com", roomlist, "banned", False, False, None, None, 0.1, logger
)
assert "ban_list" in result
assert "error_list" in result
assert "@user:example.com" in result["ban_list"]
assert len(result["ban_list"]["@user:example.com"]) == 2
@pytest.mark.asyncio
async def test_ban_user_from_rooms_with_redaction(self):
"""Test user banning with message redaction."""
client = Mock()
client.ban_user = AsyncMock()
client.get_state_event = AsyncMock(return_value={"name": "Test Room"})
# Mock message redaction
mock_msg = Mock()
mock_msg.event_id = "!msg123:example.com"
get_messages_func = AsyncMock(return_value=[mock_msg])
database = Mock()
database.execute = AsyncMock()
roomlist = ["!room1:example.com"]
logger = Mock()
result = await ban_user_from_rooms(
client, "@user:example.com", roomlist, "banned", False, True,
get_messages_func, database, 0.1, logger
)
assert "ban_list" in result
database.execute.assert_called()
@pytest.mark.asyncio
async def test_ban_user_from_rooms_error(self):
"""Test user banning with errors."""
client = Mock()
client.ban_user = AsyncMock(side_effect=Exception("Ban failed"))
client.get_state_event = AsyncMock(return_value={"name": "Test Room"})
roomlist = ["!room1:example.com"]
logger = Mock()
result = await ban_user_from_rooms(
client, "@user:example.com", roomlist, "banned", False, False, None, None, 0.1, logger
)
assert "error_list" in result
assert "@user:example.com" in result["error_list"]
@pytest.mark.asyncio
async def test_user_permitted_unlimited_power(self):
"""Test user permission check with unlimited power."""
client = Mock()
with patch('community.helpers.room_utils.user_has_unlimited_power') as mock_unlimited:
mock_unlimited.return_value = True
result = await user_permitted(client, "@user:example.com", "!parent:example.com", 50, None, None)
assert result == True
@pytest.mark.asyncio
async def test_user_permitted_sufficient_level(self):
"""Test user permission check with sufficient power level."""
client = Mock()
with patch('community.helpers.room_utils.user_has_unlimited_power') as mock_unlimited:
mock_unlimited.return_value = False
power_levels = Mock()
power_levels.get_user_level.return_value = 75
client.get_state_event = AsyncMock(return_value=power_levels)
result = await user_permitted(client, "@user:example.com", "!parent:example.com", 50, None, None)
assert result == True
@pytest.mark.asyncio
async def test_user_permitted_insufficient_level(self):
"""Test user permission check with insufficient power level."""
client = Mock()
with patch('community.helpers.room_utils.user_has_unlimited_power') as mock_unlimited:
mock_unlimited.return_value = False
power_levels = Mock()
power_levels.get_user_level.return_value = 25
client.get_state_event = AsyncMock(return_value=power_levels)
result = await user_permitted(client, "@user:example.com", "!parent:example.com", 50, None, None)
assert result == False
@pytest.mark.asyncio
async def test_user_permitted_error(self):
"""Test user permission check with error."""
client = Mock()
with patch('community.helpers.room_utils.user_has_unlimited_power') as mock_unlimited:
mock_unlimited.side_effect = Exception("Network error")
logger = Mock()
result = await user_permitted(client, "@user:example.com", "!parent:example.com", 50, None, logger)
assert result == False
logger.error.assert_called()