Files
telegram-helper-bot/tests/test_voice_services.py
Andrey 3d6b4353f9
All checks were successful
CI pipeline / Test & Code Quality (push) Successful in 34s
Refactor imports across multiple files to improve code organization and readability.
2026-02-28 23:24:25 +03:00

398 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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__])