Подготовка к релизу

This commit is contained in:
2025-10-30 18:28:26 +03:00
parent 69c0162c6b
commit 4981db90a0
191 changed files with 45557 additions and 3651 deletions
+199
View File
@@ -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
+364
View File
@@ -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 "&quot;quotes&quot;" in result
assert "&#x27;apostrophe&#x27;" 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"
+209
View File
@@ -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 == {}
+218
View File
@@ -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 валиден