Enhance bot functionality with new features and improvements

- Added a new `/status` endpoint in `server_prometheus.py` to provide process status information, including uptime and resource usage metrics.
- Implemented a PID manager in `run_helper.py` to track the bot's process, improving monitoring capabilities.
- Introduced a method to delete audio moderation records in `audio_repository.py`, enhancing database management.
- Updated voice message handling in callback handlers to ensure proper deletion of audio moderation records.
- Improved error handling and logging in various services, ensuring better tracking of media processing and file downloads.
- Refactored media handling functions to streamline operations and improve code readability.
- Enhanced metrics tracking for file downloads and media processing, providing better insights into bot performance.
This commit is contained in:
2025-09-04 00:46:45 +03:00
parent ae7bd476bb
commit fc0517c011
17 changed files with 1421 additions and 84 deletions

View File

@@ -0,0 +1,266 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from helper_bot.handlers.voice.services import AudioFileService
from helper_bot.handlers.voice.exceptions import FileOperationError, DatabaseError
@pytest.fixture
def mock_bot_db():
"""Мок для базы данных"""
mock_db = Mock()
mock_db.get_user_audio_records_count = AsyncMock(return_value=0)
mock_db.get_path_for_audio_record = AsyncMock(return_value=None)
mock_db.add_audio_record_simple = AsyncMock()
return mock_db
@pytest.fixture
def audio_service(mock_bot_db):
"""Экземпляр AudioFileService для тестов"""
return AudioFileService(mock_bot_db)
@pytest.fixture
def sample_datetime():
"""Тестовая дата"""
return datetime(2025, 1, 15, 14, 30, 0)
@pytest.fixture
def mock_bot():
"""Мок для бота"""
bot = Mock()
bot.get_file = AsyncMock()
bot.download_file = AsyncMock()
return bot
@pytest.fixture
def mock_message():
"""Мок для сообщения"""
message = Mock()
message.voice = Mock()
message.voice.file_id = "test_file_id"
return message
@pytest.fixture
def mock_file_info():
"""Мок для информации о файле"""
file_info = Mock()
file_info.file_path = "voice/test_file_id.ogg"
return file_info
class TestGenerateFileName:
"""Тесты для метода generate_file_name"""
@pytest.mark.asyncio
async def test_generate_file_name_first_record(self, audio_service, mock_bot_db):
"""Тест генерации имени файла для первой записи пользователя"""
mock_bot_db.get_user_audio_records_count.return_value = 0
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_1"
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
@pytest.mark.asyncio
async def test_generate_file_name_existing_records(self, audio_service, mock_bot_db):
"""Тест генерации имени файла для существующих записей"""
mock_bot_db.get_user_audio_records_count.return_value = 3
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_3"
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_4"
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
@pytest.mark.asyncio
async def test_generate_file_name_no_last_record(self, audio_service, mock_bot_db):
"""Тест генерации имени файла когда нет последней записи"""
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = None
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_3"
@pytest.mark.asyncio
async def test_generate_file_name_invalid_last_record_format(self, audio_service, mock_bot_db):
"""Тест генерации имени файла с некорректным форматом последней записи"""
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = "invalid_format"
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_3"
@pytest.mark.asyncio
async def test_generate_file_name_exception_handling(self, audio_service, mock_bot_db):
"""Тест обработки исключений при генерации имени файла"""
mock_bot_db.get_user_audio_records_count.side_effect = Exception("Database error")
with pytest.raises(FileOperationError) as exc_info:
await audio_service.generate_file_name(12345)
assert "Не удалось сгенерировать имя файла" in str(exc_info.value)
class TestSaveAudioFile:
"""Тесты для метода save_audio_file"""
@pytest.mark.asyncio
async def test_save_audio_file_success(self, audio_service, mock_bot_db, sample_datetime):
"""Тест успешного сохранения аудио файла"""
file_name = "test_audio.ogg"
user_id = 12345
file_id = "test_file_id"
await audio_service.save_audio_file(file_name, user_id, sample_datetime, file_id)
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, user_id, sample_datetime)
@pytest.mark.asyncio
async def test_save_audio_file_with_string_date(self, audio_service, mock_bot_db):
"""Тест сохранения аудио файла со строковой датой"""
file_name = "test_audio.ogg"
user_id = 12345
date_string = "2025-01-15 14:30:00"
file_id = "test_file_id"
await audio_service.save_audio_file(file_name, user_id, date_string, file_id)
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, user_id, date_string)
@pytest.mark.asyncio
async def test_save_audio_file_exception_handling(self, audio_service, mock_bot_db, sample_datetime):
"""Тест обработки исключений при сохранении аудио файла"""
mock_bot_db.add_audio_record_simple.side_effect = Exception("Database error")
with pytest.raises(DatabaseError) as exc_info:
await audio_service.save_audio_file("test.ogg", 12345, sample_datetime, "file_id")
assert "Не удалось сохранить аудио файл в БД" in str(exc_info.value)
class TestDownloadAndSaveAudio:
"""Тесты для метода download_and_save_audio"""
@pytest.mark.asyncio
async def test_download_and_save_audio_success(self, audio_service, mock_bot, mock_message, mock_file_info):
"""Тест успешного скачивания и сохранения аудио"""
mock_bot.get_file.return_value = mock_file_info
# Мокаем скачанный файл
mock_downloaded_file = Mock()
mock_downloaded_file.tell.return_value = 0
mock_downloaded_file.seek = Mock()
mock_downloaded_file.read.return_value = b"audio_data"
mock_bot.download_file.return_value = mock_downloaded_file
with patch('builtins.open', mock_open()) as mock_file:
with patch('os.makedirs'):
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
mock_bot.get_file.assert_called_once_with(file_id="test_file_id")
mock_bot.download_file.assert_called_once_with(file_path="voice/test_file_id.ogg")
mock_file.assert_called_once()
@pytest.mark.asyncio
async def test_download_and_save_audio_no_message(self, audio_service, mock_bot):
"""Тест скачивания когда сообщение отсутствует"""
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(mock_bot, None, "test_audio")
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_no_voice(self, audio_service, mock_bot):
"""Тест скачивания когда у сообщения нет voice атрибута"""
message = Mock()
message.voice = None
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(mock_bot, message, "test_audio")
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_download_failed(self, audio_service, mock_bot, mock_message, mock_file_info):
"""Тест скачивания когда загрузка не удалась"""
mock_bot.get_file.return_value = mock_file_info
mock_bot.download_file.return_value = None
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
assert "Не удалось скачать файл" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_exception_handling(self, audio_service, mock_bot, mock_message):
"""Тест обработки исключений при скачивании"""
mock_bot.get_file.side_effect = Exception("Network error")
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(mock_bot, mock_message, "test_audio")
assert "Не удалось скачать и сохранить аудио" in str(exc_info.value)
def mock_open():
"""Мок для функции open"""
from unittest.mock import mock_open as _mock_open
return _mock_open()
class TestAudioFileServiceIntegration:
"""Интеграционные тесты для AudioFileService"""
@pytest.mark.asyncio
async def test_full_audio_processing_workflow(self, mock_bot_db):
"""Тест полного рабочего процесса обработки аудио"""
service = AudioFileService(mock_bot_db)
# Настраиваем моки
mock_bot_db.get_user_audio_records_count.return_value = 1
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_1"
mock_bot_db.add_audio_record_simple = AsyncMock()
# Тестируем генерацию имени файла
file_name = await service.generate_file_name(12345)
assert file_name == "message_from_12345_number_2"
# Тестируем сохранение в БД
test_date = datetime.now()
await service.save_audio_file(file_name, 12345, test_date, "test_file_id")
# Проверяем вызовы
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
mock_bot_db.add_audio_record_simple.assert_called_once_with(file_name, 12345, test_date)
@pytest.mark.asyncio
async def test_file_name_generation_sequence(self, mock_bot_db):
"""Тест последовательности генерации имен файлов"""
service = AudioFileService(mock_bot_db)
# Первая запись
mock_bot_db.get_user_audio_records_count.return_value = 0
file_name_1 = await service.generate_file_name(12345)
assert file_name_1 == "message_from_12345_number_1"
# Вторая запись
mock_bot_db.get_user_audio_records_count.return_value = 1
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_1"
file_name_2 = await service.generate_file_name(12345)
assert file_name_2 == "message_from_12345_number_2"
# Третья запись
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = "message_from_12345_number_2"
file_name_3 = await service.generate_file_name(12345)
assert file_name_3 == "message_from_12345_number_3"
if __name__ == '__main__':
pytest.main([__file__])

