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

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
@@ -0,0 +1,407 @@
"""
Интеграционный тест для проверки работы системы предупреждений.
Тестирует весь путь: добавление предупреждения → отображение в /info команде.
Проверяет поддержку русского языка, эмодзи и UTF-8 кодировки.
"""
import pytest
import tempfile
import os
from unittest.mock import Mock, AsyncMock
from datetime import datetime
# Добавляем корневую директорию в путь
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.application import Application
from core.config import Config
class TestWarningsIntegration:
"""Интеграционные тесты системы предупреждений с поддержкой UTF-8"""
@pytest.fixture
def temp_config(self):
"""Временный файл конфигурации для интеграционных тестов"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, encoding='utf-8') as f:
f.write("""
BOT_TOKEN = "123456789:integration_test_token"
ADMIN_IDS = [123456789, 987654321]
OPENWEATHER_API_KEY = "test_key"
NEWS_API_KEY = "test_key"
OPENAI_API_KEY = "test_key"
""")
config_path = f.name
yield config_path
# Очистка
if os.path.exists(config_path):
os.unlink(config_path)
@pytest.fixture
def temp_db(self):
"""Временная база данных для тестов с инициализацией таблиц"""
from database.models import DatabaseSchema
from database.repository import UserRepository, ScoreRepository
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
db_path = f.name
# Инициализируем базу данных
repo = UserRepository(db_path)
try:
# Создаем все таблицы
for table_sql in DatabaseSchema.get_create_tables_sql():
repo._execute_query(table_sql)
# Создаем стандартные достижения
repo.initialize_achievements()
finally:
repo.close()
yield db_path
# Очистка
if os.path.exists(db_path):
os.unlink(db_path)
@pytest.fixture
def mock_update(self):
"""Мокированный объект Update для команд"""
update = Mock()
# Мокаем пользователя
user = Mock()
user.id = 123456789
user.username = "test_user"
user.first_name = "Тестовый"
user.last_name = "Пользователь"
update.effective_user = user
# Мокаем сообщение
message = Mock()
message.message_id = 1
message.reply_text = AsyncMock()
update.message = message
# Мокаем чат
chat = Mock()
chat.id = -1001234567890
update.effective_chat = chat
return update
@pytest.fixture
def mock_context(self):
"""Мокированный контекст бота"""
context = Mock()
context.args = []
# Мокаем приложение
app = Mock()
app._date_time = datetime.now()
context._application = app
# Мокаем бота
bot = AsyncMock()
context.bot = bot
context.user_data = {}
return context
@pytest.mark.asyncio
async def test_full_warnings_flow_utf8(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест полного цикла предупреждений с поддержкой UTF-8"""
# Создаем конфигурацию
config = Config(temp_config)
# Создаем репозитории и сервисы
from database.repository import UserRepository, ScoreRepository
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
from services.user_service import UserService
user_service = UserService(user_repo, score_repo)
from services.moderation_service import ModerationService
moderation_service = ModerationService(user_repo, score_repo)
# Создаем обработчик команд
from handlers.user_handlers import UserHandlers
user_handlers = UserHandlers(config, config, user_service)
try:
# Шаг 1: Создаем пользователя
user_profile = await user_service.get_or_create_user(
mock_update.effective_user.id,
mock_update.effective_user.username,
mock_update.effective_user.first_name,
mock_update.effective_user.last_name
)
assert user_profile is not None, "Пользователь должен быть создан"
assert user_profile.warnings == 0, "Изначально предупреждений должно быть 0"
# Шаг 2: Добавляем предупреждение с UTF-8 текстом
test_reasons = [
"Спам в чате 🚫",
"Нецензурная лексика с эмодзи 😡",
"Нарушение правил поведения 📋",
"Русский текст с UTF-8 символами: Привет мир! 🌍",
"Специальные символы: àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
"Кириллица: АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя"
]
admin_id = 987654321 # ID администратора
for i, reason in enumerate(test_reasons):
print(f"[TEST] Добавляем предупреждение {i+1}: '{reason}'")
result = await moderation_service.warn_user(
mock_update.effective_user.id,
reason,
admin_id
)
assert result['success'] == True, f"Предупреждение {i+1} должно быть добавлено успешно"
assert result['warnings_count'] == i + 1, f"Количество предупреждений должно быть {i+1}"
assert result['reason'] == reason, f"Причина предупреждения должна сохраниться: {reason}"
# Проверяем, что предупреждение сохранено в базе данных
current_warnings = user_repo.get_warnings_count(mock_update.effective_user.id)
assert current_warnings == i + 1, f"В базе данных должно быть {i+1} предупреждение(й)"
# Шаг 3: Проверяем отображение в команде /info
await user_handlers.handle_info(mock_update, mock_context)
# Проверяем, что ответ был отправлен
mock_update.message.reply_text.assert_called_once()
# Получаем отправленный текст
call_args = mock_update.message.reply_text.call_args
response_text = call_args[0][0] # Первый позиционный аргумент
# Проверяем содержимое ответа
assert "👤" in response_text, "Ответ должен содержать иконку пользователя"
assert "Информация о пользователе" in response_text, "Ответ должен содержать заголовок"
assert "Тестовый" in response_text, "Должно отображаться имя пользователя"
assert f"⚠️ Предупреждений: {len(test_reasons)}" in response_text, f"Должно отображаться {len(test_reasons)} предупреждений"
# Проверяем, что все UTF-8 символы сохранены корректно
for reason in test_reasons:
# Проверяем, что ключевые части причин присутствуют в ответе
if "Спам в чате" in reason:
assert "Спам" in response_text, "Текст с кириллицей должен отображаться корректно"
if "🚫" in reason:
assert "🚫" in response_text, "Эмодзи должны отображаться корректно"
if "🌍" in reason:
assert "🌍" in response_text, "Эмодзи должны отображаться корректно"
print("[SUCCESS] Все предупреждения корректно сохранены и отображаются в /info")
finally:
# Очищаем ресурсы
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_warnings_with_special_characters(self, temp_config, temp_db):
"""Тест предупреждений с специальными символами UTF-8"""
from database.repository import UserRepository, ScoreRepository
from services.user_service import UserService
from services.moderation_service import ModerationService
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
moderation_service = ModerationService(user_repo, score_repo)
try:
test_user_id = 111111111
admin_id = 222222222
# Создаем пользователя
await user_service.get_or_create_user(test_user_id, "testuser", "Тест", "Юзер")
# Тестируем различные специальные символы
special_reasons = [
"Математические символы: ∑∏√∫∆∞≠≈≤≥",
"Стрелки: ←↑→↓↔⇄⇅",
"Фигурки: ●○■□◆◇★☆♡♢♤♧",
"Валюты: ¢£¤¥€₽₿",
"Дроби: ½⅓¼¾⅕⅙⅛⅔⅖⅚⅜⅗⅘⅙⅚⅛⅜⅝⅞⅟",
"Надстрочные: ⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿ",
"Подстрочные: ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎",
"Акценты: àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
"Греческий: αβγδεζηθικλμνξοπρστυφχψω",
"Китайские иероглифы: 你好世界こんにちは안녕하세요"
]
for reason in special_reasons:
print(f"[TEST] Тестируем специальную строку: '{reason}'")
result = await moderation_service.warn_user(test_user_id, reason, admin_id)
assert result['success'] == True, f"Предупреждение с символами '{reason[:20]}...' должно быть добавлено"
assert result['reason'] == reason, f"Текст предупреждения должен сохраниться без изменений: {reason}"
# Проверяем в базе данных
warnings_count = user_repo.get_warnings_count(test_user_id)
assert warnings_count > 0, "Предупреждение должно быть сохранено в БД"
print("[SUCCESS] Все специальные символы UTF-8 корректно обработаны")
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_warnings_display_in_info_command(self, temp_config, temp_db, mock_update, mock_context):
"""Тест отображения предупреждений в команде /info"""
from database.repository import UserRepository, ScoreRepository
from services.user_service import UserService
from services.moderation_service import ModerationService
from handlers.user_handlers import UserHandlers
from core.config import Config
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
moderation_service = ModerationService(user_repo, score_repo)
user_handlers = UserHandlers(config, config, user_service)
try:
# Создаем пользователя
await user_service.get_or_create_user(
mock_update.effective_user.id,
mock_update.effective_user.username,
mock_update.effective_user.first_name,
mock_update.effective_user.last_name
)
# Добавляем несколько предупреждений с эмодзи
warnings_to_add = [
"Первое предупреждение ⚠️",
"Второе нарушение 🚫",
"Третье замечание 📋"
]
for reason in warnings_to_add:
await moderation_service.warn_user(
mock_update.effective_user.id,
reason,
987654321
)
# Выполняем команду /info
await user_handlers.handle_info(mock_update, mock_context)
# Получаем ответ
call_args = mock_update.message.reply_text.call_args
response_text = call_args[0][0]
# Проверяем отображение количества предупреждений
assert "⚠️ Предупреждений: 3" in response_text, "Должно отображаться правильное количество предупреждений"
# Проверяем, что имя пользователя отображается корректно
assert "Тестовый" in response_text, "Имя пользователя должно отображаться"
print("[SUCCESS] Команда /info корректно отображает предупреждения")
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_warnings_persistence_across_sessions(self, temp_config, temp_db):
"""Тест сохранения предупреждений между сессиями"""
from database.repository import UserRepository, ScoreRepository
from services.user_service import UserService
from services.moderation_service import ModerationService
# Первая "сессия" - добавляем предупреждения
user_repo1 = UserRepository(temp_db)
score_repo1 = ScoreRepository(temp_db)
user_service1 = UserService(user_repo1, score_repo1)
moderation_service1 = ModerationService(user_repo1, score_repo1)
try:
test_user_id = 333333333
admin_id = 444444444
# Создаем пользователя и добавляем предупреждения
await user_service1.get_or_create_user(test_user_id, "persist_test", "Персист", "Тест")
test_reason = "Тест persistence с эмодзи 💾"
await moderation_service1.warn_user(test_user_id, test_reason, admin_id)
initial_count = user_repo1.get_warnings_count(test_user_id)
assert initial_count == 1, "Предупреждение должно быть сохранено"
finally:
user_repo1.close()
score_repo1.close()
# Вторая "сессия" - проверяем, что предупреждения сохранились
user_repo2 = UserRepository(temp_db)
score_repo2 = ScoreRepository(temp_db)
user_service2 = UserService(user_repo2, score_repo2)
try:
# Получаем профиль пользователя
user_profile = await user_service2.get_or_create_user(test_user_id, "persist_test", "Персист", "Тест")
# Проверяем, что предупреждения сохранились
final_count = user_repo2.get_warnings_count(test_user_id)
assert final_count == 1, "Предупреждение должно сохраниться между сессиями"
assert user_profile.warnings == 1, "Количество предупреждений в профиле должно быть корректным"
print("[SUCCESS] Предупреждения корректно сохраняются между сессиями")
finally:
user_repo2.close()
score_repo2.close()
@pytest.mark.asyncio
async def test_multiple_users_warnings_isolation(self, temp_config, temp_db):
"""Тест изоляции предупреждений между пользователями"""
from database.repository import UserRepository, ScoreRepository
from services.user_service import UserService
from services.moderation_service import ModerationService
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
moderation_service = ModerationService(user_repo, score_repo)
try:
# Создаем двух пользователей
user1_id = 555555555
user2_id = 666666666
admin_id = 777777777
await user_service.get_or_create_user(user1_id, "user1", "Пользователь", "Первый")
await user_service.get_or_create_user(user2_id, "user2", "Пользователь", "Второй")
# Добавляем предупреждения первому пользователю
await moderation_service.warn_user(user1_id, "Предупреждение для первого пользователя 👤", admin_id)
await moderation_service.warn_user(user1_id, "Второе предупреждение для первого 🚨", admin_id)
# Добавляем одно предупреждение второму пользователю
await moderation_service.warn_user(user2_id, "Предупреждение для второго пользователя 🎯", admin_id)
# Проверяем изоляцию
user1_warnings = user_repo.get_warnings_count(user1_id)
user2_warnings = user_repo.get_warnings_count(user2_id)
assert user1_warnings == 2, "Первый пользователь должен иметь 2 предупреждения"
assert user2_warnings == 1, "Второй пользователь должен иметь 1 предупреждение"
print("[SUCCESS] Предупреждения пользователей изолированы друг от друга")
finally:
user_repo.close()
score_repo.close()