- Создана система отслеживания миграций (MigrationRepository, таблица migrations) - Добавлен скрипт apply_migrations.py для автоматического применения миграций - Созданы CI/CD пайплайны (.github/workflows/ci.yml, deploy.yml) - Обновлена документация по миграциям в database-patterns.md - Миграции применяются автоматически при деплое в продакшн
211 lines
8.4 KiB
Python
211 lines
8.4 KiB
Python
from datetime import datetime, timedelta
|
||
from unittest.mock import Mock, patch
|
||
|
||
import pytest
|
||
from aiogram import types
|
||
from helper_bot.handlers.voice.utils import (format_time_ago,
|
||
get_last_message_text,
|
||
get_user_emoji_safe, plural_time,
|
||
validate_voice_message)
|
||
|
||
|
||
class TestVoiceUtils:
|
||
"""Тесты для утилит voice модуля"""
|
||
|
||
@pytest.fixture
|
||
def mock_bot_db(self):
|
||
"""Мок для базы данных"""
|
||
mock_db = Mock()
|
||
mock_db.settings = {
|
||
'Telegram': {
|
||
'group_for_logs': 'test_logs_chat'
|
||
}
|
||
}
|
||
return mock_db
|
||
|
||
@pytest.fixture
|
||
def mock_message(self):
|
||
"""Мок для сообщения"""
|
||
message = Mock(spec=types.Message)
|
||
message.from_user.id = 123
|
||
message.from_user.first_name = "Test"
|
||
message.from_user.full_name = "Test User"
|
||
message.from_user.username = "testuser"
|
||
message.from_user.is_bot = False
|
||
message.from_user.language_code = "ru"
|
||
message.chat.id = 456
|
||
return message
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_last_message_text(self, mock_bot_db):
|
||
"""Тест получения последнего сообщения"""
|
||
# Возвращаем UNIX timestamp
|
||
from unittest.mock import AsyncMock
|
||
mock_bot_db.last_date_audio = AsyncMock(return_value=1641034800) # 2022-01-01 12:00:00
|
||
|
||
result = await get_last_message_text(mock_bot_db)
|
||
|
||
assert result is not None
|
||
assert "минут" in result or "часа" in result or "дня" in result or "день" in result or "дней" in result
|
||
mock_bot_db.last_date_audio.assert_called_once()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_validate_voice_message_valid(self):
|
||
"""Тест валидации голосового сообщения"""
|
||
mock_message = Mock()
|
||
mock_message.content_type = 'voice'
|
||
mock_message.voice = Mock()
|
||
|
||
result = await validate_voice_message(mock_message)
|
||
|
||
assert result is True
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_validate_voice_message_invalid(self):
|
||
"""Тест валидации невалидного сообщения"""
|
||
mock_message = Mock()
|
||
mock_message.voice = None
|
||
|
||
result = await validate_voice_message(mock_message)
|
||
|
||
assert result is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_user_emoji_safe_with_emoji(self, mock_bot_db):
|
||
"""Тест безопасного получения эмодзи пользователя когда эмодзи есть"""
|
||
from unittest.mock import AsyncMock
|
||
mock_bot_db.get_user_emoji = AsyncMock(return_value="😊")
|
||
|
||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||
|
||
assert result == "😊"
|
||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_user_emoji_safe_without_emoji(self, mock_bot_db):
|
||
"""Тест безопасного получения эмодзи пользователя когда эмодзи нет"""
|
||
from unittest.mock import AsyncMock
|
||
mock_bot_db.get_user_emoji = AsyncMock(return_value=None)
|
||
|
||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||
|
||
assert result == "😊"
|
||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_user_emoji_safe_with_empty_emoji(self, mock_bot_db):
|
||
"""Тест безопасного получения эмодзи пользователя с пустым эмодзи"""
|
||
from unittest.mock import AsyncMock
|
||
mock_bot_db.get_user_emoji = AsyncMock(return_value="")
|
||
|
||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||
|
||
assert result == "😊"
|
||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_user_emoji_safe_with_error(self, mock_bot_db):
|
||
"""Тест безопасного получения эмодзи пользователя при ошибке"""
|
||
from unittest.mock import AsyncMock
|
||
mock_bot_db.get_user_emoji = AsyncMock(return_value="Ошибка")
|
||
|
||
result = await get_user_emoji_safe(mock_bot_db, 123)
|
||
|
||
assert result == "Ошибка"
|
||
mock_bot_db.get_user_emoji.assert_called_once_with(123)
|
||
|
||
def test_format_time_ago_minutes(self):
|
||
"""Тест форматирования времени в минутах"""
|
||
from datetime import datetime, timedelta
|
||
|
||
# Создаем дату 30 минут назад
|
||
test_date = (datetime.now() - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
result = format_time_ago(test_date)
|
||
|
||
assert result is not None
|
||
assert "минут" in result
|
||
assert "30" in result or "29" in result or "31" in result
|
||
|
||
def test_format_time_ago_hours(self):
|
||
"""Тест форматирования времени в часах"""
|
||
from datetime import datetime, timedelta
|
||
|
||
# Создаем дату 2 часа назад
|
||
test_date = (datetime.now() - timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
result = format_time_ago(test_date)
|
||
|
||
assert result is not None
|
||
assert "часа" in result or "часов" in result
|
||
|
||
def test_format_time_ago_days(self):
|
||
"""Тест форматирования времени в днях"""
|
||
from datetime import datetime, timedelta
|
||
|
||
# Создаем дату 3 дня назад
|
||
test_date = (datetime.now() - timedelta(days=3)).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
result = format_time_ago(test_date)
|
||
|
||
assert result is not None
|
||
assert "дня" in result or "дней" in result
|
||
|
||
def test_format_time_ago_none(self):
|
||
"""Тест форматирования времени с None"""
|
||
result = format_time_ago(None)
|
||
|
||
assert result is None
|
||
|
||
def test_format_time_ago_invalid_format(self):
|
||
"""Тест форматирования времени с неверным форматом"""
|
||
result = format_time_ago("invalid_date_format")
|
||
|
||
assert result is None
|
||
|
||
def test_plural_time_minutes(self):
|
||
"""Тест множественного числа для минут"""
|
||
assert "1 минуту" in plural_time(1, 1)
|
||
assert "2 минуты" in plural_time(1, 2)
|
||
assert "5 минут" in plural_time(1, 5)
|
||
assert "11 минут" in plural_time(1, 11)
|
||
assert "21 минуту" in plural_time(1, 21)
|
||
|
||
def test_plural_time_hours(self):
|
||
"""Тест множественного числа для часов"""
|
||
assert "1 час" in plural_time(2, 1)
|
||
assert "2 часа" in plural_time(2, 2)
|
||
assert "5 часов" in plural_time(2, 5)
|
||
assert "11 часов" in plural_time(2, 11)
|
||
assert "21 час" in plural_time(2, 21)
|
||
|
||
def test_plural_time_days(self):
|
||
"""Тест множественного числа для дней"""
|
||
assert "1 день" in plural_time(3, 1)
|
||
assert "2 дня" in plural_time(3, 2)
|
||
assert "5 дней" in plural_time(3, 5)
|
||
assert "11 дней" in plural_time(3, 11)
|
||
assert "21 день" in plural_time(3, 21)
|
||
|
||
def test_plural_time_invalid_type(self):
|
||
"""Тест множественного числа с неверным типом"""
|
||
result = plural_time(4, 5)
|
||
|
||
assert result == "5"
|
||
|
||
def test_plural_time_edge_cases(self):
|
||
"""Тест граничных случаев для множественного числа"""
|
||
# Тест для 0
|
||
assert "0 минут" in plural_time(1, 0)
|
||
assert "0 часов" in plural_time(2, 0)
|
||
assert "0 дней" in plural_time(3, 0)
|
||
|
||
# Тест для больших чисел
|
||
assert "100 минут" in plural_time(1, 100)
|
||
assert "100 часов" in plural_time(2, 100)
|
||
assert "100 дней" in plural_time(3, 100)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
pytest.main([__file__])
|