mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
569 lines
24 KiB
Python
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() |