Refactor admin handlers to improve access control and state management. Added checks for admin rights in ban functions and streamlined router inclusion order in main bot file. Updated keyboard layouts for better user experience and removed unused state definitions.
This commit is contained in:
@@ -1,11 +1,33 @@
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock, patch, AsyncMock
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from helper_bot.utils.helper_func import (
|
||||
get_first_name,
|
||||
get_text_message,
|
||||
check_username_and_full_name
|
||||
check_username_and_full_name,
|
||||
safe_html_escape,
|
||||
download_file,
|
||||
prepare_media_group_from_middlewares,
|
||||
add_in_db_media_mediagroup,
|
||||
add_in_db_media,
|
||||
send_media_group_message_to_private_chat,
|
||||
send_media_group_to_channel,
|
||||
send_text_message,
|
||||
send_photo_message,
|
||||
send_video_message,
|
||||
send_video_note_message,
|
||||
send_audio_message,
|
||||
send_voice_message,
|
||||
check_access,
|
||||
add_days_to_date,
|
||||
get_banned_users_list,
|
||||
get_banned_users_buttons,
|
||||
delete_user_blacklist,
|
||||
update_user_info,
|
||||
check_user_emoji,
|
||||
get_random_emoji
|
||||
)
|
||||
from helper_bot.utils.messages import get_message
|
||||
from helper_bot.utils.base_dependency_factory import BaseDependencyFactory, get_global_instance
|
||||
@@ -83,6 +105,40 @@ class TestHelperFunctions:
|
||||
assert result is True
|
||||
|
||||
|
||||
class TestSafeHtmlEscape:
|
||||
"""Тесты для функции безопасного экранирования HTML"""
|
||||
|
||||
def test_safe_html_escape_normal_text(self):
|
||||
"""Тест экранирования обычного текста"""
|
||||
result = safe_html_escape("Hello World")
|
||||
assert result == "Hello World"
|
||||
|
||||
def test_safe_html_escape_html_tags(self):
|
||||
"""Тест экранирования HTML тегов"""
|
||||
result = safe_html_escape("<script>alert('xss')</script>")
|
||||
assert result == "<script>alert('xss')</script>"
|
||||
|
||||
def test_safe_html_escape_special_chars(self):
|
||||
"""Тест экранирования специальных символов"""
|
||||
result = safe_html_escape("& < > \" '")
|
||||
assert result == "& < > " '"
|
||||
|
||||
def test_safe_html_escape_none_input(self):
|
||||
"""Тест экранирования None значения"""
|
||||
result = safe_html_escape(None)
|
||||
assert result == ""
|
||||
|
||||
def test_safe_html_escape_empty_string(self):
|
||||
"""Тест экранирования пустой строки"""
|
||||
result = safe_html_escape("")
|
||||
assert result == ""
|
||||
|
||||
def test_safe_html_escape_non_string_input(self):
|
||||
"""Тест экранирования нестрокового ввода"""
|
||||
result = safe_html_escape(123)
|
||||
assert result == "123"
|
||||
|
||||
|
||||
class TestMessages:
|
||||
"""Тесты для системы сообщений"""
|
||||
|
||||
@@ -204,5 +260,422 @@ class TestConfigurationHandling:
|
||||
pass
|
||||
|
||||
|
||||
class TestDownloadFile:
|
||||
"""Тесты для функции скачивания файлов"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_success(self):
|
||||
"""Тест успешного скачивания файла"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
|
||||
# Мокаем get_file
|
||||
mock_file = Mock()
|
||||
mock_file.file_path = "photos/file_123.jpg"
|
||||
mock_message.bot.get_file.return_value = mock_file
|
||||
|
||||
# Мокаем download_file
|
||||
mock_message.bot.download_file = AsyncMock()
|
||||
|
||||
# Мокаем os.makedirs
|
||||
with patch('os.makedirs') as mock_makedirs:
|
||||
with patch('os.path.join', return_value="files/photos/file_123.jpg"):
|
||||
result = await download_file(mock_message, "file_id_123")
|
||||
|
||||
assert result == "files/photos/file_123.jpg"
|
||||
mock_makedirs.assert_called()
|
||||
mock_message.bot.get_file.assert_called_once_with("file_id_123")
|
||||
mock_message.bot.download_file.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_exception(self):
|
||||
"""Тест обработки ошибки при скачивании"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
mock_message.bot.get_file.side_effect = Exception("Network error")
|
||||
|
||||
with patch('os.makedirs'):
|
||||
with patch('helper_bot.utils.helper_func.logger') as mock_logger:
|
||||
result = await download_file(mock_message, "file_id_123")
|
||||
|
||||
assert result is None
|
||||
mock_logger.error.assert_called_once()
|
||||
|
||||
|
||||
class TestPrepareMediaGroup:
|
||||
"""Тесты для подготовки медиагрупп"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_media_group_photos(self):
|
||||
"""Тест подготовки медиагруппы с фотографиями"""
|
||||
album = []
|
||||
for i in range(3):
|
||||
message = Mock()
|
||||
message.photo = [Mock()]
|
||||
message.photo[-1].file_id = f"photo_{i}"
|
||||
album.append(message)
|
||||
|
||||
result = await prepare_media_group_from_middlewares(album, "Тестовая подпись")
|
||||
|
||||
assert len(result) == 3
|
||||
assert result[0].media == "photo_0"
|
||||
assert result[1].media == "photo_1"
|
||||
assert result[2].media == "photo_2"
|
||||
assert result[2].caption == "Тестовая подпись"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_media_group_mixed_types(self):
|
||||
"""Тест подготовки медиагруппы с разными типами медиа"""
|
||||
album = []
|
||||
|
||||
# Фото
|
||||
photo_message = Mock()
|
||||
photo_message.photo = [Mock()]
|
||||
photo_message.photo[-1].file_id = "photo_1"
|
||||
album.append(photo_message)
|
||||
|
||||
# Видео
|
||||
video_message = Mock()
|
||||
video_message.photo = None
|
||||
video_message.video = Mock()
|
||||
video_message.video.file_id = "video_1"
|
||||
album.append(video_message)
|
||||
|
||||
# Аудио
|
||||
audio_message = Mock()
|
||||
audio_message.photo = None
|
||||
audio_message.video = None
|
||||
audio_message.audio = Mock()
|
||||
audio_message.audio.file_id = "audio_1"
|
||||
album.append(audio_message)
|
||||
|
||||
result = await prepare_media_group_from_middlewares(album, "Смешанная группа")
|
||||
|
||||
assert len(result) == 3
|
||||
assert result[0].media == "photo_1"
|
||||
assert result[1].media == "video_1"
|
||||
assert result[2].media == "audio_1"
|
||||
assert result[2].caption == "Смешанная группа"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_media_group_empty_album(self):
|
||||
"""Тест подготовки пустой медиагруппы"""
|
||||
album = []
|
||||
result = await prepare_media_group_from_middlewares(album, "Пустая группа")
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_media_group_unsupported_type(self):
|
||||
"""Тест подготовки медиагруппы с неподдерживаемым типом"""
|
||||
album = []
|
||||
message = Mock()
|
||||
message.photo = None
|
||||
message.video = None
|
||||
message.audio = None
|
||||
album.append(message)
|
||||
|
||||
result = await prepare_media_group_from_middlewares(album, "Тест")
|
||||
assert result == []
|
||||
|
||||
|
||||
class TestMediaDatabaseOperations:
|
||||
"""Тесты для операций с медиа в базе данных"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_mediagroup(self):
|
||||
"""Тест добавления медиагруппы в базу данных"""
|
||||
sent_message = []
|
||||
for i in range(2):
|
||||
message = Mock()
|
||||
message.message_id = i + 1
|
||||
message.photo = [Mock()]
|
||||
message.photo[-1].file_id = f"photo_{i}"
|
||||
sent_message.append(message)
|
||||
|
||||
mock_db = Mock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value=f"files/photo_{i}.jpg"):
|
||||
await add_in_db_media_mediagroup(sent_message, mock_db)
|
||||
|
||||
assert mock_db.add_post_content_in_db.call_count == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_photo(self):
|
||||
"""Тест добавления фото в базу данных"""
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = [Mock()]
|
||||
mock_message.photo[-1].file_id = "photo_123"
|
||||
|
||||
mock_db = Mock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value="files/photo_123.jpg"):
|
||||
await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
mock_db.add_post_content_in_db.assert_called_once_with(
|
||||
123, 123, "files/photo_123.jpg", 'photo'
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_video(self):
|
||||
"""Тест добавления видео в базу данных"""
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = None # У видео нет фото
|
||||
mock_message.video = Mock()
|
||||
mock_message.video.file_id = "video_123"
|
||||
|
||||
mock_db = Mock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value="files/video_123.mp4"):
|
||||
await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
mock_db.add_post_content_in_db.assert_called_once_with(
|
||||
123, 123, "files/video_123.mp4", 'video'
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_in_db_media_voice(self):
|
||||
"""Тест добавления голосового сообщения в базу данных"""
|
||||
mock_message = Mock()
|
||||
mock_message.message_id = 123
|
||||
mock_message.photo = None # У голосового сообщения нет фото
|
||||
mock_message.video = None # У голосового сообщения нет видео
|
||||
mock_message.voice = Mock()
|
||||
mock_message.voice.file_id = "voice_123"
|
||||
|
||||
mock_db = Mock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.download_file', return_value="files/voice_123.ogg"):
|
||||
await add_in_db_media(mock_message, mock_db)
|
||||
|
||||
mock_db.add_post_content_in_db.assert_called_once_with(
|
||||
123, 123, "files/voice_123.ogg", 'voice'
|
||||
)
|
||||
|
||||
|
||||
class TestSendMessageFunctions:
|
||||
"""Тесты для функций отправки сообщений"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_text_message_without_markup(self):
|
||||
"""Тест отправки текстового сообщения без разметки"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
mock_message.bot.send_message = AsyncMock()
|
||||
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 456
|
||||
mock_message.bot.send_message.return_value = mock_sent_message
|
||||
|
||||
result = await send_text_message(123, mock_message, "Тестовое сообщение")
|
||||
|
||||
assert result == 456
|
||||
mock_message.bot.send_message.assert_called_once_with(
|
||||
chat_id=123,
|
||||
text="Тестовое сообщение"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_text_message_with_markup(self):
|
||||
"""Тест отправки текстового сообщения с разметкой"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
mock_message.bot.send_message = AsyncMock()
|
||||
|
||||
mock_markup = Mock()
|
||||
mock_sent_message = Mock()
|
||||
mock_sent_message.message_id = 456
|
||||
mock_message.bot.send_message.return_value = mock_sent_message
|
||||
|
||||
result = await send_text_message(123, mock_message, "Тестовое сообщение", mock_markup)
|
||||
|
||||
assert result == 456
|
||||
mock_message.bot.send_message.assert_called_once_with(
|
||||
chat_id=123,
|
||||
text="Тестовое сообщение",
|
||||
reply_markup=mock_markup
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_photo_message(self):
|
||||
"""Тест отправки фото"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
mock_message.bot.send_photo = AsyncMock()
|
||||
|
||||
mock_sent_message = Mock()
|
||||
mock_message.bot.send_photo.return_value = mock_sent_message
|
||||
|
||||
result = await send_photo_message(123, mock_message, "photo.jpg", "Подпись к фото")
|
||||
|
||||
assert result == mock_sent_message
|
||||
mock_message.bot.send_photo.assert_called_once_with(
|
||||
chat_id=123,
|
||||
caption="Подпись к фото",
|
||||
photo="photo.jpg"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_video_message(self):
|
||||
"""Тест отправки видео"""
|
||||
mock_message = Mock()
|
||||
mock_message.bot = AsyncMock()
|
||||
mock_message.bot.send_video = AsyncMock()
|
||||
|
||||
mock_sent_message = Mock()
|
||||
mock_message.bot.send_video.return_value = mock_sent_message
|
||||
|
||||
result = await send_video_message(123, mock_message, "video.mp4", "Подпись к видео")
|
||||
|
||||
assert result == mock_sent_message
|
||||
mock_message.bot.send_video.assert_called_once_with(
|
||||
chat_id=123,
|
||||
caption="Подпись к видео",
|
||||
video="video.mp4"
|
||||
)
|
||||
|
||||
|
||||
class TestUtilityFunctions:
|
||||
"""Тесты для утилитарных функций"""
|
||||
|
||||
def test_check_access(self):
|
||||
"""Тест проверки доступа"""
|
||||
mock_db = Mock()
|
||||
mock_db.is_admin.return_value = True
|
||||
|
||||
result = check_access(123, mock_db)
|
||||
assert result is True
|
||||
|
||||
mock_db.is_admin.return_value = False
|
||||
result = check_access(123, mock_db)
|
||||
assert result is False
|
||||
|
||||
def test_add_days_to_date(self):
|
||||
"""Тест добавления дней к дате"""
|
||||
with patch('helper_bot.utils.helper_func.datetime') as mock_datetime:
|
||||
from datetime import timedelta
|
||||
mock_now = datetime(2024, 1, 1)
|
||||
mock_datetime.now.return_value = mock_now
|
||||
mock_datetime.timedelta = timedelta
|
||||
|
||||
result = add_days_to_date(5)
|
||||
expected_date = (mock_now + timedelta(days=5)).strftime("%d-%m-%Y")
|
||||
assert result == expected_date
|
||||
|
||||
def test_get_banned_users_list(self):
|
||||
"""Тест получения списка заблокированных пользователей"""
|
||||
mock_db = Mock()
|
||||
mock_db.get_banned_users_from_db_with_limits.return_value = [
|
||||
("User1", 123, "Spam", "01-01-2025"),
|
||||
("User2", 456, "Violation", "02-01-2025")
|
||||
]
|
||||
|
||||
result = get_banned_users_list(0, mock_db)
|
||||
|
||||
assert "Список заблокированных пользователей:" in result
|
||||
assert "User1" in result
|
||||
assert "User2" in result
|
||||
assert "Spam" in result
|
||||
assert "Violation" in result
|
||||
|
||||
def test_get_banned_users_buttons(self):
|
||||
"""Тест получения кнопок заблокированных пользователей"""
|
||||
mock_db = Mock()
|
||||
mock_db.get_banned_users_from_db.return_value = [
|
||||
("User1", 123),
|
||||
("User2", 456)
|
||||
]
|
||||
|
||||
result = get_banned_users_buttons(mock_db)
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0] == ("User1", 123)
|
||||
assert result[1] == ("User2", 456)
|
||||
|
||||
def test_delete_user_blacklist(self):
|
||||
"""Тест удаления пользователя из черного списка"""
|
||||
mock_db = Mock()
|
||||
mock_db.delete_user_blacklist.return_value = True
|
||||
|
||||
result = delete_user_blacklist(123, mock_db)
|
||||
assert result is True
|
||||
|
||||
mock_db.delete_user_blacklist.assert_called_once_with(user_id=123)
|
||||
|
||||
|
||||
class TestUserManagement:
|
||||
"""Тесты для управления пользователями"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user_info_new_user(self):
|
||||
"""Тест обновления информации о новом пользователе"""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
mock_message.from_user.full_name = "Test User"
|
||||
mock_message.from_user.username = "testuser"
|
||||
mock_message.from_user.is_bot = False
|
||||
mock_message.from_user.language_code = "ru"
|
||||
mock_message.answer = AsyncMock()
|
||||
mock_message.bot.send_message = AsyncMock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.get_first_name', return_value="Test"):
|
||||
with patch('helper_bot.utils.helper_func.get_random_emoji', return_value="😀"):
|
||||
with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db:
|
||||
mock_bot_db.user_exists.return_value = False
|
||||
mock_bot_db.add_new_user_in_db = Mock()
|
||||
mock_bot_db.update_date_for_user = Mock()
|
||||
|
||||
await update_user_info("test", mock_message)
|
||||
|
||||
mock_bot_db.add_new_user_in_db.assert_called_once()
|
||||
mock_bot_db.update_date_for_user.assert_called_once()
|
||||
|
||||
def test_check_user_emoji_existing(self):
|
||||
"""Тест проверки эмодзи пользователя (существующий)"""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
|
||||
with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db:
|
||||
mock_bot_db.check_emoji_for_user.return_value = "😀"
|
||||
|
||||
result = check_user_emoji(mock_message)
|
||||
assert result == "😀"
|
||||
|
||||
def test_check_user_emoji_new(self):
|
||||
"""Тест проверки эмодзи пользователя (новый)"""
|
||||
mock_message = Mock()
|
||||
mock_message.from_user.id = 123
|
||||
|
||||
with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db:
|
||||
mock_bot_db.check_emoji_for_user.return_value = None
|
||||
mock_bot_db.update_emoji_for_user = Mock()
|
||||
|
||||
with patch('helper_bot.utils.helper_func.get_random_emoji', return_value="😀"):
|
||||
result = check_user_emoji(mock_message)
|
||||
assert result == "😀"
|
||||
mock_bot_db.update_emoji_for_user.assert_called_once_with(user_id=123, emoji="😀")
|
||||
|
||||
def test_get_random_emoji_success(self):
|
||||
"""Тест получения случайного эмодзи (успех)"""
|
||||
with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db:
|
||||
mock_bot_db.check_emoji.return_value = False
|
||||
|
||||
with patch('helper_bot.utils.helper_func.random.choice', return_value="😀"):
|
||||
result = get_random_emoji()
|
||||
assert result == "😀"
|
||||
|
||||
def test_get_random_emoji_fallback(self):
|
||||
"""Тест получения случайного эмодзи (fallback)"""
|
||||
with patch('helper_bot.utils.helper_func.BotDB') as mock_bot_db:
|
||||
mock_bot_db.check_emoji.return_value = True # Все эмодзи заняты
|
||||
|
||||
with patch('helper_bot.utils.helper_func.random.choice', return_value="😀"):
|
||||
with patch('helper_bot.utils.helper_func.logger') as mock_logger:
|
||||
result = get_random_emoji()
|
||||
assert result == "Эмоджи не определен"
|
||||
mock_logger.error.assert_called_once()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
|
||||
Reference in New Issue
Block a user