398 lines
16 KiB
Python
398 lines
16 KiB
Python
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__])
|