mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
394 lines
19 KiB
Python
394 lines
19 KiB
Python
"""
|
|
Комплексные интеграционные тесты для системы уведомлений.
|
|
Тестирование полной цепочки: пользователь → модерация → уведомление
|
|
Проверяет взаимодействие: handlers -> moderation_service -> notification_service -> repositories
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import os
|
|
from unittest.mock import Mock, AsyncMock, patch
|
|
from datetime import datetime, timedelta
|
|
|
|
# Добавляем корневую директорию в путь
|
|
import sys
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from core.config import Config
|
|
from services.user_service import UserService
|
|
from services.moderation_service import ModerationService
|
|
from services.notification_service import NotificationService
|
|
from handlers.moderation_handlers import ModerationHandlers
|
|
from database.repository import UserRepository, ScoreRepository
|
|
|
|
|
|
class TestNotificationsIntegration:
|
|
"""Комплексные интеграционные тесты системы уведомлений"""
|
|
|
|
@pytest.fixture
|
|
def temp_config(self):
|
|
"""Временный файл конфигурации для интеграционных тестов"""
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
|
f.write("""
|
|
BOT_TOKEN = "123456789:integration_test_token"
|
|
ADMIN_IDS = [123456789, 987654321]
|
|
MODERATOR_IDS = [555666777]
|
|
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
|
|
|
|
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 = "admin_user"
|
|
user.first_name = "Admin"
|
|
user.last_name = "User"
|
|
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_notification_chain_user_profanity_moderation(self, temp_config, temp_db, mock_update, mock_context):
|
|
"""Интеграционный тест полной цепочки: пользователь отправляет сообщение с матом → автоматическое предупреждение → уведомление о модерации"""
|
|
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)
|
|
notification_service = NotificationService("123456789:integration_test_token", [123456789, 987654321])
|
|
moderation_handlers = ModerationHandlers(config, config, user_service, moderation_service)
|
|
|
|
try:
|
|
# Шаг 1: Создаем пользователя и добавляем слово в фильтр
|
|
target_user_id = 987654321
|
|
await user_service.get_or_create_user(
|
|
target_user_id, "target_user", "Target", "User"
|
|
)
|
|
|
|
moderation_service.add_profanity_word("плохое_слово")
|
|
|
|
# Шаг 2: Имитируем сообщение с матом через модерацию
|
|
profane_message = "Это сообщение содержит плохое_слово"
|
|
|
|
# Мокаем отправку уведомления о предупреждении
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock) as mock_notify:
|
|
# Шаг 3: Выполняем модерацию сообщения
|
|
await moderation_service.moderate_message(
|
|
target_user_id,
|
|
profane_message,
|
|
mock_update.effective_chat.id
|
|
)
|
|
|
|
# Шаг 4: Проверяем, что уведомление было отправлено
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
assert call_args[0][0] == target_user_id # user_id
|
|
assert "предупреждение" in call_args[0][1].lower() # message содержит предупреждение
|
|
|
|
# Шаг 5: Проверяем, что предупреждение записано в базу
|
|
moderation_stats = await moderation_service.get_moderation_stats()
|
|
assert moderation_stats['total_warnings'] >= 1
|
|
|
|
# Шаг 6: Проверяем историю модерации пользователя
|
|
user_history = await moderation_service.get_user_moderation_history(target_user_id)
|
|
assert len(user_history) >= 1
|
|
assert user_history[0]['action_type'] == 'warn'
|
|
|
|
# Шаг 7: Проверяем обновление профиля пользователя
|
|
updated_profile = await user_service.get_or_create_user(
|
|
target_user_id, "target_user", "Target", "User"
|
|
)
|
|
assert updated_profile.warnings >= 1
|
|
|
|
finally:
|
|
user_repo.close()
|
|
score_repo.close()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notification_chain_moderator_actions(self, temp_config, temp_db, mock_update, mock_context):
|
|
"""Интеграционный тест цепочки уведомлений при действиях модератора"""
|
|
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)
|
|
notification_service = NotificationService("123456789:integration_test_token", [123456789, 987654321])
|
|
moderation_handlers = ModerationHandlers(config, config, user_service, moderation_service)
|
|
|
|
try:
|
|
# Шаг 1: Создаем администратора и целевого пользователя
|
|
admin_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
|
|
)
|
|
|
|
target_user_id = 987654321
|
|
target_profile = await user_service.get_or_create_user(
|
|
target_user_id, "target_user", "Target", "User"
|
|
)
|
|
|
|
# Шаг 2: Тестируем предупреждение с уведомлением
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock) as mock_notify:
|
|
mock_context.args = [str(target_user_id), "Тестовое предупреждение"]
|
|
await moderation_handlers._handle_warn(mock_update, mock_context, mock_context.args)
|
|
|
|
# Проверяем уведомление пользователю
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
assert call_args[0][0] == target_user_id
|
|
assert "предупреждение" in call_args[0][1].lower()
|
|
|
|
# Шаг 3: Тестируем мут с уведомлением
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock) as mock_notify:
|
|
mock_context.args = [str(target_user_id), "Тестовый мут", "60"]
|
|
await moderation_handlers._handle_mute(mock_update, mock_context, mock_context.args)
|
|
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
assert call_args[0][0] == target_user_id
|
|
assert "мут" in call_args[0][1].lower()
|
|
|
|
# Шаг 4: Тестируем бан с уведомлением
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock) as mock_notify:
|
|
mock_context.args = [str(target_user_id), "Тестовый бан"]
|
|
await moderation_handlers._handle_ban(mock_update, mock_context, mock_context.args)
|
|
|
|
mock_notify.assert_called_once()
|
|
call_args = mock_notify.call_args
|
|
assert call_args[0][0] == target_user_id
|
|
assert "бан" in call_args[0][1].lower() or "заблокирован" in call_args[0][1].lower()
|
|
|
|
# Шаг 5: Проверяем состояние пользователя после всех действий
|
|
assert moderation_service.is_user_banned(target_user_id)
|
|
assert moderation_service.is_user_muted(target_user_id)
|
|
|
|
# Шаг 6: Проверяем историю модерации
|
|
user_history = await moderation_service.get_user_moderation_history(target_user_id)
|
|
action_types = [h['action_type'] for h in user_history]
|
|
assert 'warn' in action_types
|
|
assert 'mute' in action_types
|
|
assert 'ban' in action_types
|
|
|
|
finally:
|
|
user_repo.close()
|
|
score_repo.close()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notification_chain_with_real_dependencies(self, temp_config, temp_db, mock_update, mock_context):
|
|
"""Интеграционный тест с реальными зависимостями UserRepository и ScoreRepository"""
|
|
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)
|
|
notification_service = NotificationService("123456789:integration_test_token", [123456789, 987654321])
|
|
moderation_handlers = ModerationHandlers(config, config, user_service, moderation_service)
|
|
|
|
try:
|
|
# Шаг 1: Создаем нескольких пользователей
|
|
users = [
|
|
(123456789, "admin", "Admin", "User"),
|
|
(987654321, "user1", "User", "One"),
|
|
(111222333, "user2", "User", "Two"),
|
|
(444555666, "user3", "User", "Three")
|
|
]
|
|
|
|
for user_id, username, first_name, last_name in users:
|
|
await user_service.get_or_create_user(user_id, username, first_name, last_name)
|
|
|
|
# Шаг 2: Добавляем достижения пользователям через ScoreRepository
|
|
for user_id, _, _, _ in users[1:]: # Пропускаем админа
|
|
score_repo.add_score_transaction(user_id, 100, "Тестовое достижение")
|
|
|
|
# Шаг 3: Выполняем массовую модерацию с уведомлениями
|
|
notification_calls = []
|
|
|
|
def mock_notification(user_id, message, parse_mode=None):
|
|
notification_calls.append((user_id, message))
|
|
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock, side_effect=mock_notification):
|
|
# Предупреждаем всех пользователей
|
|
for user_id, _, _, _ in users[1:]:
|
|
mock_context.args = [str(user_id), f"Массовое предупреждение для {user_id}"]
|
|
await moderation_handlers._handle_warn(mock_update, mock_context, mock_context.args)
|
|
|
|
# Шаг 4: Проверяем, что все уведомления отправлены
|
|
assert len(notification_calls) == 3 # Три пользователя (без админа)
|
|
|
|
for user_id, _, _, _ in users[1:]:
|
|
user_notifications = [call for call in notification_calls if call[0] == user_id]
|
|
assert len(user_notifications) == 1
|
|
assert "предупреждение" in user_notifications[0][1].lower()
|
|
|
|
# Шаг 5: Проверяем, что данные корректно сохранены в репозиториях
|
|
for user_id, _, _, _ in users[1:]:
|
|
# Проверяем профиль через UserService
|
|
profile = await user_service.get_or_create_user(user_id, f"user{user_id}", "User", str(user_id))
|
|
assert profile.warnings >= 1
|
|
|
|
# Проверяем достижения через ScoreRepository
|
|
achievements = score_repo.get_user_achievements(user_id)
|
|
assert len(achievements) >= 1
|
|
|
|
# Шаг 6: Проверяем общую статистику модерации
|
|
stats = await moderation_service.get_moderation_stats()
|
|
assert stats['total_warnings'] >= 3
|
|
|
|
finally:
|
|
user_repo.close()
|
|
score_repo.close()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notification_error_handling_integration(self, temp_config, temp_db, mock_update, mock_context):
|
|
"""Тест обработки ошибок в цепочке уведомлений"""
|
|
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)
|
|
notification_service = NotificationService("invalid_token", [123456789, 987654321])
|
|
moderation_handlers = ModerationHandlers(config, config, user_service, moderation_service)
|
|
|
|
try:
|
|
# Шаг 1: Создаем пользователя
|
|
target_user_id = 987654321
|
|
await user_service.get_or_create_user(
|
|
target_user_id, "target_user", "Target", "User"
|
|
)
|
|
|
|
# Шаг 2: Имитируем ошибку отправки уведомления
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock, side_effect=Exception("Network error")):
|
|
# Шаг 3: Пытаемся выполнить модерацию
|
|
mock_context.args = [str(target_user_id), "Тестовое предупреждение"]
|
|
await moderation_handlers._handle_warn(mock_update, mock_context, mock_context.args)
|
|
|
|
# Шаг 4: Проверяем, что несмотря на ошибку уведомления, модерация прошла
|
|
moderation_stats = await moderation_service.get_moderation_stats()
|
|
assert moderation_stats['total_warnings'] >= 1
|
|
|
|
# Шаг 5: Проверяем, что пользователь получил предупреждение
|
|
updated_profile = await user_service.get_or_create_user(
|
|
target_user_id, "target_user", "Target", "User"
|
|
)
|
|
assert updated_profile.warnings >= 1
|
|
|
|
finally:
|
|
user_repo.close()
|
|
score_repo.close()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_notification_chain_performance(self, temp_config, temp_db, mock_update, mock_context):
|
|
"""Тест производительности цепочки уведомлений"""
|
|
import time
|
|
|
|
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)
|
|
notification_service = NotificationService("123456789:integration_test_token", [123456789, 987654321])
|
|
moderation_handlers = ModerationHandlers(config, config, user_service, moderation_service)
|
|
|
|
try:
|
|
# Шаг 1: Создаем несколько пользователей
|
|
user_ids = []
|
|
for i in range(10):
|
|
user_id = 100000000 + i
|
|
user_ids.append(user_id)
|
|
await user_service.get_or_create_user(
|
|
user_id, f"user{i}", f"User{i}", f"Test{i}"
|
|
)
|
|
|
|
# Шаг 2: Замеряем время массовой отправки уведомлений
|
|
start_time = time.time()
|
|
|
|
with patch.object(notification_service, 'send_custom_notification', new_callable=AsyncMock):
|
|
for user_id in user_ids:
|
|
mock_context.args = [str(user_id), f"Массовое уведомление {user_id}"]
|
|
await moderation_handlers._handle_warn(mock_update, mock_context, mock_context.args)
|
|
|
|
end_time = time.time()
|
|
duration = end_time - start_time
|
|
|
|
# Шаг 3: Проверяем, что время выполнения разумное (менее 5 секунд для 10 пользователей)
|
|
assert duration < 5.0, f"Слишком долгое выполнение: {duration} секунд"
|
|
|
|
# Шаг 4: Проверяем, что все действия выполнены
|
|
stats = await moderation_service.get_moderation_stats()
|
|
assert stats['total_warnings'] >= 10
|
|
|
|
finally:
|
|
user_repo.close()
|
|
score_repo.close() |