import time from datetime import datetime from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from helper_bot.handlers.callback.callback_handlers import ( delete_voice_message, save_voice_message) from helper_bot.handlers.voice.constants import CALLBACK_DELETE, CALLBACK_SAVE @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__])