View File

@@ -331,6 +331,20 @@ class TestAudioRepository:
assert result is None
@pytest.mark.asyncio
async def test_delete_audio_moderate_record(self, audio_repository):
"""Тест удаления записи из таблицы audio_moderate"""
message_id = 12345
await audio_repository.delete_audio_moderate_record(message_id)
audio_repository._execute_query.assert_called_once_with(
"DELETE FROM audio_moderate WHERE message_id = ?", (message_id,)
)
audio_repository.logger.info.assert_called_once_with(
f"Удалена запись из audio_moderate для message_id {message_id}"
)
@pytest.mark.asyncio
async def test_add_audio_record_logging(self, audio_repository, sample_audio_message):
"""Тест логирования при добавлении аудио записи"""

View File

@@ -0,0 +1,297 @@
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from datetime import datetime
import time
from helper_bot.handlers.callback.callback_handlers import (
save_voice_message,
delete_voice_message
)
from helper_bot.handlers.voice.constants import CALLBACK_SAVE, CALLBACK_DELETE
@pytest.fixture
def mock_call():
"""Мок для CallbackQuery"""
call = Mock()
call.message = Mock()
call.message.message_id = 12345
call.message.voice = Mock()
call.message.voice.file_id = "test_file_id_123"
call.bot = Mock()
call.bot.delete_message = AsyncMock()
call.answer = AsyncMock()
return call
@pytest.fixture
def mock_bot_db():
"""Мок для базы данных"""
mock_db = Mock()
mock_db.get_user_id_by_message_id_for_voice_bot = AsyncMock(return_value=67890)
mock_db.delete_audio_moderate_record = AsyncMock()
return mock_db
@pytest.fixture
def mock_settings():
"""Мок для настроек"""
return {
'Telegram': {
'group_for_posts': 'test_group_id'
}
}
@pytest.fixture
def mock_audio_service():
"""Мок для AudioFileService"""
mock_service = Mock()
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
mock_service.save_audio_file = AsyncMock()
mock_service.download_and_save_audio = AsyncMock()
return mock_service
class TestSaveVoiceMessage:
"""Тесты для функции save_voice_message"""
@pytest.mark.asyncio
async def test_save_voice_message_success(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
"""Тест успешного сохранения голосового сообщения"""
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service_class.return_value = mock_audio_service
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что все методы вызваны
mock_bot_db.get_user_id_by_message_id_for_voice_bot.assert_called_once_with(12345)
mock_audio_service.generate_file_name.assert_called_once_with(67890)
mock_audio_service.save_audio_file.assert_called_once()
mock_audio_service.download_and_save_audio.assert_called_once_with(
mock_call.bot, mock_call.message, "message_from_67890_number_1"
)
# Проверяем удаление сообщения из чата
mock_call.bot.delete_message.assert_called_once_with(
chat_id='test_group_id',
message_id=12345
)
# Проверяем удаление записи из audio_moderate
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(12345)
# Проверяем ответ пользователю
mock_call.answer.assert_called_once_with(text='Сохранено!', cache_time=3)
@pytest.mark.asyncio
async def test_save_voice_message_with_correct_parameters(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
"""Тест сохранения с правильными параметрами"""
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service_class.return_value = mock_audio_service
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем параметры save_audio_file
save_call_args = mock_audio_service.save_audio_file.call_args
assert save_call_args[0][0] == "message_from_67890_number_1" # file_name
assert save_call_args[0][1] == 67890 # user_id
assert isinstance(save_call_args[0][2], datetime) # date_added
assert save_call_args[0][3] == "test_file_id_123" # file_id
@pytest.mark.asyncio
async def test_save_voice_message_exception_handling(self, mock_call, mock_bot_db, mock_settings):
"""Тест обработки исключений при сохранении"""
mock_bot_db.get_user_id_by_message_id_for_voice_bot.side_effect = Exception("Database error")
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что при ошибке отправляется соответствующий ответ
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
@pytest.mark.asyncio
async def test_save_voice_message_audio_service_exception(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
"""Тест обработки исключений в AudioFileService"""
mock_audio_service.save_audio_file.side_effect = Exception("Save error")
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service_class.return_value = mock_audio_service
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что при ошибке отправляется соответствующий ответ
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
@pytest.mark.asyncio
async def test_save_voice_message_download_exception(self, mock_call, mock_bot_db, mock_settings, mock_audio_service):
"""Тест обработки исключений при скачивании файла"""
mock_audio_service.download_and_save_audio.side_effect = Exception("Download error")
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service_class.return_value = mock_audio_service
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что при ошибке отправляется соответствующий ответ
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
class TestDeleteVoiceMessage:
"""Тесты для функции delete_voice_message"""
@pytest.mark.asyncio
async def test_delete_voice_message_success(self, mock_call, mock_bot_db, mock_settings):
"""Тест успешного удаления голосового сообщения"""
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем удаление сообщения из чата
mock_call.bot.delete_message.assert_called_once_with(
chat_id='test_group_id',
message_id=12345
)
# Проверяем удаление записи из audio_moderate
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(12345)
# Проверяем ответ пользователю
mock_call.answer.assert_called_once_with(text='Удалено!', cache_time=3)
@pytest.mark.asyncio
async def test_delete_voice_message_exception_handling(self, mock_call, mock_bot_db, mock_settings):
"""Тест обработки исключений при удалении"""
mock_call.bot.delete_message.side_effect = Exception("Delete error")
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что при ошибке отправляется соответствующий ответ
mock_call.answer.assert_called_once_with(text='Ошибка при удалении!', cache_time=3)
@pytest.mark.asyncio
async def test_delete_voice_message_database_exception(self, mock_call, mock_bot_db, mock_settings):
"""Тест обработки исключений в базе данных при удалении"""
mock_bot_db.delete_audio_moderate_record.side_effect = Exception("Database error")
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что при ошибке отправляется соответствующий ответ
mock_call.answer.assert_called_once_with(text='Ошибка при удалении!', cache_time=3)
class TestCallbackHandlersIntegration:
"""Интеграционные тесты для callback handlers"""
@pytest.mark.asyncio
async def test_save_voice_message_full_workflow(self, mock_call, mock_bot_db, mock_settings):
"""Тест полного рабочего процесса сохранения"""
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service = Mock()
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
mock_service.save_audio_file = AsyncMock()
mock_service.download_and_save_audio = AsyncMock()
mock_service_class.return_value = mock_service
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем последовательность вызовов
assert mock_bot_db.get_user_id_by_message_id_for_voice_bot.called
assert mock_service.generate_file_name.called
assert mock_service.save_audio_file.called
assert mock_service.download_and_save_audio.called
assert mock_call.bot.delete_message.called
assert mock_bot_db.delete_audio_moderate_record.called
assert mock_call.answer.called
@pytest.mark.asyncio
async def test_delete_voice_message_full_workflow(self, mock_call, mock_bot_db, mock_settings):
"""Тест полного рабочего процесса удаления"""
await delete_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем последовательность вызовов
assert mock_call.bot.delete_message.called
assert mock_bot_db.delete_audio_moderate_record.called
assert mock_call.answer.called
@pytest.mark.asyncio
async def test_audio_moderate_cleanup_consistency(self, mock_call, mock_bot_db, mock_settings):
"""Тест консистентности очистки audio_moderate"""
# Тестируем, что в обоих случаях (сохранение и удаление)
# вызывается delete_audio_moderate_record
# Создаем отдельные моки для каждого теста
mock_bot_db_save = Mock()
mock_bot_db_save.get_user_id_by_message_id_for_voice_bot = AsyncMock(return_value=67890)
mock_bot_db_save.delete_audio_moderate_record = AsyncMock()
mock_bot_db_delete = Mock()
mock_bot_db_delete.delete_audio_moderate_record = AsyncMock()
# Тест для сохранения
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService') as mock_service_class:
mock_service = Mock()
mock_service.generate_file_name = AsyncMock(return_value="message_from_67890_number_1")
mock_service.save_audio_file = AsyncMock()
mock_service.download_and_save_audio = AsyncMock()
mock_service_class.return_value = mock_service
await save_voice_message(mock_call, bot_db=mock_bot_db_save, settings=mock_settings)
save_calls = mock_bot_db_save.delete_audio_moderate_record.call_count
# Тест для удаления
await delete_voice_message(mock_call, bot_db=mock_bot_db_delete, settings=mock_settings)
delete_calls = mock_bot_db_delete.delete_audio_moderate_record.call_count
# Проверяем, что в обоих случаях вызывается очистка
assert save_calls == 1
assert delete_calls == 1
class TestCallbackHandlersEdgeCases:
"""Тесты граничных случаев для callback handlers"""
@pytest.mark.asyncio
async def test_save_voice_message_no_voice_attribute(self, mock_bot_db, mock_settings):
"""Тест сохранения когда у сообщения нет voice атрибута"""
call = Mock()
call.message = Mock()
call.message.message_id = 12345
call.message.voice = None # Нет голосового сообщения
call.bot = Mock()
call.bot.delete_message = AsyncMock()
call.answer = AsyncMock()
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService'):
await save_voice_message(call, bot_db=mock_bot_db, settings=mock_settings)
# Должна быть ошибка
call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
@pytest.mark.asyncio
async def test_save_voice_message_user_not_found(self, mock_call, mock_bot_db, mock_settings):
"""Тест сохранения когда пользователь не найден"""
mock_bot_db.get_user_id_by_message_id_for_voice_bot.return_value = None
with patch('helper_bot.handlers.callback.callback_handlers.AudioFileService'):
await save_voice_message(mock_call, bot_db=mock_bot_db, settings=mock_settings)
# Должна быть ошибка
mock_call.answer.assert_called_once_with(text='Ошибка при сохранении!', cache_time=3)
@pytest.mark.asyncio
async def test_delete_voice_message_with_different_message_id(self, mock_bot_db, mock_settings):
"""Тест удаления с другим message_id"""
call = Mock()
call.message = Mock()
call.message.message_id = 99999 # Другой ID
call.bot = Mock()
call.bot.delete_message = AsyncMock()
call.answer = AsyncMock()
await delete_voice_message(call, bot_db=mock_bot_db, settings=mock_settings)
# Проверяем, что используется правильный message_id
call.bot.delete_message.assert_called_once_with(
chat_id='test_group_id',
message_id=99999
)
mock_bot_db.delete_audio_moderate_record.assert_called_once_with(99999)
if __name__ == '__main__':
pytest.main([__file__])

View File

@@ -0,0 +1,301 @@
"""
Тесты для улучшенных методов обработки медиа
"""
import pytest
import os
import tempfile
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from aiogram import types
from helper_bot.utils.helper_func import (
download_file,
add_in_db_media,
add_in_db_media_mediagroup,
send_media_group_message_to_private_chat
)
class TestDownloadFile:
"""Тесты для функции download_file"""
@pytest.mark.asyncio
async def test_download_file_success_photo(self):
"""Тест успешного скачивания фото"""
# Создаем временную директорию
with tempfile.TemporaryDirectory() as temp_dir:
with patch('helper_bot.utils.helper_func.os.makedirs'), \
patch('helper_bot.utils.helper_func.os.path.exists', return_value=True), \
patch('helper_bot.utils.helper_func.os.path.getsize', return_value=1024), \
patch('helper_bot.utils.helper_func.os.path.basename', return_value='photo.jpg'), \
patch('helper_bot.utils.helper_func.os.path.splitext', return_value=('photo', '.jpg')):
# Мокаем сообщение и бота
mock_message = Mock()
mock_message.bot = Mock()
mock_file = Mock()
mock_file.file_path = 'photos/photo.jpg'
mock_message.bot.get_file = AsyncMock(return_value=mock_file)
mock_message.bot.download_file = AsyncMock()
# Вызываем функцию
result = await download_file(mock_message, 'test_file_id', 'photo')
# Проверяем результат
assert result is not None
assert 'files/photos/test_file_id.jpg' in result
mock_message.bot.get_file.assert_called_once_with('test_file_id')
mock_message.bot.download_file.assert_called_once()
@pytest.mark.asyncio
async def test_download_file_invalid_parameters(self):
"""Тест с неверными параметрами"""
result = await download_file(None, 'test_file_id', 'photo')
assert result is None
mock_message = Mock()
mock_message.bot = None
result = await download_file(mock_message, 'test_file_id', 'photo')
assert result is None
@pytest.mark.asyncio
async def test_download_file_error(self):
"""Тест обработки ошибки при скачивании"""
mock_message = Mock()
mock_message.bot = Mock()
mock_message.bot.get_file = AsyncMock(side_effect=Exception("Network error"))
result = await download_file(mock_message, 'test_file_id', 'photo')
assert result is None
class TestAddInDbMedia:
"""Тесты для функции add_in_db_media"""
@pytest.mark.asyncio
async def test_add_in_db_media_success_photo(self):
"""Тест успешного добавления фото в БД"""
# Мокаем сообщение
mock_message = Mock()
mock_message.message_id = 123
mock_message.photo = [Mock()]
mock_message.photo[-1].file_id = 'photo_123'
mock_message.video = None
mock_message.voice = None
mock_message.audio = None
mock_message.video_note = None
# Мокаем БД
mock_db = AsyncMock()
mock_db.add_post_content = AsyncMock(return_value=True)
with patch('helper_bot.utils.helper_func.download_file', return_value='files/photos/photo_123.jpg'):
result = await add_in_db_media(mock_message, mock_db)
assert result is True
mock_db.add_post_content.assert_called_once_with(123, 123, 'files/photos/photo_123.jpg', 'photo')
@pytest.mark.asyncio
async def test_add_in_db_media_download_fails(self):
"""Тест когда скачивание файла не удается"""
mock_message = Mock()
mock_message.message_id = 123
mock_message.photo = [Mock()]
mock_message.photo[-1].file_id = 'photo_123'
mock_message.video = None
mock_message.voice = None
mock_message.audio = None
mock_message.video_note = None
mock_db = AsyncMock()
with patch('helper_bot.utils.helper_func.download_file', return_value=None):
result = await add_in_db_media(mock_message, mock_db)
assert result is False
mock_db.add_post_content.assert_not_called()
@pytest.mark.asyncio
async def test_add_in_db_media_db_fails(self):
"""Тест когда добавление в БД не удается"""
mock_message = Mock()
mock_message.message_id = 123
mock_message.photo = [Mock()]
mock_message.photo[-1].file_id = 'photo_123'
mock_message.video = None
mock_message.voice = None
mock_message.audio = None
mock_message.video_note = None
mock_db = AsyncMock()
mock_db.add_post_content = AsyncMock(return_value=False)
with patch('helper_bot.utils.helper_func.download_file', return_value='files/photos/photo_123.jpg'), \
patch('helper_bot.utils.helper_func.os.remove'):
result = await add_in_db_media(mock_message, mock_db)
assert result is False
mock_db.add_post_content.assert_called_once()
@pytest.mark.asyncio
async def test_add_in_db_media_unsupported_content(self):
"""Тест с неподдерживаемым типом контента"""
mock_message = Mock()
mock_message.message_id = 123
mock_message.photo = None
mock_message.video = None
mock_message.voice = None
mock_message.audio = None
mock_message.video_note = None
mock_db = AsyncMock()
result = await add_in_db_media(mock_message, mock_db)
assert result is False
mock_db.add_post_content.assert_not_called()
class TestAddInDbMediaMediagroup:
"""Тесты для функции add_in_db_media_mediagroup"""
@pytest.mark.asyncio
async def test_add_in_db_media_mediagroup_success(self):
"""Тест успешного добавления медиагруппы в БД"""
# Создаем моки сообщений
mock_message1 = Mock()
mock_message1.message_id = 1
mock_message1.photo = [Mock()]
mock_message1.photo[-1].file_id = 'photo_1'
mock_message1.video = None
mock_message1.voice = None
mock_message1.audio = None
mock_message1.video_note = None
mock_message2 = Mock()
mock_message2.message_id = 2
mock_message2.photo = None
mock_message2.video = Mock()
mock_message2.video.file_id = 'video_1'
mock_message2.voice = None
mock_message2.audio = None
mock_message2.video_note = None
sent_messages = [mock_message1, mock_message2]
# Мокаем БД
mock_db = AsyncMock()
mock_db.add_post_content = AsyncMock(return_value=True)
with patch('helper_bot.utils.helper_func.download_file', return_value='files/test.jpg'):
result = await add_in_db_media_mediagroup(sent_messages, mock_db, main_post_id=100)
assert result is True
assert mock_db.add_post_content.call_count == 2
@pytest.mark.asyncio
async def test_add_in_db_media_mediagroup_empty_list(self):
"""Тест с пустым списком сообщений"""
mock_db = AsyncMock()
result = await add_in_db_media_mediagroup([], mock_db)
assert result is False
mock_db.add_post_content.assert_not_called()
@pytest.mark.asyncio
async def test_add_in_db_media_mediagroup_partial_failure(self):
"""Тест когда часть сообщений обрабатывается успешно"""
# Создаем моки сообщений
mock_message1 = Mock()
mock_message1.message_id = 1
mock_message1.photo = [Mock()]
mock_message1.photo[-1].file_id = 'photo_1'
mock_message1.video = None
mock_message1.voice = None
mock_message1.audio = None
mock_message1.video_note = None
mock_message2 = Mock()
mock_message2.message_id = 2
mock_message2.photo = None
mock_message2.video = None
mock_message2.voice = None
mock_message2.audio = None
mock_message2.video_note = None # Неподдерживаемый тип
sent_messages = [mock_message1, mock_message2]
# Мокаем БД
mock_db = AsyncMock()
mock_db.add_post_content = AsyncMock(return_value=True)
with patch('helper_bot.utils.helper_func.download_file', return_value='files/test.jpg'):
result = await add_in_db_media_mediagroup(sent_messages, mock_db)
# Должен вернуть False, так как есть ошибки (второе сообщение не поддерживается)
assert result is False
assert mock_db.add_post_content.call_count == 1
class TestSendMediaGroupMessageToPrivateChat:
"""Тесты для функции send_media_group_message_to_private_chat"""
@pytest.mark.asyncio
async def test_send_media_group_message_success(self):
"""Тест успешной отправки медиагруппы"""
# Мокаем сообщение
mock_message = Mock()
mock_message.from_user.id = 123
mock_message.bot = Mock()
# Мокаем отправленное сообщение
mock_sent_message = Mock()
mock_sent_message.message_id = 456
mock_sent_message.caption = "Test caption"
mock_message.bot.send_media_group = AsyncMock(return_value=[mock_sent_message])
# Мокаем БД
mock_db = AsyncMock()
mock_db.add_post = AsyncMock()
with patch('helper_bot.utils.helper_func.add_in_db_media_mediagroup', return_value=True):
result = await send_media_group_message_to_private_chat(
100, mock_message, [], mock_db, main_post_id=789
)
assert result == 456
mock_message.bot.send_media_group.assert_called_once()
mock_db.add_post.assert_called_once()
@pytest.mark.asyncio
async def test_send_media_group_message_media_processing_fails(self):
"""Тест когда обработка медиа не удается"""
# Мокаем сообщение
mock_message = Mock()
mock_message.from_user.id = 123
mock_message.bot = Mock()
# Мокаем отправленное сообщение
mock_sent_message = Mock()
mock_sent_message.message_id = 456
mock_sent_message.caption = "Test caption"
mock_message.bot.send_media_group = AsyncMock(return_value=[mock_sent_message])
# Мокаем БД
mock_db = AsyncMock()
mock_db.add_post = AsyncMock()
with patch('helper_bot.utils.helper_func.add_in_db_media_mediagroup', return_value=False):
result = await send_media_group_message_to_private_chat(
100, mock_message, [], mock_db, main_post_id=789
)
assert result == 456 # Функция все равно возвращает message_id
mock_message.bot.send_media_group.assert_called_once()
mock_db.add_post.assert_called_once()
if __name__ == "__main__":
pytest.main([__file__])

View File

@@ -288,15 +288,20 @@ class TestDownloadFile:
# Мокаем download_file
mock_message.bot.download_file = AsyncMock()
# Мокаем os.makedirs
# Мокаем 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()
with patch('os.path.exists', return_value=True):
with patch('os.path.getsize', return_value=1024):
with patch('os.path.basename', return_value='file_123.jpg'):
with patch('os.path.splitext', return_value=('file_123', '.jpg')):
with patch('helper_bot.utils.helper_func.metrics') as mock_metrics:
result = await download_file(mock_message, "file_id_123", "photo")
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):