Files
telegram-helper-bot/tests/test_audio_file_service.py
2026-02-01 23:03:23 +03:00

347 lines
14 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.
import time
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, mock_open, patch
import pytest
from helper_bot.handlers.voice.exceptions import DatabaseError, FileOperationError
from helper_bot.handlers.voice.services import AudioFileService
@pytest.fixture
def mock_bot_db():
"""Мок для базы данных"""
mock_db = Mock()
mock_db.get_user_audio_records_count = AsyncMock(return_value=0)
mock_db.get_path_for_audio_record = AsyncMock(return_value=None)
mock_db.add_audio_record_simple = AsyncMock()
return mock_db
@pytest.fixture
def audio_service(mock_bot_db):
"""Экземпляр AudioFileService для тестов"""
return AudioFileService(mock_bot_db)
@pytest.fixture
def sample_datetime():
"""Тестовая дата"""
return datetime(2025, 1, 15, 14, 30, 0)
@pytest.fixture
def mock_bot():
"""Мок для бота"""
bot = Mock()
bot.get_file = AsyncMock()
bot.download_file = AsyncMock()
return bot
@pytest.fixture
def mock_message():
"""Мок для сообщения"""
message = Mock()
message.voice = Mock()
message.voice.file_id = "test_file_id"
return message
@pytest.fixture
def mock_file_info():
"""Мок для информации о файле"""
file_info = Mock()
file_info.file_path = "voice/test_file_id.ogg"
return file_info
class TestGenerateFileName:
"""Тесты для метода generate_file_name"""
@pytest.mark.asyncio
async def test_generate_file_name_first_record(self, audio_service, mock_bot_db):
"""Тест генерации имени файла для первой записи пользователя"""
mock_bot_db.get_user_audio_records_count.return_value = 0
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_1"
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
@pytest.mark.asyncio
async def test_generate_file_name_existing_records(
self, audio_service, mock_bot_db
):
"""Тест генерации имени файла для существующих записей"""
mock_bot_db.get_user_audio_records_count.return_value = 3
mock_bot_db.get_path_for_audio_record.return_value = (
"message_from_12345_number_3"
)
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_4"
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
@pytest.mark.asyncio
async def test_generate_file_name_no_last_record(self, audio_service, mock_bot_db):
"""Тест генерации имени файла когда нет последней записи"""
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = None
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_3"
@pytest.mark.asyncio
async def test_generate_file_name_invalid_last_record_format(
self, audio_service, mock_bot_db
):
"""Тест генерации имени файла с некорректным форматом последней записи"""
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = "invalid_format"
result = await audio_service.generate_file_name(12345)
assert result == "message_from_12345_number_3"
@pytest.mark.asyncio
async def test_generate_file_name_exception_handling(
self, audio_service, mock_bot_db
):
"""Тест обработки исключений при генерации имени файла"""
mock_bot_db.get_user_audio_records_count.side_effect = Exception(
"Database error"
)
with pytest.raises(FileOperationError) as exc_info:
await audio_service.generate_file_name(12345)
assert "Не удалось сгенерировать имя файла" in str(exc_info.value)
class TestSaveAudioFile:
"""Тесты для метода save_audio_file"""
@pytest.mark.asyncio
async def test_save_audio_file_success(
self, audio_service, mock_bot_db, sample_datetime
):
"""Тест успешного сохранения аудио файла"""
file_name = "test_audio"
user_id = 12345
file_id = "test_file_id"
# Мокаем verify_file_exists чтобы он возвращал True
with patch.object(audio_service, "verify_file_exists", return_value=True):
await audio_service.save_audio_file(
file_name, user_id, sample_datetime, file_id
)
mock_bot_db.add_audio_record_simple.assert_called_once_with(
file_name, user_id, sample_datetime
)
@pytest.mark.asyncio
async def test_save_audio_file_with_string_date(self, audio_service, mock_bot_db):
"""Тест сохранения аудио файла со строковой датой"""
file_name = "test_audio"
user_id = 12345
date_string = "2025-01-15 14:30:00"
file_id = "test_file_id"
# Мокаем verify_file_exists чтобы он возвращал True
with patch.object(audio_service, "verify_file_exists", return_value=True):
await audio_service.save_audio_file(
file_name, user_id, date_string, file_id
)
mock_bot_db.add_audio_record_simple.assert_called_once_with(
file_name, user_id, date_string
)
@pytest.mark.asyncio
async def test_save_audio_file_exception_handling(
self, audio_service, mock_bot_db, sample_datetime
):
"""Тест обработки исключений при сохранении аудио файла"""
mock_bot_db.add_audio_record_simple.side_effect = Exception("Database error")
# Мокаем verify_file_exists чтобы он возвращал True
with patch.object(audio_service, "verify_file_exists", return_value=True):
with pytest.raises(DatabaseError) as exc_info:
await audio_service.save_audio_file(
"test", 12345, sample_datetime, "file_id"
)
assert "Не удалось сохранить аудио файл в БД" in str(exc_info.value)
class TestDownloadAndSaveAudio:
"""Тесты для метода download_and_save_audio"""
@pytest.mark.asyncio
async def test_download_and_save_audio_success(
self, audio_service, mock_bot, mock_message, mock_file_info
):
"""Тест успешного скачивания и сохранения аудио"""
mock_bot.get_file.return_value = mock_file_info
# Мокаем скачанный файл
mock_downloaded_file = Mock()
mock_downloaded_file.tell.return_value = 0
mock_downloaded_file.seek = Mock()
mock_downloaded_file.read.return_value = b"audio_data"
# Настраиваем поведение tell() для получения размера файла
def mock_tell():
return 0 if mock_downloaded_file.seek.call_count == 0 else 1024
mock_downloaded_file.tell = Mock(side_effect=mock_tell)
mock_bot.download_file.return_value = mock_downloaded_file
with patch("builtins.open", mock_open()) as mock_file:
with patch("os.makedirs"):
with patch("os.path.exists", return_value=True):
with patch("os.path.getsize", return_value=1024):
await audio_service.download_and_save_audio(
mock_bot, mock_message, "test_audio"
)
mock_bot.get_file.assert_called_once_with(
file_id="test_file_id"
)
mock_bot.download_file.assert_called_once_with(
file_path="voice/test_file_id.ogg"
)
mock_file.assert_called_once()
@pytest.mark.asyncio
async def test_download_and_save_audio_no_message(self, audio_service, mock_bot):
"""Тест скачивания когда сообщение отсутствует."""
with patch(
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
):
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(
mock_bot, None, "test_audio"
)
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_no_voice(self, audio_service, mock_bot):
"""Тест скачивания когда у сообщения нет voice атрибута."""
message = Mock()
message.voice = None
with patch(
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
):
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(
mock_bot, message, "test_audio"
)
assert "Сообщение или голосовое сообщение не найдено" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_download_failed(
self, audio_service, mock_bot, mock_message, mock_file_info
):
"""Тест скачивания когда загрузка не удалась."""
mock_bot.get_file.return_value = mock_file_info
mock_bot.download_file.return_value = None
with patch(
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
):
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(
mock_bot, mock_message, "test_audio"
)
assert "Не удалось скачать файл" in str(exc_info.value)
@pytest.mark.asyncio
async def test_download_and_save_audio_exception_handling(
self, audio_service, mock_bot, mock_message
):
"""Тест обработки исключений при скачивании."""
mock_bot.get_file.side_effect = Exception("Network error")
with patch(
"helper_bot.handlers.voice.services.asyncio.sleep", new_callable=AsyncMock
):
with pytest.raises(FileOperationError) as exc_info:
await audio_service.download_and_save_audio(
mock_bot, mock_message, "test_audio"
)
assert "Не удалось скачать и сохранить аудио" in str(exc_info.value)
class TestAudioFileServiceIntegration:
"""Интеграционные тесты для AudioFileService"""
@pytest.mark.asyncio
async def test_full_audio_processing_workflow(self, mock_bot_db):
"""Тест полного рабочего процесса обработки аудио"""
service = AudioFileService(mock_bot_db)
# Настраиваем моки
mock_bot_db.get_user_audio_records_count.return_value = 1
mock_bot_db.get_path_for_audio_record.return_value = (
"message_from_12345_number_1"
)
mock_bot_db.add_audio_record_simple = AsyncMock()
# Тестируем генерацию имени файла
file_name = await service.generate_file_name(12345)
assert file_name == "message_from_12345_number_2"
# Тестируем сохранение в БД
test_date = datetime.now()
with patch.object(service, "verify_file_exists", return_value=True):
await service.save_audio_file(file_name, 12345, test_date, "test_file_id")
# Проверяем вызовы
mock_bot_db.get_user_audio_records_count.assert_called_once_with(user_id=12345)
mock_bot_db.get_path_for_audio_record.assert_called_once_with(user_id=12345)
mock_bot_db.add_audio_record_simple.assert_called_once_with(
file_name, 12345, test_date
)
@pytest.mark.asyncio
async def test_file_name_generation_sequence(self, mock_bot_db):
"""Тест последовательности генерации имен файлов"""
service = AudioFileService(mock_bot_db)
# Первая запись
mock_bot_db.get_user_audio_records_count.return_value = 0
file_name_1 = await service.generate_file_name(12345)
assert file_name_1 == "message_from_12345_number_1"
# Вторая запись
mock_bot_db.get_user_audio_records_count.return_value = 1
mock_bot_db.get_path_for_audio_record.return_value = (
"message_from_12345_number_1"
)
file_name_2 = await service.generate_file_name(12345)
assert file_name_2 == "message_from_12345_number_2"
# Третья запись
mock_bot_db.get_user_audio_records_count.return_value = 2
mock_bot_db.get_path_for_audio_record.return_value = (
"message_from_12345_number_2"
)
file_name_3 = await service.generate_file_name(12345)
assert file_name_3 == "message_from_12345_number_3"
if __name__ == "__main__":
pytest.main([__file__])