Files
bottohelp/test_integration/test_game_logic_integration.py
T

569 lines
24 KiB
Python

"""
Комплексные интеграционные тесты для игровой логики.
Тестирование всех игр: камень-ножницы-бумага, крестики-нолики, викторина, морской бой, 2048, тетрис, змейка.
Проверяет взаимодействие: handlers -> game_service -> repositories.
"""
import pytest
import tempfile
import os
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime
# Добавляем корневую директорию в путь
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.config import Config
from services.game_service import GameService
from services.user_service import UserService
from handlers.game_handlers import GameHandlers
from database.repository import UserRepository, ScoreRepository
class TestGameLogicIntegration:
"""Комплексные интеграционные тесты игровой логики"""
@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]
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 = "test_user"
user.first_name = "Test"
user.last_name = "User"
update.effective_user = user
# Мокаем сообщение
message = Mock()
message.message_id = 1
message.reply_text = AsyncMock()
message.edit_text = AsyncMock()
update.message = message
# Мокаем чат
chat = Mock()
chat.id = -1001234567890
update.effective_chat = chat
# Мокаем callback_query
callback_query = Mock()
callback_query.id = "test_callback_id"
callback_query.data = "test_data"
callback_query.message = message
callback_query.answer = AsyncMock()
update.callback_query = callback_query
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_rock_paper_scissors_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест камень-ножницы-бумага: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем игру камень-ножницы-бумага
await game_handlers._handle_play_game(mock_update, mock_context)
# Шаг 3: Выбираем камень (rock)
mock_context.args = ['rock']
await game_handlers.handle_rps_choice(mock_update, mock_context)
# Шаг 4: Проверяем, что игра создана и сыграна
# Проверяем статистику пользователя
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
)
# Проверяем, что счетчик игр увеличился
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert 'rock_paper_scissors' in game_stats or game_stats.get('games_played', 0) >= 0
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_tic_tac_toe_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест крестики-нолики: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем крестики-нолики
await game_handlers._handle_tic_tac_toe(mock_update, mock_context)
# Шаг 3: Делаем ходы (имитируем игру)
# Ход 1: центр
mock_context.args = ['4'] # позиция 4 (0-based индекс)
await game_handlers.handle_tictactoe_move(mock_update, mock_context)
# Ход 2: угол
mock_context.args = ['0']
await game_handlers.handle_tictactoe_move(mock_update, mock_context)
# Ход 3: другой угол
mock_context.args = ['2']
await game_handlers.handle_tictactoe_move(mock_update, mock_context)
# Проверяем, что игра продолжается или завершилась
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_quiz_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест викторины: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем викторину
await game_handlers._handle_quiz(mock_update, mock_context)
# Шаг 3: Отвечаем на вопрос (первый вариант)
mock_context.args = ['0']
await game_handlers.handle_quiz_answer(mock_update, mock_context)
# Проверяем, что ответ обработан
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_battleship_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест морской бой: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем морской бой
await game_handlers._handle_battleship(mock_update, mock_context)
# Шаг 3: Делаем выстрел (случайная позиция)
mock_context.args = ['0', '0'] # row=0, col=0
await game_handlers.handle_battleship_shot(mock_update, mock_context)
# Проверяем, что выстрел обработан
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_2048_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест 2048: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем 2048
await game_handlers._handle_2048(mock_update, mock_context)
# Шаг 3: Делаем ход (вверх)
mock_context.args = ['up']
await game_handlers.handle_2048_move(mock_update, mock_context)
# Проверяем, что ход обработан
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_tetris_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест тетриса: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем тетрис
await game_handlers._handle_tetris(mock_update, mock_context)
# Шаг 3: Делаем движение (влево)
mock_context.args = ['left']
await game_handlers.handle_tetris_move(mock_update, mock_context)
# Проверяем, что движение обработано
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_snake_full_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест змейки: handler -> service -> repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Запускаем змейку
await game_handlers._handle_snake(mock_update, mock_context)
# Шаг 3: Делаем движение (вверх)
mock_context.args = ['up']
await game_handlers.handle_snake_move(mock_update, mock_context)
# Проверяем, что движение обработано
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_game_menu_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Интеграционный тест игрового меню: handler -> service"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Открываем игровое меню
await game_handlers.handle_game_menu(mock_update, mock_context)
# Шаг 3: Проверяем, что меню отображено
mock_update.message.reply_text.assert_called_once()
menu_text = mock_update.message.reply_text.call_args[0][0]
# Проверяем наличие опций игр в меню
assert "🎮" in menu_text or "Игры" in menu_text
assert "Камень" in menu_text or "ножницы" in menu_text
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_game_scoring_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Тест начисления очков за игры: game_service -> score_repository"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
user_service = UserService(user_repo, score_repo)
game_service = GameService(user_repo, score_repo)
try:
# Шаг 1: Создаем пользователя
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
)
initial_score = profile.reputation
# Шаг 2: Имитируем победу в игре (добавляем очки напрямую)
await score_repo.update_score(mock_update.effective_user.id, 10)
# Шаг 3: Проверяем, что очки начислены
updated_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 updated_profile.reputation == initial_score + 10
# Шаг 4: Проверяем статистику игр
game_stats = game_service.get_game_statistics(mock_update.effective_user.id)
assert isinstance(game_stats, dict)
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_game_session_management_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Тест управления игровыми сессиями: создание, получение, завершение"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
game_service = GameService(user_repo, score_repo)
try:
# Шаг 1: Создаем игровую сессию
session = game_service.create_game_session(
"rock_paper_scissors",
mock_update.effective_user.id,
mock_update.effective_chat.id
)
assert session is not None
assert session.game_type == "rock_paper_scissors"
assert session.player_id == mock_update.effective_user.id
# Шаг 2: Получаем сессию
retrieved_session = game_service.get_game_session(session.game_id)
assert retrieved_session is not None
assert retrieved_session.game_id == session.game_id
# Шаг 3: Завершаем сессию
success = game_service.end_game_session(session.game_id)
assert success
# Шаг 4: Проверяем, что сессия завершена
ended_session = game_service.get_game_session(session.game_id)
assert ended_session is None
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_game_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)
game_service = GameService(user_repo, score_repo)
game_handlers = GameHandlers(config, config, game_service, config)
try:
# Шаг 1: Создаем пользователя
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
)
# Шаг 2: Имитируем ошибку в сервисе игр
original_play_rps = game_service.play_rock_paper_scissors
game_service.play_rock_paper_scissors = Mock(side_effect=Exception("Game service error"))
# Шаг 3: Пытаемся сыграть в игру (должна обработать ошибку)
await game_handlers._handle_rock_paper_scissors(mock_update, mock_context)
# Шаг 4: Проверяем, что ошибка обработана
mock_update.message.reply_text.assert_called()
error_message = mock_update.message.reply_text.call_args[0][0]
assert "ошибка" in error_message.lower() or "неожиданная" in error_message.lower()
# Шаг 5: Восстанавливаем оригинальную функцию
game_service.play_rock_paper_scissors = original_play_rps
finally:
user_repo.close()
score_repo.close()
@pytest.mark.asyncio
async def test_game_concurrent_sessions_integration(self, temp_config, temp_db, mock_update, mock_context):
"""Тест одновременных игровых сессий"""
config = Config(temp_config)
user_repo = UserRepository(temp_db)
score_repo = ScoreRepository(temp_db)
game_service = GameService(user_repo, score_repo)
try:
# Шаг 1: Создаем несколько игровых сессий
session1 = game_service.create_game_session("tic_tac_toe", mock_update.effective_user.id, mock_update.effective_chat.id)
session2 = game_service.create_game_session("quiz", mock_update.effective_user.id, mock_update.effective_chat.id)
assert session1 is not None
assert session2 is not None
assert session1.game_id != session2.game_id
assert session1.game_type != session2.game_type
# Шаг 2: Проверяем, что обе сессии существуют
retrieved1 = game_service.get_game_session(session1.game_id)
retrieved2 = game_service.get_game_session(session2.game_id)
assert retrieved1 is not None
assert retrieved2 is not None
# Шаг 3: Завершаем все сессии
game_service.cleanup_old_sessions(max_age_minutes=0)
# Проверяем, что сессии завершены
ended1 = game_service.get_game_session(session1.game_id)
ended2 = game_service.get_game_session(session2.game_id)
assert ended1 is None
assert ended2 is None
finally:
user_repo.close()
score_repo.close()