""" 🧪 Общие утилиты для тестов - применение DRY принципа Централизованные Mock классы, фикстуры и хелперы для избежания дублирования. """ from typing import Any, Dict, List, Optional import time class MockRequest: """🔍 Универсальный Mock для HTTP request""" def __init__(self, method: str = "POST", path: str = "/graphql", headers: Optional[Dict] = None): self.method = method self.url = type('MockURL', (), {"path": path})() self.headers = headers or {} class MockGraphQLResolveInfo: """🔍 Универсальный Mock для GraphQL resolve info""" def __init__( self, author_id: Optional[int] = None, requested_fields: Optional[List[str]] = None, context_overrides: Optional[Dict] = None ): base_context = { "request": MockRequest(), "author": {"id": author_id, "name": "Test User"} if author_id else None, "roles": ["reader", "author"] if author_id else [], "is_admin": False, } if context_overrides: base_context.update(context_overrides) self.context = base_context self.field_nodes = [MockFieldNode(requested_fields or [])] class MockFieldNode: """🔍 Mock для GraphQL field node""" def __init__(self, requested_fields: List[str]): self.selection_set = MockSelectionSet(requested_fields) class MockSelectionSet: """🔍 Mock для GraphQL selection set""" def __init__(self, requested_fields: List[str]): self.selections = [ type('MockSelection', (), { 'name': type('MockName', (), {'value': field})() })() for field in requested_fields ] def create_test_author( db_session, author_id: int, email: str, name: str = "Test User", slug: Optional[str] = None, password: str = "test_password" ) -> Any: """🔍 DRY хелпер для создания тестового автора""" from orm.author import Author # Очищаем существующие записи db_session.query(Author).where( (Author.id == author_id) | (Author.email == email) ).delete() db_session.commit() author = Author( id=author_id, name=name, email=email, slug=slug or f"test-user-{author_id}", password=password ) db_session.add(author) db_session.commit() return author def create_test_community( db_session, community_id: int, name: str = "Test Community", slug: Optional[str] = None, creator_id: Optional[int] = None ) -> Any: """🔍 DRY хелпер для создания тестового сообщества""" from orm.community import Community # Очищаем существующие записи db_session.query(Community).where(Community.id == community_id).delete() db_session.commit() community = Community( id=community_id, name=name, slug=slug or f"test-community-{community_id}", desc=f"Test community description for {name}", created_at=int(time.time()), creator_id=creator_id ) db_session.add(community) db_session.commit() return community def cleanup_test_data(db_session, *model_id_pairs) -> None: """🔍 DRY хелпер для очистки тестовых данных""" try: for model_class, entity_id in model_id_pairs: db_session.query(model_class).where(model_class.id == entity_id).delete() db_session.commit() except Exception as e: print(f"⚠️ Ошибка при очистке тестовых данных: {e}") db_session.rollback() class TestDataBuilder: """🔍 Builder паттерн для создания тестовых данных - YAGNI подход""" def __init__(self, db_session): self.db_session = db_session self._cleanup_tasks = [] def author(self, author_id: int, email: str, **kwargs) -> 'TestDataBuilder': """Создает автора и добавляет в очередь очистки""" author = create_test_author(self.db_session, author_id, email, **kwargs) self._cleanup_tasks.append(('orm.author.Author', author_id)) return self def community(self, community_id: int, **kwargs) -> 'TestDataBuilder': """Создает сообщество и добавляет в очередь очистки""" community = create_test_community(self.db_session, community_id, **kwargs) self._cleanup_tasks.append(('orm.community.Community', community_id)) return self def cleanup(self) -> None: """Очищает все созданные данные""" for model_path, entity_id in self._cleanup_tasks: try: module_path, class_name = model_path.rsplit('.', 1) module = __import__(module_path, fromlist=[class_name]) model_class = getattr(module, class_name) self.db_session.query(model_class).where(model_class.id == entity_id).delete() except Exception as e: print(f"⚠️ Ошибка очистки {model_path}#{entity_id}: {e}") try: self.db_session.commit() except Exception: self.db_session.rollback() def skip_if_auth_fails(func): """🔍 Декоратор для пропуска тестов при ошибках авторизации - YAGNI""" import pytest from functools import wraps @wraps(func) async def wrapper(*args, **kwargs): try: return await func(*args, **kwargs) except Exception as e: if any(phrase in str(e) for phrase in [ "Авторизация не прошла", "AuthorizationError", "Требуется авторизация" ]): pytest.skip(f"Тест пропущен из-за ошибки авторизации: {e}") raise return wrapper