tests-upgrade
All checks were successful
Deploy on push / deploy (push) Successful in 57m1s

This commit is contained in:
2025-09-25 09:40:12 +03:00
parent 1992434a13
commit ac0111cdb9
17 changed files with 766 additions and 363 deletions

View File

@@ -1,128 +1,119 @@
"""
Конфигурация для тестов
🧪 Общая конфигурация для тестов - DRY принцип
Централизованные настройки, константы и общие фикстуры.
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.routing import Route
from starlette.testclient import TestClient
import pytest
from typing import Dict, Any
# Импортируем все модели чтобы SQLAlchemy знал о них
from orm.author import ( # noqa: F401
Author,
AuthorBookmark,
AuthorFollower,
AuthorRating
)
from orm.collection import ShoutCollection # noqa: F401
from orm.community import Community, CommunityAuthor, CommunityFollower # noqa: F401
from orm.draft import Draft, DraftAuthor, DraftTopic # noqa: F401
from orm.invite import Invite # noqa: F401
from orm.notification import Notification # noqa: F401
from orm.shout import Shout, ShoutReactionsFollower, ShoutTopic # noqa: F401
from orm.topic import Topic, TopicFollower # noqa: F401
# 🔍 DRY: Общие константы для тестов
TEST_USER_IDS = {
"FOLLOWER": 2466,
"TARGET_AUTHOR": 9999,
"ADMIN": 1,
"REGULAR_USER": 100,
}
# Используем in-memory SQLite для тестов
TEST_DB_URL = "sqlite:///:memory:"
TEST_EMAILS = {
"FOLLOWER": "follower@example.com",
"TARGET_AUTHOR": "target@example.com",
"ADMIN": "admin@example.com",
"REGULAR_USER": "user@example.com",
}
TEST_SLUGS = {
"FOLLOWER": "test-follower",
"TARGET_AUTHOR": "test-target-author",
"ADMIN": "test-admin",
"REGULAR_USER": "test-user",
}
# 🔍 YAGNI: Только необходимые OAuth провайдеры для тестов
OAUTH_PROVIDERS = ["google", "github", "facebook"]
# 🔍 DRY: Общие настройки для Redis тестов
REDIS_TEST_CONFIG = {
"host": "127.0.0.1",
"port": 6379,
"db": 0,
"decode_responses": True,
}
# 🔍 DRY: Общие HTTP статусы для тестов
HTTP_STATUS = {
"OK": 200,
"BAD_REQUEST": 400,
"UNAUTHORIZED": 401,
"FORBIDDEN": 403,
"NOT_FOUND": 404,
"REDIRECT": 307,
}
class DatabaseMiddleware(BaseHTTPMiddleware):
"""Middleware для внедрения сессии БД"""
def __init__(self, app, session_maker):
super().__init__(app)
self.session_maker = session_maker
async def dispatch(self, request, call_next):
session = self.session_maker()
request.state.db = session
try:
response = await call_next(request)
finally:
session.close()
return response
@pytest.fixture
def test_user_data() -> Dict[str, Any]:
"""🔍 DRY фикстура с тестовыми данными пользователей"""
return {
"ids": TEST_USER_IDS,
"emails": TEST_EMAILS,
"slugs": TEST_SLUGS,
}
def create_test_app():
"""Create a test Starlette application."""
from importlib import import_module
from ariadne import load_schema_from_path, make_executable_schema
from ariadne.asgi import GraphQL
from starlette.responses import JSONResponse
from storage.db import Base
from storage.schema import resolvers
# Создаем движок и таблицы
engine = create_engine(
TEST_DB_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
echo=False,
)
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
# Создаем фабрику сессий
session_local = sessionmaker(bind=engine)
# Импортируем резолверы для GraphQL
import_module("resolvers")
# Создаем схему GraphQL
schema = make_executable_schema(load_schema_from_path("schema/"), list(resolvers))
# Создаем кастомный GraphQL класс для тестов
class TestGraphQL(GraphQL):
async def get_context_for_request(self, request, data):
"""Переопределяем контекст для тестов"""
context = {
"request": None, # Устанавливаем None для активации тестового режима
"author": None,
"roles": [],
}
# Для тестов, если есть заголовок авторизации, создаем мок пользователя
auth_header = request.headers.get("authorization")
if auth_header and auth_header.startswith("Bearer "):
# Простая мок авторизация для тестов - создаем пользователя с ID 1
context["author"] = {"id": 1, "name": "Test User"}
context["roles"] = ["reader", "author"]
return context
# Создаем GraphQL приложение с кастомным классом
graphql_app = TestGraphQL(schema, debug=True)
async def graphql_handler(request):
"""Простой GraphQL обработчик для тестов"""
try:
return await graphql_app.handle_request(request)
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
# Создаем middleware для сессий
middleware = [Middleware(DatabaseMiddleware, session_maker=session_local)]
# Создаем тестовое приложение с GraphQL маршрутом
app = Starlette(
debug=True,
middleware=middleware,
routes=[
Route("/", graphql_handler, methods=["GET", "POST"]), # Основной GraphQL эндпоинт
Route("/graphql", graphql_handler, methods=["GET", "POST"]), # Альтернативный путь
],
)
return app, session_local
@pytest.fixture
def oauth_test_data() -> Dict[str, Any]:
"""🔍 DRY фикстура с тестовыми данными OAuth"""
return {
"providers": OAUTH_PROVIDERS,
"valid_state": "valid_test_state_123",
"invalid_state": "invalid_state",
"auth_code": "test_auth_code_123",
"access_token": "test_access_token_123",
"redirect_uri": "https://localhost:3000",
"code_verifier": "test_code_verifier_123",
}
def get_test_client():
"""Get a test client with initialized database."""
app, session_local = create_test_app()
return TestClient(app), session_local
@pytest.fixture
def redis_test_config() -> Dict[str, Any]:
"""🔍 DRY фикстура с конфигурацией Redis для тестов"""
return REDIS_TEST_CONFIG.copy()
def skip_test_if_condition(condition: bool, reason: str):
"""🔍 DRY хелпер для условного пропуска тестов"""
if condition:
pytest.skip(reason)
def assert_response_status(response, expected_status: int, message: str = ""):
"""🔍 DRY хелпер для проверки статуса ответа"""
actual_status = getattr(response, 'status_code', None)
assert actual_status == expected_status, f"{message} Expected {expected_status}, got {actual_status}"
def assert_json_contains(response_data: str, expected_keys: list, message: str = ""):
"""🔍 DRY хелпер для проверки содержимого JSON ответа"""
for key in expected_keys:
assert key in response_data, f"{message} Missing key '{key}' in response"
# 🔍 YAGNI: Убираем сложные тестовые сценарии, оставляем простые
SIMPLE_TEST_SCENARIOS = {
"follow_success": {
"action": "follow",
"expected_error": None,
"expected_count": 1,
},
"follow_already_following": {
"action": "follow_duplicate",
"expected_error": "already following",
"expected_count": 1,
},
"unfollow_success": {
"action": "unfollow",
"expected_error": None,
"expected_count": 0,
},
}