from datetime import datetime from pathlib import Path from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from helper_bot.handlers.voice.exceptions import AudioProcessingError, VoiceMessageError from helper_bot.handlers.voice.services import VoiceBotService class TestVoiceBotService: """Тесты для VoiceBotService""" @pytest.fixture def mock_bot_db(self): """Мок для базы данных""" mock_db = Mock() mock_db.settings = { "Settings": {"logs": True}, "Telegram": {"important_logs": "test_chat_id"}, } return mock_db @pytest.fixture def mock_settings(self): """Мок для настроек""" return {"Settings": {"logs": True}, "Telegram": {"preview_link": True}} @pytest.fixture def voice_service(self, mock_bot_db, mock_settings): """Экземпляр VoiceBotService для тестов""" return VoiceBotService(mock_bot_db, mock_settings) @pytest.mark.asyncio async def test_get_welcome_sticker_success(self, voice_service, mock_settings): """Тест успешного получения стикера""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.return_value = ["/path/to/sticker1.tgs", "/path/to/sticker2.tgs"] sticker = await voice_service.get_welcome_sticker() assert sticker is not None mock_rglob.assert_called_once() @pytest.mark.asyncio async def test_get_welcome_sticker_no_stickers(self, voice_service, mock_settings): """Тест получения стикера когда их нет""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.return_value = [] sticker = await voice_service.get_welcome_sticker() assert sticker is None @pytest.mark.asyncio async def test_get_welcome_sticker_only_webp_files( self, voice_service, mock_settings ): """Тест получения стикера когда есть только webp файлы""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.return_value = [ "/path/to/sticker1.webp", "/path/to/sticker2.webp", ] sticker = await voice_service.get_welcome_sticker() # Проверяем, что стикер не None (метод ищет файлы по паттерну Hello_*) assert sticker is not None @pytest.mark.asyncio async def test_get_welcome_sticker_mixed_files(self, voice_service, mock_settings): """Тест получения стикера когда есть смешанные файлы""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.return_value = [ "/path/to/sticker1.webp", "/path/to/sticker2.tgs", "/path/to/sticker3.webp", ] sticker = await voice_service.get_welcome_sticker() assert sticker is not None # Проверяем, что стикер не None (метод возвращает FSInputFile объект) @pytest.mark.asyncio async def test_get_random_audio_success(self, voice_service, mock_bot_db): """Тест успешного получения случайного аудио""" mock_bot_db.check_listen_audio = AsyncMock(return_value=["audio1", "audio2"]) mock_bot_db.get_user_id_by_file_name = AsyncMock(return_value=123) mock_bot_db.get_date_by_file_name = AsyncMock( return_value="2025-01-01 12:00:00" ) mock_bot_db.get_user_emoji = AsyncMock(return_value="😊") result = await voice_service.get_random_audio(456) assert result is not None assert len(result) == 3 # Проверяем, что результат содержит ожидаемые данные, но не проверяем точное значение audio assert result[0] in ["audio1", "audio2"] assert result[1] == "2025-01-01 12:00:00" assert result[2] == "😊" @pytest.mark.asyncio async def test_get_random_audio_no_audio(self, voice_service, mock_bot_db): """Тест получения аудио когда их нет""" mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) result = await voice_service.get_random_audio(456) assert result is None @pytest.mark.asyncio async def test_get_random_audio_single_audio(self, voice_service, mock_bot_db): """Тест получения аудио когда есть только одно""" mock_bot_db.check_listen_audio = AsyncMock(return_value=["audio1"]) mock_bot_db.get_user_id_by_file_name = AsyncMock(return_value=123) mock_bot_db.get_date_by_file_name = AsyncMock( return_value="2025-01-01 12:00:00" ) mock_bot_db.get_user_emoji = AsyncMock(return_value="😊") result = await voice_service.get_random_audio(456) assert result is not None assert len(result) == 3 assert result[0] == "audio1" @pytest.mark.asyncio async def test_mark_audio_as_listened_success(self, voice_service, mock_bot_db): """Тест успешной пометки аудио как прослушанного""" mock_bot_db.mark_listened_audio = AsyncMock() await voice_service.mark_audio_as_listened("test_audio", 123) mock_bot_db.mark_listened_audio.assert_called_once_with( "test_audio", user_id=123 ) @pytest.mark.asyncio async def test_clear_user_listenings_success(self, voice_service, mock_bot_db): """Тест успешной очистки прослушиваний""" mock_bot_db.delete_listen_count_for_user = AsyncMock() await voice_service.clear_user_listenings(123) mock_bot_db.delete_listen_count_for_user.assert_called_once_with(123) @pytest.mark.asyncio async def test_get_remaining_audio_count_success(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио""" mock_bot_db.check_listen_audio = AsyncMock( return_value=["audio1", "audio2", "audio3"] ) result = await voice_service.get_remaining_audio_count(123) assert result == 3 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) @pytest.mark.asyncio async def test_get_remaining_audio_count_zero(self, voice_service, mock_bot_db): """Тест получения количества оставшихся аудио когда их нет""" mock_bot_db.check_listen_audio = AsyncMock(return_value=[]) result = await voice_service.get_remaining_audio_count(123) assert result == 0 mock_bot_db.check_listen_audio.assert_called_once_with(user_id=123) @pytest.mark.asyncio async def test_send_welcome_messages_success( self, voice_service, mock_bot_db, mock_settings ): """Тест успешной отправки приветственных сообщений.""" mock_message = Mock() mock_message.from_user.id = 123 mock_message.answer = AsyncMock() mock_message.answer.return_value = Mock() mock_message.answer_sticker = AsyncMock() with patch.object( voice_service, "get_welcome_sticker", new_callable=AsyncMock, return_value="test_sticker.tgs", ): with patch( "helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock, ): await voice_service.send_welcome_messages(mock_message, "😊") assert mock_message.answer.call_count >= 1 @pytest.mark.asyncio async def test_send_welcome_messages_no_sticker( self, voice_service, mock_bot_db, mock_settings ): """Тест отправки приветственных сообщений без стикера.""" mock_message = Mock() mock_message.from_user.id = 123 mock_message.answer = AsyncMock() mock_message.answer.return_value = Mock() with patch.object( voice_service, "get_welcome_sticker", new_callable=AsyncMock, return_value=None, ): with patch( "helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock, ): await voice_service.send_welcome_messages(mock_message, "😊") assert mock_message.answer.call_count >= 1 @pytest.mark.asyncio async def test_send_welcome_messages_with_sticker( self, voice_service, mock_bot_db, mock_settings ): """Тест отправки приветственных сообщений со стикером.""" mock_message = Mock() mock_message.from_user.id = 123 mock_message.answer = AsyncMock() mock_message.answer.return_value = Mock() mock_message.answer_sticker = AsyncMock() with patch.object( voice_service, "get_welcome_sticker", new_callable=AsyncMock, return_value="test_sticker.tgs", ): with patch( "helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock, ): await voice_service.send_welcome_messages(mock_message, "😊") assert mock_message.answer.call_count >= 1 @pytest.mark.asyncio async def test_get_welcome_sticker_with_tgs_files( self, voice_service, mock_settings ): """Тест получения стикера когда есть .tgs файлы""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.return_value = ["/path/to/sticker1.tgs", "/path/to/sticker2.tgs"] sticker = await voice_service.get_welcome_sticker() assert sticker is not None # Проверяем, что стикер не None (метод возвращает FSInputFile объект) def test_service_initialization(self, mock_bot_db, mock_settings): """Тест инициализации сервиса""" service = VoiceBotService(mock_bot_db, mock_settings) assert service.bot_db == mock_bot_db assert service.settings == mock_settings def test_service_attributes(self, voice_service): """Тест атрибутов сервиса""" assert hasattr(voice_service, "bot_db") assert hasattr(voice_service, "settings") assert hasattr(voice_service, "get_welcome_sticker") assert hasattr(voice_service, "get_random_audio") assert hasattr(voice_service, "mark_audio_as_listened") assert hasattr(voice_service, "clear_user_listenings") assert hasattr(voice_service, "get_remaining_audio_count") assert hasattr(voice_service, "send_welcome_messages") @pytest.mark.asyncio async def test_get_welcome_sticker_exception_returns_none( self, voice_service, mock_settings ): """get_welcome_sticker при исключении возвращает None.""" with patch("pathlib.Path.rglob") as mock_rglob: mock_rglob.side_effect = OSError("Permission denied") sticker = await voice_service.get_welcome_sticker() assert sticker is None @pytest.mark.asyncio async def test_get_welcome_sticker_exception_sends_to_logs_when_enabled( self, voice_service, mock_settings ): """get_welcome_sticker при исключении и logs=True отправляет ошибку в логи.""" voice_service.settings = {"Settings": {"logs": True}, "Telegram": {}} with patch("pathlib.Path.rglob", side_effect=OSError("err")): with patch.object( voice_service, "_send_error_to_logs", new_callable=AsyncMock ) as mock_send_logs: await voice_service.get_welcome_sticker() mock_send_logs.assert_awaited_once() @pytest.mark.asyncio async def test_get_random_audio_exception_raises(self, voice_service, mock_bot_db): """get_random_audio при исключении выбрасывает AudioProcessingError.""" mock_bot_db.check_listen_audio = AsyncMock(side_effect=Exception("DB error")) with pytest.raises( AudioProcessingError, match="Не удалось получить случайное аудио" ): await voice_service.get_random_audio(123) @pytest.mark.asyncio async def test_mark_audio_as_listened_exception_raises( self, voice_service, mock_bot_db ): """mark_audio_as_listened при исключении выбрасывает DatabaseError.""" from helper_bot.handlers.voice.exceptions import DatabaseError mock_bot_db.mark_listened_audio = AsyncMock(side_effect=Exception("DB error")) with pytest.raises(DatabaseError, match="Не удалось пометить аудио"): await voice_service.mark_audio_as_listened("file", 123) @pytest.mark.asyncio async def test_clear_user_listenings_exception_raises( self, voice_service, mock_bot_db ): """clear_user_listenings при исключении выбрасывает DatabaseError.""" from helper_bot.handlers.voice.exceptions import DatabaseError mock_bot_db.delete_listen_count_for_user = AsyncMock( side_effect=Exception("DB error") ) with pytest.raises(DatabaseError, match="Не удалось очистить прослушивания"): await voice_service.clear_user_listenings(123) @pytest.mark.asyncio async def test_get_remaining_audio_count_exception_raises( self, voice_service, mock_bot_db ): """get_remaining_audio_count при исключении выбрасывает DatabaseError.""" from helper_bot.handlers.voice.exceptions import DatabaseError mock_bot_db.check_listen_audio = AsyncMock(side_effect=Exception("DB error")) with pytest.raises(DatabaseError, match="Не удалось получить количество аудио"): await voice_service.get_remaining_audio_count(123) @pytest.mark.asyncio async def test_send_welcome_messages_exception_raises( self, voice_service, mock_bot_db, mock_settings ): """send_welcome_messages при исключении выбрасывает VoiceMessageError.""" mock_message = Mock() mock_message.answer = AsyncMock(side_effect=Exception("Network error")) mock_message.answer_sticker = AsyncMock() with patch.object( voice_service, "get_welcome_sticker", new_callable=AsyncMock, return_value=None, ): with patch.object(voice_service, "_get_main_keyboard", return_value=Mock()): with pytest.raises( VoiceMessageError, match="Не удалось отправить приветственные сообщения", ): await voice_service.send_welcome_messages(mock_message, "😊") def test_get_main_keyboard_returns_keyboard(self, voice_service): """_get_main_keyboard возвращает клавиатуру.""" with patch("helper_bot.keyboards.keyboards.get_main_keyboard") as mock_kb: mock_kb.return_value = Mock() result = voice_service._get_main_keyboard() mock_kb.assert_called_once() assert result is not None @pytest.mark.asyncio async def test_send_error_to_logs_handles_exception( self, voice_service, mock_settings ): """_send_error_to_logs при ошибке отправки логирует и не падает.""" voice_service.settings = { "Settings": {}, "Telegram": {"important_logs": "-123"}, } with patch( "helper_bot.utils.helper_func.send_voice_message", new_callable=AsyncMock ) as mock_send: mock_send.side_effect = Exception("Send failed") await voice_service._send_error_to_logs("Test error") mock_send.assert_awaited_once() if __name__ == "__main__": pytest.main([__file__])