from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from aiogram import types from aiogram.fsm.context import FSMContext from helper_bot.handlers.voice.constants import STATE_STANDUP_WRITE, STATE_START from helper_bot.handlers.voice.voice_handler import VoiceHandlers class TestVoiceHandler: """Тесты для VoiceHandler""" @pytest.fixture def mock_db(self): """Мок для базы данных""" mock_db = Mock() mock_db.settings = { "Telegram": { "group_for_logs": "test_logs_chat", "group_for_posts": "test_posts_chat", "preview_link": True, } } return mock_db @pytest.fixture def mock_settings(self): """Мок для настроек""" return { "Telegram": { "group_for_logs": "test_logs_chat", "group_for_posts": "test_posts_chat", "preview_link": True, } } @pytest.fixture def mock_message(self): """Мок для сообщения""" message = Mock(spec=types.Message) message.from_user = Mock() message.from_user.id = 123 message.from_user.first_name = "Test" message.from_user.full_name = "Test User" message.chat = Mock() message.chat.id = 456 message.answer = AsyncMock() message.forward = AsyncMock() return message @pytest.fixture def mock_state(self): """Мок для состояния FSM""" state = Mock(spec=FSMContext) state.set_state = AsyncMock() return state @pytest.fixture def voice_handler(self, mock_db, mock_settings): """Экземпляр VoiceHandler для тестов""" return VoiceHandlers(mock_db, mock_settings) @pytest.mark.asyncio async def test_voice_bot_button_handler_welcome_received( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """Тест обработчика кнопки когда приветствие уже получено""" from unittest.mock import AsyncMock mock_db.check_voice_bot_welcome_received = AsyncMock(return_value=True) with patch.object(voice_handler, "restart_function") as mock_restart: with patch( "helper_bot.handlers.voice.voice_handler.update_user_info" ) as mock_update_user: mock_update_user.return_value = None await voice_handler.voice_bot_button_handler( mock_message, mock_state, mock_db, mock_settings ) mock_db.check_voice_bot_welcome_received.assert_called_once_with(123) mock_restart.assert_called_once_with( mock_message, mock_state, mock_db, mock_settings ) @pytest.mark.asyncio async def test_voice_bot_button_handler_welcome_not_received( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """Тест обработчика кнопки когда приветствие не получено""" from unittest.mock import AsyncMock mock_db.check_voice_bot_welcome_received = AsyncMock(return_value=False) with patch.object(voice_handler, "start") as mock_start: await voice_handler.voice_bot_button_handler( mock_message, mock_state, mock_db, mock_settings ) mock_db.check_voice_bot_welcome_received.assert_called_once_with(123) mock_start.assert_called_once_with( mock_message, mock_state, mock_db, mock_settings ) @pytest.mark.asyncio async def test_voice_bot_button_handler_exception( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """Тест обработчика кнопки при исключении""" mock_db.check_voice_bot_welcome_received.side_effect = Exception("Test error") with patch.object(voice_handler, "start") as mock_start: await voice_handler.voice_bot_button_handler( mock_message, mock_state, mock_db, mock_settings ) mock_start.assert_called_once_with( mock_message, mock_state, mock_db, mock_settings ) # Упрощенные тесты для основных функций @pytest.mark.asyncio async def test_standup_write( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """Тест функции standup_write""" with patch( "helper_bot.handlers.voice.voice_handler.messages.get_message" ) as mock_get_message: mock_get_message.return_value = "Record voice message" await voice_handler.standup_write( mock_message, mock_state, mock_db, mock_settings ) mock_state.set_state.assert_called_once_with(STATE_STANDUP_WRITE) mock_message.answer.assert_called_once_with( text="Record voice message", reply_markup=types.ReplyKeyboardRemove() ) def test_setup_handlers(self, voice_handler): """Тест настройки обработчиков""" # Проверяем, что роутер содержит обработчики assert len(voice_handler.router.message.handlers) > 0 @pytest.mark.asyncio async def test_restart_function_sets_state_and_answers( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """restart_function пересылает в логи, обновляет инфо и отправляет клавиатуру.""" with patch( "helper_bot.handlers.voice.voice_handler.update_user_info", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.check_user_emoji", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.get_main_keyboard" ) as mock_keyboard: mock_keyboard.return_value = MagicMock() await voice_handler.restart_function( mock_message, mock_state, mock_db, mock_settings ) mock_message.forward.assert_awaited_once_with( chat_id=mock_settings["Telegram"]["group_for_logs"] ) mock_state.set_state.assert_called_once_with(STATE_START) mock_message.answer.assert_called_once() assert ( "Записывайся" in mock_message.answer.call_args[1]["text"] or "слушай" in mock_message.answer.call_args[1]["text"] ) @pytest.mark.asyncio async def test_start_sets_state_and_sends_welcome( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """start устанавливает состояние и отправляет приветствие через VoiceBotService.""" mock_db.mark_voice_bot_welcome_received = AsyncMock() with patch( "helper_bot.handlers.voice.voice_handler.update_user_info", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.get_user_emoji_safe", new_callable=AsyncMock, return_value="😊", ): with patch( "helper_bot.handlers.voice.voice_handler.VoiceBotService" ) as mock_svc_cls: mock_svc = MagicMock() mock_svc.send_welcome_messages = AsyncMock() mock_svc_cls.return_value = mock_svc await voice_handler.start( mock_message, mock_state, mock_db, mock_settings ) mock_state.set_state.assert_called_once_with(STATE_START) mock_svc.send_welcome_messages.assert_awaited_once() mock_db.mark_voice_bot_welcome_received.assert_awaited_once_with( 123 ) @pytest.mark.asyncio async def test_help_function_answers_help_message( self, voice_handler, mock_message, mock_state, mock_settings ): """help_function пересылает в логи и отправляет HELP_MESSAGE.""" with patch( "helper_bot.handlers.voice.voice_handler.update_user_info", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.messages.get_message" ) as mock_get: mock_get.return_value = "Help text" await voice_handler.help_function( mock_message, mock_state, mock_settings ) mock_message.forward.assert_awaited_once_with( chat_id=mock_settings["Telegram"]["group_for_logs"] ) mock_message.answer.assert_called_once_with( text="Help text", disable_web_page_preview=not mock_settings["Telegram"][ "preview_link" ], ) mock_state.set_state.assert_called_once_with(STATE_START) @pytest.mark.asyncio async def test_cancel_handler_returns_to_menu( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """cancel_handler пересылает в логи и возвращает в главное меню.""" with patch( "helper_bot.handlers.voice.voice_handler.update_user_info", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.get_reply_keyboard", new_callable=AsyncMock, ) as mock_kb: mock_kb.return_value = MagicMock() await voice_handler.cancel_handler( mock_message, mock_state, mock_db, mock_settings ) mock_message.forward.assert_awaited_once() mock_message.answer.assert_called_once() assert ( "Добро пожаловать" in mock_message.answer.call_args[1]["text"] or "меню" in mock_message.answer.call_args[1]["text"] ) mock_state.set_state.assert_called_once() @pytest.mark.asyncio async def test_refresh_listen_function_clears_listenings( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """refresh_listen_function очищает прослушивания и отправляет сообщение.""" with patch( "helper_bot.handlers.voice.voice_handler.update_user_info", new_callable=AsyncMock, ): with patch( "helper_bot.handlers.voice.voice_handler.get_main_keyboard" ) as mock_keyboard: with patch( "helper_bot.handlers.voice.voice_handler.VoiceBotService" ) as mock_svc_cls: mock_svc = MagicMock() mock_svc.clear_user_listenings = AsyncMock() mock_svc_cls.return_value = mock_svc with patch( "helper_bot.handlers.voice.voice_handler.messages.get_message" ) as mock_get: mock_get.return_value = "Прослушивания сброшены" await voice_handler.refresh_listen_function( mock_message, mock_state, mock_db, mock_settings ) mock_svc.clear_user_listenings.assert_awaited_once_with(123) mock_message.answer.assert_called_once() mock_state.set_state.assert_called_once_with(STATE_START) @pytest.mark.asyncio async def test_suggest_voice_valid_sends_to_group_and_saves( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """suggest_voice при валидном голосовом отправляет в группу и сохраняет message_id.""" mock_message.voice = MagicMock() mock_message.voice.file_id = "voice_123" with patch( "helper_bot.handlers.voice.voice_handler.validate_voice_message", new_callable=AsyncMock, return_value=True, ): with patch( "helper_bot.handlers.voice.voice_handler.send_voice_message", new_callable=AsyncMock, ) as mock_send: sent = MagicMock() sent.message_id = 999 mock_send.return_value = sent mock_db.set_user_id_and_message_id_for_voice_bot = AsyncMock() with patch( "helper_bot.handlers.voice.voice_handler.get_reply_keyboard_for_voice" ) as mock_kb: mock_kb.return_value = MagicMock() with patch( "helper_bot.handlers.voice.voice_handler.messages.get_message" ) as mock_get: mock_get.return_value = "Голос сохранён" await voice_handler.suggest_voice( mock_message, mock_state, mock_db, mock_settings ) mock_send.assert_awaited_once() mock_db.set_user_id_and_message_id_for_voice_bot.assert_awaited_once_with( 999, 123 ) mock_message.answer.assert_called_once() mock_state.set_state.assert_called_once_with(STATE_START) @pytest.mark.asyncio async def test_suggest_voice_invalid_keeps_state_standup_write( self, voice_handler, mock_message, mock_state, mock_db, mock_settings ): """suggest_voice при невалидном голосовом оставляет состояние STANDUP_WRITE.""" with patch( "helper_bot.handlers.voice.voice_handler.validate_voice_message", new_callable=AsyncMock, return_value=False, ): with patch( "helper_bot.handlers.voice.voice_handler.get_main_keyboard" ) as mock_keyboard: mock_keyboard.return_value = MagicMock() with patch( "helper_bot.handlers.voice.voice_handler.messages.get_message" ) as mock_get: mock_get.return_value = "Неверный контент" await voice_handler.suggest_voice( mock_message, mock_state, mock_db, mock_settings ) mock_message.answer.assert_called() mock_state.set_state.assert_called_once_with(STATE_STANDUP_WRITE) @pytest.mark.asyncio async def test_handle_emoji_message_answers_emoji( self, voice_handler, mock_message, mock_state, mock_settings ): """handle_emoji_message пересылает в логи и отвечает эмодзи или ничего.""" with patch( "helper_bot.handlers.voice.voice_handler.check_user_emoji", new_callable=AsyncMock, return_value="😊", ): await voice_handler.handle_emoji_message( mock_message, mock_state, mock_settings ) mock_message.forward.assert_awaited_once() mock_state.set_state.assert_called_once_with(STATE_START) mock_message.answer.assert_called_once_with( f"Твоя эмодзя - 😊", parse_mode="HTML" ) if __name__ == "__main__": pytest.main([__file__])