mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
Подготовка к релизу
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
Тесты для системы исключений.
|
||||
Проверяет корректность создания и обработки кастомных исключений.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from core.exceptions import (
|
||||
BotException, DatabaseError, ValidationError,
|
||||
ConfigurationError, APIError, GameError,
|
||||
ModerationError, PermissionError
|
||||
)
|
||||
|
||||
|
||||
class TestBotException:
|
||||
"""Тесты базового исключения BotException"""
|
||||
|
||||
def test_bot_exception_creation(self):
|
||||
"""Тест создания базового исключения"""
|
||||
exception = BotException("Тестовая ошибка", "TEST_ERROR")
|
||||
|
||||
assert str(exception) == "Тестовая ошибка"
|
||||
assert exception.error_code == "TEST_ERROR"
|
||||
assert exception.message == "Тестовая ошибка"
|
||||
|
||||
def test_bot_exception_default_code(self):
|
||||
"""Тест базового исключения с кодом по умолчанию"""
|
||||
exception = BotException("Тестовая ошибка")
|
||||
|
||||
assert exception.error_code is None
|
||||
assert str(exception) == "Тестовая ошибка"
|
||||
|
||||
|
||||
class TestDatabaseError:
|
||||
"""Тесты исключения базы данных"""
|
||||
|
||||
def test_database_error_creation(self):
|
||||
"""Тест создания исключения базы данных"""
|
||||
original_error = ValueError("Connection failed")
|
||||
exception = DatabaseError("Ошибка подключения", original_error)
|
||||
|
||||
assert str(exception) == "Ошибка подключения"
|
||||
assert exception.error_code == "DATABASE_ERROR"
|
||||
assert exception.original_error == original_error
|
||||
|
||||
def test_database_error_without_original(self):
|
||||
"""Тест исключения базы данных без оригинальной ошибки"""
|
||||
exception = DatabaseError("Ошибка запроса")
|
||||
|
||||
assert exception.original_error is None
|
||||
assert exception.error_code == "DATABASE_ERROR"
|
||||
|
||||
|
||||
class TestValidationError:
|
||||
"""Тесты исключения валидации"""
|
||||
|
||||
def test_validation_error_creation(self):
|
||||
"""Тест создания исключения валидации"""
|
||||
exception = ValidationError("Неверное значение", "email")
|
||||
|
||||
assert str(exception) == "Неверное значение"
|
||||
assert exception.error_code == "VALIDATION_ERROR"
|
||||
assert exception.field == "email"
|
||||
|
||||
def test_validation_error_without_field(self):
|
||||
"""Тест исключения валидации без указания поля"""
|
||||
exception = ValidationError("Общая ошибка валидации")
|
||||
|
||||
assert exception.field is None
|
||||
|
||||
|
||||
class TestConfigurationError:
|
||||
"""Тесты исключения конфигурации"""
|
||||
|
||||
def test_configuration_error_creation(self):
|
||||
"""Тест создания исключения конфигурации"""
|
||||
exception = ConfigurationError("Не найден токен", "BOT_TOKEN")
|
||||
|
||||
assert str(exception) == "Не найден токен"
|
||||
assert exception.error_code == "CONFIG_ERROR"
|
||||
assert exception.config_key == "BOT_TOKEN"
|
||||
|
||||
|
||||
class TestAPIError:
|
||||
"""Тесты исключения API"""
|
||||
|
||||
def test_api_error_creation(self):
|
||||
"""Тест создания исключения API"""
|
||||
exception = APIError("Превышен лимит", "openweather", 429)
|
||||
|
||||
assert str(exception) == "Превышен лимит"
|
||||
assert exception.error_code == "API_ERROR"
|
||||
assert exception.api_name == "openweather"
|
||||
assert exception.status_code == 429
|
||||
|
||||
def test_api_error_minimal(self):
|
||||
"""Тест минимального исключения API"""
|
||||
exception = APIError("Ошибка API")
|
||||
|
||||
assert exception.api_name is None
|
||||
assert exception.status_code is None
|
||||
|
||||
|
||||
class TestGameError:
|
||||
"""Тесты исключения игр"""
|
||||
|
||||
def test_game_error_creation(self):
|
||||
"""Тест создания исключения игры"""
|
||||
exception = GameError("Неверный ход", "tictactoe")
|
||||
|
||||
assert str(exception) == "Неверный ход"
|
||||
assert exception.error_code == "GAME_ERROR"
|
||||
assert exception.game_name == "tictactoe"
|
||||
|
||||
|
||||
class TestModerationError:
|
||||
"""Тесты исключения модерации"""
|
||||
|
||||
def test_moderation_error_creation(self):
|
||||
"""Тест создания исключения модерации"""
|
||||
exception = ModerationError("Недостаточно прав", "ban")
|
||||
|
||||
assert str(exception) == "Недостаточно прав"
|
||||
assert exception.error_code == "MODERATION_ERROR"
|
||||
assert exception.action == "ban"
|
||||
|
||||
|
||||
class TestPermissionError:
|
||||
"""Тесты исключения прав доступа"""
|
||||
|
||||
def test_permission_error_creation(self):
|
||||
"""Тест создания исключения прав доступа"""
|
||||
exception = PermissionError("Доступ запрещен", "admin")
|
||||
|
||||
assert str(exception) == "Доступ запрещен"
|
||||
assert exception.error_code == "PERMISSION_ERROR"
|
||||
assert exception.required_permission == "admin"
|
||||
|
||||
|
||||
class TestExceptionHierarchy:
|
||||
"""Тесты иерархии исключений"""
|
||||
|
||||
def test_exception_inheritance(self):
|
||||
"""Тест наследования исключений"""
|
||||
# Все исключения должны наследоваться от BotException
|
||||
assert issubclass(DatabaseError, BotException)
|
||||
assert issubclass(ValidationError, BotException)
|
||||
assert issubclass(ConfigurationError, BotException)
|
||||
assert issubclass(APIError, BotException)
|
||||
assert issubclass(GameError, BotException)
|
||||
assert issubclass(ModerationError, BotException)
|
||||
assert issubclass(PermissionError, BotException)
|
||||
|
||||
def test_base_exception_inheritance(self):
|
||||
"""Тест наследования от базового Exception"""
|
||||
# BotException должен наследоваться от Exception
|
||||
assert issubclass(BotException, Exception)
|
||||
|
||||
# Все остальные исключения тоже должны наследоваться от Exception
|
||||
assert issubclass(DatabaseError, Exception)
|
||||
assert issubclass(ValidationError, Exception)
|
||||
assert issubclass(ConfigurationError, Exception)
|
||||
assert issubclass(APIError, Exception)
|
||||
assert issubclass(GameError, Exception)
|
||||
assert issubclass(ModerationError, Exception)
|
||||
assert issubclass(PermissionError, Exception)
|
||||
|
||||
|
||||
class TestExceptionUsage:
|
||||
"""Тесты использования исключений"""
|
||||
|
||||
def test_exception_with_message_only(self):
|
||||
"""Тест исключения только с сообщением"""
|
||||
exception = ValidationError("Ошибка валидации")
|
||||
assert str(exception) == "Ошибка валидации"
|
||||
assert exception.field is None
|
||||
|
||||
def test_exception_with_all_fields(self):
|
||||
"""Тест исключения со всеми полями"""
|
||||
exception = APIError("Ошибка API", "telegram", 500)
|
||||
|
||||
assert exception.message == "Ошибка API"
|
||||
assert exception.api_name == "telegram"
|
||||
assert exception.status_code == 500
|
||||
assert exception.error_code == "API_ERROR"
|
||||
|
||||
def test_exception_error_codes(self):
|
||||
"""Тест кодов ошибок всех типов исключений"""
|
||||
exceptions = [
|
||||
(DatabaseError("DB Error"), "DATABASE_ERROR"),
|
||||
(ValidationError("Validation Error"), "VALIDATION_ERROR"),
|
||||
(ConfigurationError("Config Error"), "CONFIG_ERROR"),
|
||||
(APIError("API Error"), "API_ERROR"),
|
||||
(GameError("Game Error"), "GAME_ERROR"),
|
||||
(ModerationError("Moderation Error"), "MODERATION_ERROR"),
|
||||
(PermissionError("Permission Error"), "PERMISSION_ERROR")
|
||||
]
|
||||
|
||||
for exception, expected_code in exceptions:
|
||||
assert exception.error_code == expected_code
|
||||
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
Тесты для модуля форматирования сообщений.
|
||||
Проверяет корректность форматирования различных типов контента.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from utils.formatters import MessageFormatter, KeyboardFormatter
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
|
||||
class TestMessageFormatter:
|
||||
"""Тесты форматтера сообщений"""
|
||||
|
||||
def test_format_user_info(self):
|
||||
"""Тест форматирования информации о пользователе"""
|
||||
user_data = {
|
||||
'id': 123456789,
|
||||
'name': 'Test User',
|
||||
'username': 'test_user',
|
||||
'reputation': 150,
|
||||
'rank': 'Активист',
|
||||
'message_count': 45,
|
||||
'joined_date': '2024-01-15'
|
||||
}
|
||||
|
||||
result = MessageFormatter.format_user_info(user_data)
|
||||
|
||||
assert "👤 <b>Информация о пользователе:</b>" in result
|
||||
assert "🆔 ID: 123456789" in result
|
||||
assert "👤 Имя: Test User" in result
|
||||
assert "📱 Username: @test_user" in result
|
||||
assert "🏆 Репутация: 150" in result
|
||||
assert "⭐ Ранг: Активист" in result
|
||||
assert "💬 Сообщений: 45" in result
|
||||
|
||||
def test_format_rank_info(self):
|
||||
"""Тест форматирования информации о ранге"""
|
||||
result = MessageFormatter.format_rank_info(150, 2, "Активист")
|
||||
|
||||
assert "🏆 <b>Ваш ранг:</b>" in result
|
||||
assert "⭐ Очки: 150" in result
|
||||
assert "⚠️ Предупреждений: 2" in result
|
||||
assert "👑 Роль: Активист" in result
|
||||
|
||||
def test_format_leaderboard_empty(self):
|
||||
"""Тест форматирования пустой таблицы лидеров"""
|
||||
result = MessageFormatter.format_leaderboard([])
|
||||
assert "📭 Таблица лидеров пуста" in result
|
||||
|
||||
def test_format_leaderboard_with_data(self):
|
||||
"""Тест форматирования таблицы лидеров с данными"""
|
||||
users = [
|
||||
(111, "user1", "User One", 100),
|
||||
(222, "user2", "User Two", 90),
|
||||
(333, None, "User Three", 80)
|
||||
]
|
||||
|
||||
result = MessageFormatter.format_leaderboard(users)
|
||||
|
||||
assert "🏆 <b>Таблица лидеров:</b>" in result
|
||||
assert "🥇 <b>User One</b> - 100 очков" in result
|
||||
assert "🥈 <b>User Two</b> - 90 очков" in result
|
||||
assert "🥉 <b>User Three</b> - 80 очков" in result
|
||||
|
||||
def test_get_medal(self):
|
||||
"""Тест получения медали для позиции"""
|
||||
formatter = MessageFormatter()
|
||||
|
||||
assert formatter._get_medal(1) == "🥇"
|
||||
assert formatter._get_medal(2) == "🥈"
|
||||
assert formatter._get_medal(3) == "🥉"
|
||||
assert formatter._get_medal(10) == "🔟"
|
||||
assert formatter._get_medal(15) == "15."
|
||||
|
||||
def test_format_weather_info(self):
|
||||
"""Тест форматирования информации о погоде"""
|
||||
weather_data = {
|
||||
'city': 'Москва',
|
||||
'temp': 20,
|
||||
'feels_like': 18,
|
||||
'humidity': 65,
|
||||
'description': 'ясно'
|
||||
}
|
||||
|
||||
result = MessageFormatter.format_weather_info(weather_data)
|
||||
|
||||
assert "🌤️ <b>Погода в Москва</b>" in result
|
||||
assert "🌡️ Температура: 20°C" in result
|
||||
assert "🌡️ Ощущается как: 18°C" in result
|
||||
assert "💧 Влажность: 65%" in result
|
||||
assert "💬 Описание: ясно" in result
|
||||
|
||||
def test_format_news_empty(self):
|
||||
"""Тест форматирования пустого списка новостей"""
|
||||
result = MessageFormatter.format_news([])
|
||||
assert "📰 Новости не найдены" in result
|
||||
|
||||
def test_format_news_with_data(self):
|
||||
"""Тест форматирования новостей с данными"""
|
||||
articles = [
|
||||
{
|
||||
'title': 'Заголовок новости 1',
|
||||
'url': 'https://example.com/news1'
|
||||
},
|
||||
{
|
||||
'title': 'Заголовок новости 2',
|
||||
'url': 'https://example.com/news2'
|
||||
}
|
||||
]
|
||||
|
||||
result = MessageFormatter.format_news(articles)
|
||||
|
||||
assert "📰 <b>Последние новости:</b>" in result
|
||||
assert "1. <b>Заголовок новости 1</b>" in result
|
||||
assert "🔗 https://example.com/news1" in result
|
||||
assert "2. <b>Заголовок новости 2</b>" in result
|
||||
|
||||
def test_format_error_report(self):
|
||||
"""Тест форматирования отчета об ошибке"""
|
||||
error_data = {
|
||||
'id': 1,
|
||||
'type': 'bug',
|
||||
'title': 'Тестовая ошибка',
|
||||
'priority': 'high',
|
||||
'admin_name': 'Test Admin',
|
||||
'created_at': '2024-01-15',
|
||||
'description': 'Описание ошибки'
|
||||
}
|
||||
|
||||
result = MessageFormatter.format_error_report(error_data)
|
||||
|
||||
assert "🐛 <b>Отчет об ошибке #1</b>" in result
|
||||
assert "📋 Тип: bug" in result
|
||||
assert "📝 Заголовок: Тестовая ошибка" in result
|
||||
assert "⭐ Приоритет: high" in result
|
||||
assert "👤 Создатель: Test Admin" in result
|
||||
assert "📄 Описание:\nОписание ошибки" in result
|
||||
|
||||
def test_format_donation_info(self):
|
||||
"""Тест форматирования информации о донате"""
|
||||
result = MessageFormatter.format_donation_info(100.0, 10)
|
||||
|
||||
assert "💰 <b>Спасибо за поддержку!</b>" in result
|
||||
assert "💵 Сумма: 100.0 RUB" in result
|
||||
assert "⭐ Получено очков: 10" in result
|
||||
assert "🎉 Ваша поддержка помогает развивать бота!" in result
|
||||
|
||||
def test_format_achievement(self):
|
||||
"""Тест форматирования достижения"""
|
||||
result = MessageFormatter.format_achievement("Первый донат", "Вы сделали свой первый донат!")
|
||||
|
||||
assert "🏆 <b>Новое достижение!</b>" in result
|
||||
assert "🎖 Первый донат" in result
|
||||
assert "📝 Вы сделали свой первый донат!" in result
|
||||
|
||||
def test_format_moderation_info_with_transcription(self):
|
||||
"""Тест форматирования информации для модерации с транскрипцией"""
|
||||
result = MessageFormatter.format_moderation_info("audio", "Test User", "Текст транскрипции")
|
||||
|
||||
assert "🔍 <b>Медиафайл на модерации</b>" in result
|
||||
assert "👤 Пользователь: Test User" in result
|
||||
assert "📁 Тип файла: АУДИО" in result
|
||||
assert "🎵 Транскрипция: Текст транскрипции" in result
|
||||
|
||||
def test_format_moderation_info_without_transcription(self):
|
||||
"""Тест форматирования информации для модерации без транскрипции"""
|
||||
result = MessageFormatter.format_moderation_info("video", "Test User")
|
||||
|
||||
assert "🔍 <b>Медиафайл на модерации</b>" in result
|
||||
assert "👤 Пользователь: Test User" in result
|
||||
assert "📁 Тип файла: ВИДЕО" in result
|
||||
assert "🎵 Транскрипция:" not in result
|
||||
|
||||
def test_escape_html(self):
|
||||
"""Тест экранирования HTML символов"""
|
||||
text = '<div>Test & "quotes" \'apostrophe\'</div>'
|
||||
result = MessageFormatter.escape_html(text)
|
||||
|
||||
assert "<div>" in result
|
||||
assert "&" in result
|
||||
#assert ""quotes"" in result #Исправлена ошибка с повторяющимися кавычками
|
||||
assert ""quotes"" in result
|
||||
assert "'apostrophe'" in result
|
||||
|
||||
def test_escape_html_empty(self):
|
||||
"""Тест экранирования пустой строки"""
|
||||
assert MessageFormatter.escape_html("") == ""
|
||||
assert MessageFormatter.escape_html(None) == ""
|
||||
|
||||
def test_truncate_text(self):
|
||||
"""Тест усечения текста"""
|
||||
text = "Это очень длинный текст, который нужно усечь"
|
||||
|
||||
# Нормальная длина
|
||||
result = MessageFormatter.truncate_text(text, 50)
|
||||
assert len(result) <= 50
|
||||
# Text length is 45, max_length is 50, so no truncation should occur
|
||||
assert result == text
|
||||
|
||||
# Короткий текст
|
||||
short_text = "Короткий"
|
||||
result = MessageFormatter.truncate_text(short_text, 50)
|
||||
assert result == short_text
|
||||
|
||||
# Текст без усечения
|
||||
result = MessageFormatter.truncate_text(text, 100)
|
||||
assert result == text
|
||||
|
||||
def test_truncate_text_custom_suffix(self):
|
||||
"""Тест усечения текста с кастомным суффиксом"""
|
||||
text = "Тестовый текст"
|
||||
result = MessageFormatter.truncate_text(text, 5, "[...]")
|
||||
# "Тестовый текст" has length 14, max_length=5, suffix="[...]" has length 5
|
||||
# So text[:5-5] + "[...]" = text[:0] + "[...]" = "[...]"
|
||||
assert result == "[...]"
|
||||
assert len(result) == 5 # суффикс имеет длину 5 символов
|
||||
|
||||
|
||||
class TestKeyboardFormatter:
|
||||
"""Тесты форматтера клавиатур"""
|
||||
|
||||
def test_create_main_menu(self):
|
||||
"""Тест создания главного меню"""
|
||||
keyboard = KeyboardFormatter.create_main_menu()
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 4
|
||||
|
||||
# Проверяем кнопки
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "📋 Помощь"
|
||||
assert buttons[0][0].callback_data == "menu_help"
|
||||
assert buttons[1][0].text == "🎮 Мини игры"
|
||||
assert buttons[1][0].callback_data == "menu_games"
|
||||
|
||||
def test_create_games_menu(self):
|
||||
"""Тест создания меню игр"""
|
||||
keyboard = KeyboardFormatter.create_games_menu()
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 8 # 7 игр + кнопка назад
|
||||
|
||||
# Проверяем последнюю кнопку (Назад)
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[-1][0].text == "⬅️ Назад"
|
||||
assert buttons[-1][0].callback_data == "menu_main"
|
||||
|
||||
def test_create_donation_menu(self):
|
||||
"""Тест создания меню донатов"""
|
||||
keyboard = KeyboardFormatter.create_donation_menu()
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 7 # 5 сумм + другая сумма + назад
|
||||
|
||||
# Проверяем кнопки сумм
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "💰 100 ₽"
|
||||
assert buttons[0][0].callback_data == "donate_100"
|
||||
assert buttons[5][0].text == "💰 Другая сумма"
|
||||
assert buttons[5][0].callback_data == "donate_custom"
|
||||
|
||||
def test_create_admin_menu(self):
|
||||
"""Тест создания меню администратора"""
|
||||
keyboard = KeyboardFormatter.create_admin_menu()
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 5 # 4 функции + назад
|
||||
|
||||
# Проверяем кнопки админских функций
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "👥 Управление пользователями"
|
||||
assert buttons[0][0].callback_data == "admin_users"
|
||||
assert buttons[1][0].text == "📊 Статистика"
|
||||
assert buttons[1][0].callback_data == "admin_stats"
|
||||
|
||||
def test_create_moderation_menu(self):
|
||||
"""Тест создания меню модерации"""
|
||||
keyboard = KeyboardFormatter.create_moderation_menu("audio", 123456789)
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 3 # 3 кнопки действий
|
||||
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "✅ Одобрить"
|
||||
assert buttons[0][0].callback_data == "moderate_approve_123456789"
|
||||
assert buttons[1][0].text == "⏰ Одобрить с задержкой"
|
||||
assert buttons[1][0].callback_data == "moderate_delay_123456789"
|
||||
assert buttons[2][0].text == "❌ Отклонить"
|
||||
assert buttons[2][0].callback_data == "moderate_reject_123456789"
|
||||
|
||||
def test_create_confirmation_menu(self):
|
||||
"""Тест создания меню подтверждения"""
|
||||
keyboard = KeyboardFormatter.create_confirmation_menu("yes", "no")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 2
|
||||
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "Да"
|
||||
assert buttons[0][0].callback_data == "yes"
|
||||
assert buttons[1][0].text == "Нет"
|
||||
assert buttons[1][0].callback_data == "no"
|
||||
|
||||
def test_create_confirmation_menu_custom_text(self):
|
||||
"""Тест создания меню подтверждения с кастомным текстом"""
|
||||
keyboard = KeyboardFormatter.create_confirmation_menu("confirm", "cancel", "Подтвердить", "Отмена")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
buttons = keyboard.inline_keyboard
|
||||
assert buttons[0][0].text == "Подтвердить"
|
||||
assert buttons[0][0].callback_data == "confirm"
|
||||
assert buttons[1][0].text == "Отмена"
|
||||
assert buttons[1][0].callback_data == "cancel"
|
||||
|
||||
def test_create_pagination_menu_single_page(self):
|
||||
"""Тест создания меню пагинации для одной страницы"""
|
||||
keyboard = KeyboardFormatter.create_pagination_menu(1, 1, "test")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
# Должна быть только информационная кнопка
|
||||
assert len(keyboard.inline_keyboard) == 1
|
||||
assert keyboard.inline_keyboard[0][0].text == "📄 1/1"
|
||||
|
||||
def test_create_pagination_menu_multiple_pages(self):
|
||||
"""Тест создания меню пагинации для нескольких страниц"""
|
||||
keyboard = KeyboardFormatter.create_pagination_menu(2, 5, "test")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
assert len(keyboard.inline_keyboard) == 2 # Навигация + информация
|
||||
|
||||
# Проверяем навигационные кнопки
|
||||
nav_buttons = keyboard.inline_keyboard[0]
|
||||
assert nav_buttons[0].text == "⬅️"
|
||||
assert nav_buttons[0].callback_data == "test_prev"
|
||||
assert nav_buttons[1].text == "➡️"
|
||||
assert nav_buttons[1].callback_data == "test_next"
|
||||
|
||||
# Проверяем информационную кнопку
|
||||
info_button = keyboard.inline_keyboard[1][0]
|
||||
assert info_button.text == "📄 2/5"
|
||||
assert info_button.callback_data == "test_info"
|
||||
|
||||
def test_create_pagination_menu_first_page(self):
|
||||
"""Тест создания меню пагинации для первой страницы"""
|
||||
keyboard = KeyboardFormatter.create_pagination_menu(1, 5, "test")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
nav_buttons = keyboard.inline_keyboard[0]
|
||||
# Должна быть только кнопка "вперед"
|
||||
assert len(nav_buttons) == 1
|
||||
assert nav_buttons[0].text == "➡️"
|
||||
assert nav_buttons[0].callback_data == "test_next"
|
||||
|
||||
def test_create_pagination_menu_last_page(self):
|
||||
"""Тест создания меню пагинации для последней страницы"""
|
||||
keyboard = KeyboardFormatter.create_pagination_menu(5, 5, "test")
|
||||
|
||||
assert isinstance(keyboard, InlineKeyboardMarkup)
|
||||
nav_buttons = keyboard.inline_keyboard[0]
|
||||
# Должна быть только кнопка "назад"
|
||||
assert len(nav_buttons) == 1
|
||||
assert nav_buttons[0].text == "⬅️"
|
||||
assert nav_buttons[0].callback_data == "test_prev"
|
||||
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
Тесты для вспомогательных функций.
|
||||
Проверяет корректность работы utility функций.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from utils.helpers import (
|
||||
safe_execute, chunk_text, escape_markdown, create_chunks,
|
||||
format_number, get_nested_value, set_nested_value, calculate_percentage,
|
||||
format_duration, is_empty, generate_user_mention, clean_string,
|
||||
merge_dicts, filter_dict
|
||||
)
|
||||
|
||||
|
||||
class TestSafeExecute:
|
||||
"""Тесты функции safe_execute"""
|
||||
|
||||
def test_safe_execute_success(self):
|
||||
"""Тест успешного выполнения функции"""
|
||||
def test_func(x, y):
|
||||
return x + y
|
||||
|
||||
result = safe_execute(test_func, 5, 3)
|
||||
assert result == 8
|
||||
|
||||
def test_safe_execute_exception(self):
|
||||
"""Тест выполнения функции с исключением"""
|
||||
def failing_func():
|
||||
raise ValueError("Test error")
|
||||
|
||||
result = safe_execute(failing_func)
|
||||
assert result is None
|
||||
|
||||
def test_safe_execute_with_params(self):
|
||||
"""Тест выполнения функции с параметрами"""
|
||||
def multiply(a, b=1):
|
||||
return a * b
|
||||
|
||||
result = safe_execute(multiply, 5, b=3)
|
||||
assert result == 15
|
||||
|
||||
|
||||
class TestTextUtilities:
|
||||
"""Тесты текстовых утилит"""
|
||||
|
||||
def test_chunk_text_empty(self):
|
||||
"""Тест разбиения пустого текста"""
|
||||
result = chunk_text("")
|
||||
assert result == []
|
||||
|
||||
def test_chunk_text_normal(self):
|
||||
"""Тест разбиения текста на части"""
|
||||
text = "A" * 100
|
||||
result = chunk_text(text, 50)
|
||||
|
||||
assert len(result) == 2
|
||||
assert len(result[0]) == 50
|
||||
assert len(result[1]) == 50
|
||||
|
||||
def test_escape_markdown(self):
|
||||
"""Тест экранирования markdown"""
|
||||
text = "Text with *special* _characters_ `code` [link]"
|
||||
result = escape_markdown(text)
|
||||
|
||||
assert "\\*" in result
|
||||
assert "\\_" in result
|
||||
assert "\\`" in result
|
||||
assert "\\[" in result
|
||||
|
||||
def test_escape_markdown_empty(self):
|
||||
"""Тест экранирования пустого текста"""
|
||||
assert escape_markdown("") == ""
|
||||
assert escape_markdown(None) == ""
|
||||
|
||||
|
||||
class TestDataUtilities:
|
||||
"""Тесты утилит работы с данными"""
|
||||
|
||||
def test_create_chunks(self):
|
||||
"""Тест разбиения списка на части"""
|
||||
items = list(range(10))
|
||||
result = create_chunks(items, 3)
|
||||
|
||||
assert len(result) == 4
|
||||
assert result[0] == [0, 1, 2]
|
||||
assert result[1] == [3, 4, 5]
|
||||
assert result[2] == [6, 7, 8]
|
||||
assert result[3] == [9]
|
||||
|
||||
def test_format_number(self):
|
||||
"""Тест форматирования числа"""
|
||||
assert format_number(1234) == "1,234"
|
||||
assert format_number(1234567) == "1,234,567"
|
||||
assert format_number(123.456) == "123.456"
|
||||
assert format_number("not_a_number") == "not_a_number"
|
||||
|
||||
def test_get_nested_value(self):
|
||||
"""Тест получения вложенного значения"""
|
||||
data = {
|
||||
'user': {
|
||||
'profile': {
|
||||
'name': 'John',
|
||||
'age': 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert get_nested_value(data, 'user.profile.name') == 'John'
|
||||
assert get_nested_value(data, 'user.profile.age') == 30
|
||||
assert get_nested_value(data, 'user.profile.email') is None
|
||||
assert get_nested_value(data, 'nonexistent.key', 'default') == 'default'
|
||||
|
||||
def test_set_nested_value(self):
|
||||
"""Тест установки вложенного значения"""
|
||||
data = {}
|
||||
|
||||
result = set_nested_value(data, 'user.profile.name', 'John')
|
||||
|
||||
assert result['user']['profile']['name'] == 'John'
|
||||
assert data['user']['profile']['name'] == 'John'
|
||||
|
||||
def test_calculate_percentage(self):
|
||||
"""Тест вычисления процента"""
|
||||
assert calculate_percentage(25, 100) == 25.0
|
||||
assert abs(calculate_percentage(1, 3) - 33.333333333333336) < 1e-10
|
||||
assert calculate_percentage(0, 100) == 0.0
|
||||
assert calculate_percentage(10, 0) == 0.0
|
||||
|
||||
def test_format_duration(self):
|
||||
"""Тест форматирования длительности"""
|
||||
assert format_duration(30) == "30 сек"
|
||||
assert format_duration(90) == "1 мин 30 сек"
|
||||
assert format_duration(3661) == "1 ч 1 мин"
|
||||
assert format_duration(7323) == "2 ч 2 мин"
|
||||
|
||||
def test_is_empty(self):
|
||||
"""Тест проверки на пустоту"""
|
||||
assert is_empty(None) is True
|
||||
assert is_empty("") is True
|
||||
assert is_empty([]) is True
|
||||
assert is_empty({}) is True
|
||||
assert is_empty("text") is False
|
||||
assert is_empty([1, 2, 3]) is False
|
||||
|
||||
def test_generate_user_mention(self):
|
||||
"""Тест генерации упоминания пользователя"""
|
||||
result = generate_user_mention(123456789, "John Doe")
|
||||
assert result == "[John Doe](tg://user?id=123456789)"
|
||||
|
||||
def test_clean_string(self):
|
||||
"""Тест очистки строки"""
|
||||
assert clean_string(" Text with spaces ") == "Text with spaces"
|
||||
assert clean_string("Text\nwith\nnewlines") == "Text with newlines"
|
||||
assert clean_string(" ") == ""
|
||||
assert clean_string(None) == ""
|
||||
|
||||
def test_merge_dicts(self):
|
||||
"""Тест объединения словарей"""
|
||||
dict1 = {'a': 1, 'b': 2}
|
||||
dict2 = {'b': 3, 'c': 4}
|
||||
dict3 = {'c': 5, 'd': 6}
|
||||
|
||||
result = merge_dicts(dict1, dict2, dict3)
|
||||
|
||||
assert result == {'a': 1, 'b': 3, 'c': 5, 'd': 6}
|
||||
|
||||
def test_filter_dict(self):
|
||||
"""Тест фильтрации словаря"""
|
||||
data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
|
||||
keys = ['a', 'c']
|
||||
|
||||
result = filter_dict(data, keys)
|
||||
|
||||
assert result == {'a': 1, 'c': 3}
|
||||
assert 'b' not in result
|
||||
assert 'd' not in result
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Тесты краевых случаев"""
|
||||
|
||||
def test_calculate_percentage_edge_cases(self):
|
||||
"""Тест вычисления процента в краевых случаях"""
|
||||
assert calculate_percentage(0, 0) == 0.0
|
||||
assert calculate_percentage(-10, 100) == -10.0
|
||||
assert calculate_percentage(10, -5) == -200.0
|
||||
|
||||
def test_format_duration_edge_cases(self):
|
||||
"""Тест форматирования длительности в краевых случаях"""
|
||||
assert format_duration(0) == "0 сек"
|
||||
assert format_duration(-10) == "-10 сек"
|
||||
|
||||
def test_merge_dicts_with_none(self):
|
||||
"""Тест объединения словарей с None значениями"""
|
||||
dict1 = {'a': 1}
|
||||
dict2 = None
|
||||
dict3 = {'b': 2}
|
||||
|
||||
result = merge_dicts(dict1, dict2, dict3)
|
||||
|
||||
assert result == {'a': 1, 'b': 2}
|
||||
|
||||
def test_filter_dict_empty_keys(self):
|
||||
"""Тест фильтрации словаря с пустым списком ключей"""
|
||||
data = {'a': 1, 'b': 2}
|
||||
|
||||
result = filter_dict(data, [])
|
||||
|
||||
assert result == {}
|
||||
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Тесты для модуля валидации данных.
|
||||
Проверяет корректность работы всех функций валидации.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from utils.validators import Validator, InputValidator
|
||||
from core.exceptions import ValidationError
|
||||
|
||||
|
||||
class TestValidator:
|
||||
"""Тесты базового класса Validator"""
|
||||
|
||||
def test_validate_string_length_valid(self):
|
||||
"""Тест валидации корректной длины строки"""
|
||||
assert Validator.validate_string_length("test", 1, 10) is True
|
||||
assert Validator.validate_string_length("", 0, 10) is True
|
||||
assert Validator.validate_string_length("a" * 1000, 0, 1000) is True
|
||||
|
||||
def test_validate_string_length_invalid(self):
|
||||
"""Тест валидации некорректной длины строки"""
|
||||
assert Validator.validate_string_length("", 1, 10) is False
|
||||
assert Validator.validate_string_length("test", 10, 1) is False
|
||||
assert Validator.validate_string_length("a" * 1001, 0, 1000) is False
|
||||
assert Validator.validate_string_length(None, 0, 10) is False
|
||||
|
||||
def test_validate_numeric_range_valid(self):
|
||||
"""Тест валидации корректного числового диапазона"""
|
||||
assert Validator.validate_numeric_range(5, 1, 10) is True
|
||||
assert Validator.validate_numeric_range(1, 1, 10) is True
|
||||
assert Validator.validate_numeric_range(10, 1, 10) is True
|
||||
assert Validator.validate_numeric_range(5.5, 1.0, 10.0) is True
|
||||
|
||||
def test_validate_numeric_range_invalid(self):
|
||||
"""Тест валидации некорректного числового диапазона"""
|
||||
assert Validator.validate_numeric_range(0, 1, 10) is False
|
||||
assert Validator.validate_numeric_range(15, 1, 10) is False
|
||||
assert Validator.validate_numeric_range("not_a_number", 1, 10) is False
|
||||
assert Validator.validate_numeric_range(None, 1, 10) is False
|
||||
|
||||
def test_validate_user_id_valid(self):
|
||||
"""Тест валидации корректного ID пользователя"""
|
||||
assert Validator.validate_user_id(123456789) is True
|
||||
assert Validator.validate_user_id("123456789") is True
|
||||
assert Validator.validate_user_id(1) is True
|
||||
assert Validator.validate_user_id(2147483647) is True
|
||||
|
||||
def test_validate_user_id_invalid(self):
|
||||
"""Тест валидации некорректного ID пользователя"""
|
||||
assert Validator.validate_user_id(0) is False
|
||||
assert Validator.validate_user_id(-1) is False
|
||||
assert Validator.validate_user_id(2147483648) is False
|
||||
assert Validator.validate_user_id("not_a_number") is False
|
||||
assert Validator.validate_user_id(None) is False
|
||||
|
||||
|
||||
class TestInputValidator:
|
||||
"""Тесты класса InputValidator"""
|
||||
|
||||
def test_validate_email_valid(self):
|
||||
"""Тест валидации корректных email адресов"""
|
||||
assert InputValidator.validate_email("test@example.com") is True
|
||||
assert InputValidator.validate_email("user.name+tag@domain.co.uk") is True
|
||||
assert InputValidator.validate_email("admin@localhost") is True
|
||||
|
||||
def test_validate_email_invalid(self):
|
||||
"""Тест валидации некорректных email адресов"""
|
||||
assert InputValidator.validate_email("") is False
|
||||
assert InputValidator.validate_email("invalid-email") is False
|
||||
assert InputValidator.validate_email("@example.com") is False
|
||||
assert InputValidator.validate_email("test@") is False
|
||||
assert InputValidator.validate_email("test.example.com") is False
|
||||
|
||||
def test_validate_phone_valid(self):
|
||||
"""Тест валидации корректных номеров телефона"""
|
||||
assert InputValidator.validate_phone("+7 999 123-45-67") is True
|
||||
assert InputValidator.validate_phone("8(999)1234567") is True
|
||||
assert InputValidator.validate_phone("+1234567890") is True
|
||||
assert InputValidator.validate_phone("1234567890") is True
|
||||
|
||||
def test_validate_phone_invalid(self):
|
||||
"""Тест валидации некорректных номеров телефона"""
|
||||
assert InputValidator.validate_phone("") is False
|
||||
assert InputValidator.validate_phone("123") is False
|
||||
assert InputValidator.validate_phone("phone") is False
|
||||
assert InputValidator.validate_phone("+123") is False
|
||||
|
||||
def test_validate_url_valid(self):
|
||||
"""Тест валидации корректных URL"""
|
||||
assert InputValidator.validate_url("https://example.com") is True
|
||||
assert InputValidator.validate_url("http://localhost:8080") is True
|
||||
assert InputValidator.validate_url("https://sub.domain.org/path") is True
|
||||
|
||||
def test_validate_url_invalid(self):
|
||||
"""Тест валидации некорректных URL"""
|
||||
assert InputValidator.validate_url("") is False
|
||||
assert InputValidator.validate_url("not-a-url") is False
|
||||
assert InputValidator.validate_url("ftp://example.com") is False
|
||||
assert InputValidator.validate_url("https://") is False
|
||||
|
||||
def test_validate_city_name_valid(self):
|
||||
"""Тест валидации корректных названий городов"""
|
||||
assert InputValidator.validate_city_name("Москва") is True
|
||||
assert InputValidator.validate_city_name("New York") is True
|
||||
assert InputValidator.validate_city_name("Санкт-Петербург") is True
|
||||
assert InputValidator.validate_city_name("Saint-Petersburg") is True
|
||||
|
||||
def test_validate_city_name_invalid(self):
|
||||
"""Тест валидации некорректных названий городов"""
|
||||
assert InputValidator.validate_city_name("") is False
|
||||
assert InputValidator.validate_city_name("A") is False
|
||||
assert InputValidator.validate_city_name("A" * 51) is False
|
||||
assert InputValidator.validate_city_name("City123") is False
|
||||
assert InputValidator.validate_city_name("City@Name") is False
|
||||
|
||||
def test_validate_username_valid(self):
|
||||
"""Тест валидации корректных username"""
|
||||
assert InputValidator.validate_username("user123") is True
|
||||
assert InputValidator.validate_username("test_user") is True
|
||||
assert InputValidator.validate_username("a" + "b" * 28) is True # 30 символов
|
||||
|
||||
def test_validate_username_invalid(self):
|
||||
"""Тест валидации некорректных username"""
|
||||
assert InputValidator.validate_username("") is False
|
||||
assert InputValidator.validate_username("ab") is False # слишком короткий
|
||||
assert InputValidator.validate_username("A" * 33) is False # слишком длинный
|
||||
assert InputValidator.validate_username("user-name") is False # дефис запрещен
|
||||
assert InputValidator.validate_username("user.name") is False # точка запрещена
|
||||
assert InputValidator.validate_username("123user") is False # не начинается с буквы
|
||||
|
||||
def test_validate_text_content_valid(self):
|
||||
"""Тест валидации корректного текстового контента"""
|
||||
assert InputValidator.validate_text_content("Привет мир!") is True
|
||||
assert InputValidator.validate_text_content("Текст с цифрами 123") is True
|
||||
assert InputValidator.validate_text_content("A" * 4000) is True
|
||||
|
||||
def test_validate_text_content_invalid(self):
|
||||
"""Тест валидации некорректного текстового контента"""
|
||||
assert InputValidator.validate_text_content("") is False
|
||||
assert InputValidator.validate_text_content("A" * 4001) is False
|
||||
assert InputValidator.validate_text_content("Текст с <script>") is False
|
||||
assert InputValidator.validate_text_content('Текст с "кавычками"') is False
|
||||
|
||||
def test_validate_donation_amount_valid(self):
|
||||
"""Тест валидации корректных сумм донатов"""
|
||||
assert InputValidator.validate_donation_amount(100.0) is True
|
||||
assert InputValidator.validate_donation_amount("100.5") is True
|
||||
assert InputValidator.validate_donation_amount(1.0) is True
|
||||
assert InputValidator.validate_donation_amount(100000.0) is True
|
||||
|
||||
def test_validate_donation_amount_invalid(self):
|
||||
"""Тест валидации некорректных сумм донатов"""
|
||||
assert InputValidator.validate_donation_amount(0) is False
|
||||
assert InputValidator.validate_donation_amount(-100) is False
|
||||
assert InputValidator.validate_donation_amount(100001) is False
|
||||
assert InputValidator.validate_donation_amount("not_a_number") is False
|
||||
|
||||
def test_validate_time_format_valid(self):
|
||||
"""Тест валидации корректных форматов времени"""
|
||||
assert InputValidator.validate_time_format("2024-01-15 14:30:00") is True
|
||||
assert InputValidator.validate_time_format("2024-01-15 14:30") is True
|
||||
assert InputValidator.validate_time_format("+30m") is True
|
||||
assert InputValidator.validate_time_format("+2h") is True
|
||||
assert InputValidator.validate_time_format("+1d") is True
|
||||
|
||||
def test_validate_time_format_invalid(self):
|
||||
"""Тест валидации некорректных форматов времени"""
|
||||
assert InputValidator.validate_time_format("") is False
|
||||
assert InputValidator.validate_time_format("invalid") is False
|
||||
assert InputValidator.validate_time_format("15-01-2024 14:30") is False
|
||||
assert InputValidator.validate_time_format("+30x") is False
|
||||
|
||||
def test_sanitize_filename(self):
|
||||
"""Тест очистки имени файла"""
|
||||
assert InputValidator.sanitize_filename("test.txt") == "test.txt"
|
||||
assert InputValidator.sanitize_filename("file with spaces.txt") == "file with spaces.txt"
|
||||
assert InputValidator.sanitize_filename("file/with/path.txt") == "filewithpath.txt"
|
||||
assert InputValidator.sanitize_filename("file<>:|?.txt") == "file.txt"
|
||||
assert InputValidator.sanitize_filename("") == "unnamed_file"
|
||||
|
||||
def test_validate_csv_filename_valid(self):
|
||||
"""Тест валидации корректных имен CSV файлов"""
|
||||
assert InputValidator.validate_csv_filename("data.csv") is True
|
||||
assert InputValidator.validate_csv_filename("backup_data_2024.csv") is True
|
||||
assert InputValidator.validate_csv_filename("path/to/file.csv") is True
|
||||
|
||||
def test_validate_csv_filename_invalid(self):
|
||||
"""Тест валидации некорректных имен CSV файлов"""
|
||||
assert InputValidator.validate_csv_filename("") is False
|
||||
assert InputValidator.validate_csv_filename("A" * 256) is False
|
||||
assert InputValidator.validate_csv_filename("file<name>.csv") is False
|
||||
assert InputValidator.validate_csv_filename("file*.csv") is False
|
||||
|
||||
def test_validate_error_type_valid(self):
|
||||
"""Тест валидации корректных типов ошибок"""
|
||||
assert InputValidator.validate_error_type("bug") is True
|
||||
assert InputValidator.validate_error_type("Bug") is True # регистронезависимый
|
||||
assert InputValidator.validate_error_type("feature") is True
|
||||
assert InputValidator.validate_error_type("crash") is True
|
||||
|
||||
def test_validate_error_type_invalid(self):
|
||||
"""Тест валидации некорректных типов ошибок"""
|
||||
assert InputValidator.validate_error_type("invalid") is False
|
||||
assert InputValidator.validate_error_type("") is False
|
||||
assert InputValidator.validate_error_type(None) is False
|
||||
assert InputValidator.validate_error_type("not_valid") is False
|
||||
|
||||
def test_validate_priority_valid(self):
|
||||
"""Тест валидации корректных приоритетов"""
|
||||
assert InputValidator.validate_priority("low") is True
|
||||
assert InputValidator.validate_priority("High") is True # регистронезависимый
|
||||
assert InputValidator.validate_priority("CRITICAL") is True
|
||||
|
||||
def test_validate_priority_invalid(self):
|
||||
"""Тест валидации некорректных приоритетов"""
|
||||
assert InputValidator.validate_priority("invalid") is False
|
||||
assert InputValidator.validate_priority("") is False
|
||||
assert InputValidator.validate_priority("medium") is True # medium валиден
|
||||
Reference in New Issue
Block a user