diff --git a/pyproject.toml b/pyproject.toml index 46ec4626..9549b05f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dependencies = [ # https://docs.astral.sh/uv/concepts/dependencies/#development-dependencies [dependency-groups] dev = [ - "fakeredis", + "fakeredis[aioredis]", "pytest", "pytest-asyncio", "pytest-cov", @@ -61,7 +61,7 @@ dev = [ ] test = [ - "fakeredis", + "fakeredis[aioredis]", "pytest", "pytest-asyncio", "pytest-cov", diff --git a/tests/conftest.py b/tests/conftest.py index 755018a0..a66575bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,10 +9,46 @@ import requests import subprocess from typing import Optional from unittest.mock import patch +import importlib from orm.base import BaseModel as Base +def force_create_all_tables(engine): + """ + Принудительно создает все таблицы, перезагружая модели если нужно. + Это помогает в CI среде где могут быть проблемы с метаданными. + """ + from sqlalchemy import inspect + import orm + + # Перезагружаем все модули ORM для гарантии актуальности метаданных + importlib.reload(orm.base) + importlib.reload(orm.community) + importlib.reload(orm.author) + importlib.reload(orm.draft) + importlib.reload(orm.shout) + importlib.reload(orm.topic) + importlib.reload(orm.reaction) + importlib.reload(orm.invite) + importlib.reload(orm.notification) + importlib.reload(orm.collection) + importlib.reload(orm.rating) + + # Получаем обновленную Base + from orm.base import BaseModel as Base + + # Создаем все таблицы + Base.metadata.create_all(engine) + + # Проверяем результат + inspector = inspect(engine) + created_tables = inspector.get_table_names() + print(f"🔧 Force created tables: {created_tables}") + + return created_tables + + def get_test_client(): """ Создает и возвращает тестовый клиент для интеграционных тестов. @@ -54,10 +90,24 @@ def test_engine(): Создает тестовый engine для всей сессии тестирования. Использует in-memory SQLite для быстрых тестов. """ - # Импортируем все модели, чтобы они были зарегистрированы + # Принудительно импортируем ВСЕ модели чтобы они были зарегистрированы в Base.metadata + # Это критично для CI среды где импорты могут работать по-разному + import orm.base + import orm.community + import orm.author + import orm.draft + import orm.shout + import orm.topic + import orm.reaction + import orm.invite + import orm.notification + import orm.collection + import orm.rating + + # Явно импортируем классы для гарантии регистрации from orm.base import BaseModel as Base - from orm.community import Community, CommunityAuthor - from orm.author import Author + from orm.community import Community, CommunityAuthor, CommunityFollower + from orm.author import Author, AuthorFollower, AuthorRating, AuthorBookmark from orm.draft import Draft, DraftAuthor, DraftTopic from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutReactionsFollower from orm.topic import Topic, TopicFollower @@ -65,7 +115,6 @@ def test_engine(): from orm.invite import Invite from orm.notification import Notification from orm.collection import Collection - from orm.author import AuthorFollower, AuthorRating # Инициализируем RBAC систему import rbac @@ -89,8 +138,8 @@ def test_engine(): required_tables = [ 'author', 'community', 'community_author', 'community_follower', 'draft', 'draft_author', 'draft_topic', - 'shout', 'shout_author', 'shout_topic', 'shout_reactions_followers', - 'topic', 'topic_followers', 'reaction', 'invite', 'notification', + 'shout', 'shout_author', 'shout_topic', 'shout_reactions_followers', 'shout_collection', + 'topic', 'topic_followers', 'reaction', 'invite', 'notification', 'notification_seen', 'collection', 'author_follower', 'author_rating', 'author_bookmark' ] @@ -113,7 +162,60 @@ def test_engine(): if still_missing: print(f"❌ Still missing tables after explicit creation: {still_missing}") - raise RuntimeError(f"Failed to create required tables: {still_missing}") + # Последняя попытка: создаем таблицы по одной + print("🔄 Last attempt: creating tables one by one...") + for table_name in still_missing: + try: + if table_name == 'community_author': + CommunityAuthor.__table__.create(engine, checkfirst=True) + elif table_name == 'community_follower': + CommunityFollower.__table__.create(engine, checkfirst=True) + elif table_name == 'author_follower': + AuthorFollower.__table__.create(engine, checkfirst=True) + elif table_name == 'author_rating': + AuthorRating.__table__.create(engine, checkfirst=True) + elif table_name == 'author_bookmark': + AuthorBookmark.__table__.create(engine, checkfirst=True) + elif table_name == 'draft_author': + DraftAuthor.__table__.create(engine, checkfirst=True) + elif table_name == 'draft_topic': + DraftTopic.__table__.create(engine, checkfirst=True) + elif table_name == 'shout_author': + ShoutAuthor.__table__.create(engine, checkfirst=True) + elif table_name == 'shout_topic': + ShoutTopic.__table__.create(engine, checkfirst=True) + elif table_name == 'shout_reactions_followers': + ShoutReactionsFollower.__table__.create(engine, checkfirst=True) + elif table_name == 'collection': + Collection.__table__.create(engine, checkfirst=True) + elif table_name == 'topic_followers': + TopicFollower.__table__.create(engine, checkfirst=True) + elif table_name == 'notification_seen': + # notification_seen может быть частью notification модели + pass + print(f"✅ Created table {table_name}") + except Exception as e: + print(f"❌ Failed to create table {table_name}: {e}") + + # Финальная проверка + inspector = inspect(engine) + final_tables = inspector.get_table_names() + final_missing = [table for table in required_tables if table not in final_tables] + if final_missing: + print(f"❌ Still missing tables after individual creation: {final_missing}") + print("🔄 Last resort: forcing table creation with module reload...") + try: + final_tables = force_create_all_tables(engine) + final_missing = [table for table in required_tables if table not in final_tables] + if final_missing: + raise RuntimeError(f"Failed to create required tables after all attempts: {final_missing}") + else: + print("✅ All missing tables created successfully with force creation") + except Exception as e: + print(f"❌ Force creation failed: {e}") + raise RuntimeError(f"Failed to create required tables after all attempts: {final_missing}") + else: + print("✅ All missing tables created successfully in test_engine") else: print("✅ All missing tables created successfully in test_engine") except Exception as e: diff --git a/uv.lock b/uv.lock index 5c40469c..ce74f417 100644 --- a/uv.lock +++ b/uv.lock @@ -479,7 +479,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "fakeredis" }, + { name = "fakeredis", extras = ["aioredis"] }, { name = "mypy" }, { name = "playwright" }, { name = "pytest" }, @@ -493,7 +493,7 @@ lint = [ { name = "ruff" }, ] test = [ - { name = "fakeredis" }, + { name = "fakeredis", extras = ["aioredis"] }, { name = "playwright" }, { name = "pytest" }, { name = "pytest-asyncio" },