diff --git a/tests/conftest.py b/tests/conftest.py index 40903789..755018a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -65,7 +65,6 @@ def test_engine(): from orm.invite import Invite from orm.notification import Notification from orm.collection import Collection - from orm.shout import ShoutReactionsFollower from orm.author import AuthorFollower, AuthorRating # Инициализируем RBAC систему @@ -79,6 +78,49 @@ def test_engine(): # Принудительно удаляем все таблицы и создаем заново Base.metadata.drop_all(engine) Base.metadata.create_all(engine) + + # Debug: проверяем какие таблицы созданы на уровне сессии + from sqlalchemy import inspect + inspector = inspect(engine) + session_tables = inspector.get_table_names() + print(f"🔍 Created tables in test_engine fixture: {session_tables}") + + # Проверяем что все критические таблицы созданы + 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', + 'collection', 'author_follower', 'author_rating', 'author_bookmark' + ] + + missing_tables = [table for table in required_tables if table not in session_tables] + + if missing_tables: + print(f"❌ Missing tables in test_engine: {missing_tables}") + print(f"Available tables: {session_tables}") + + # Fallback: попробуем создать отсутствующие таблицы явно + print("🔄 Attempting to create missing tables explicitly in test_engine...") + try: + # Создаем все таблицы снова с принудительным импортом моделей + Base.metadata.create_all(engine) + + # Проверяем снова + inspector = inspect(engine) + updated_tables = inspector.get_table_names() + still_missing = [table for table in required_tables if table not in updated_tables] + + if still_missing: + print(f"❌ Still missing tables after explicit creation: {still_missing}") + raise RuntimeError(f"Failed to create required tables: {still_missing}") + else: + print("✅ All missing tables created successfully in test_engine") + except Exception as e: + print(f"❌ Failed to create missing tables in test_engine: {e}") + raise + else: + print("✅ All required tables created in test_engine") yield engine @@ -103,12 +145,87 @@ def db_session(test_session_factory, test_engine): # Принудительно пересоздаем таблицы для каждого теста from orm.base import BaseModel as Base from sqlalchemy import inspect + + # Убеждаемся что все модели импортированы перед созданием таблиц + # Явно импортируем все модели чтобы они были зарегистрированы в Base.metadata + 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 + from orm.reaction import Reaction + from orm.invite import Invite + from orm.notification import Notification + from orm.collection import Collection + + # Проверяем что все модели зарегистрированы + print(f"🔍 Registered tables in Base.metadata: {list(Base.metadata.tables.keys())}") + + # Убеждаемся что все критические таблицы зарегистрированы в metadata + 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', + 'collection', 'author_follower', 'author_rating', 'author_bookmark' + ] + + missing_metadata_tables = [table for table in required_tables if table not in Base.metadata.tables] + + if missing_metadata_tables: + print(f"❌ Missing tables in Base.metadata: {missing_metadata_tables}") + print("Available tables:", list(Base.metadata.tables.keys())) + raise RuntimeError(f"Critical tables not registered in Base.metadata: {missing_metadata_tables}") + else: + print("✅ All required tables registered in Base.metadata") # Удаляем все таблицы Base.metadata.drop_all(test_engine) # Создаем таблицы заново Base.metadata.create_all(test_engine) + + # Debug: проверяем какие таблицы созданы + inspector = inspect(test_engine) + created_tables = inspector.get_table_names() + print(f"🔍 Created tables in db_session fixture: {created_tables}") + + # Проверяем что все критические таблицы созданы + 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', + 'collection', 'author_follower', 'author_rating', 'author_bookmark' + ] + + missing_tables = [table for table in required_tables if table not in created_tables] + + if missing_tables: + print(f"❌ Missing tables in db_session: {missing_tables}") + print(f"Available tables: {created_tables}") + + # Fallback: попробуем создать отсутствующие таблицы явно + print("🔄 Attempting to create missing tables explicitly in db_session...") + try: + # Создаем все таблицы снова с принудительным импортом моделей + Base.metadata.create_all(test_engine) + + # Проверяем снова + inspector = inspect(test_engine) + updated_tables = inspector.get_table_names() + still_missing = [table for table in required_tables if table not in updated_tables] + + if still_missing: + print(f"❌ Still missing tables after explicit creation: {still_missing}") + raise RuntimeError(f"Failed to create required tables: {still_missing}") + else: + print("✅ All missing tables created successfully in db_session") + except Exception as e: + print(f"❌ Failed to create missing tables in db_session: {e}") + raise + else: + print("✅ All required tables created in db_session") # Проверяем что таблица draft создана с правильной схемой inspector = inspect(test_engine) @@ -582,18 +699,27 @@ def redis_client(): return redis_service._client -# Mock для Redis если он недоступен @pytest.fixture -def mock_redis_if_unavailable(): - """Мокает Redis если он недоступен - для тестов которые нуждаются в Redis""" +def fake_redis(): + """Создает fakeredis экземпляр для тестов""" try: import fakeredis.aioredis - # Используем fakeredis для тестов - with patch('storage.redis.redis') as mock_redis: - # Создаем fakeredis сервер - fake_redis = fakeredis.aioredis.FakeRedis() + return fakeredis.aioredis.FakeRedis() + except ImportError: + pytest.skip("fakeredis не установлен - установите: pip install fakeredis[aioredis]") + + +@pytest.fixture +def redis_service_mock(fake_redis): + """Создает мок RedisService с fakeredis""" + try: + import fakeredis.aioredis + + with patch('storage.redis.RedisService') as mock_service: + # Создаем экземпляр с fakeredis + mock_service.return_value._client = fake_redis - # Создаем mock для execute метода, который эмулирует поведение RedisService.execute + # Эмулируем execute метод async def mock_execute(command: str, *args): cmd_method = getattr(fake_redis, command.lower(), None) if cmd_method is not None: @@ -603,16 +729,66 @@ def mock_redis_if_unavailable(): return cmd_method return None - # Патчим методы Redis + mock_service.return_value.execute = mock_execute + mock_service.return_value.get = fake_redis.get + mock_service.return_value.set = fake_redis.set + mock_service.return_value.delete = fake_redis.delete + mock_service.return_value.exists = fake_redis.exists + mock_service.return_value.hset = fake_redis.hset + mock_service.return_value.hget = fake_redis.hget + mock_service.return_value.hgetall = fake_redis.hgetall + mock_service.return_value.hdel = fake_redis.hdel + mock_service.return_value.expire = fake_redis.expire + mock_service.return_value.ttl = fake_redis.ttl + mock_service.return_value.keys = fake_redis.keys + mock_service.return_value.scan = fake_redis.scan + + yield mock_service.return_value + + except ImportError: + pytest.skip("fakeredis не установлен - установите: pip install fakeredis[aioredis]") + + +# Используем fakeredis для тестов Redis +@pytest.fixture +def mock_redis_if_unavailable(): + """Заменяет Redis на fakeredis для тестов - более реалистичная имитация Redis""" + try: + import fakeredis.aioredis + + # Создаем fakeredis сервер + fake_redis = fakeredis.aioredis.FakeRedis() + + # Патчим глобальный redis экземпляр + with patch('storage.redis.redis') as mock_redis: + # Эмулируем RedisService.execute метод + async def mock_execute(command: str, *args): + cmd_method = getattr(fake_redis, command.lower(), None) + if cmd_method is not None: + if hasattr(cmd_method, '__call__'): + return await cmd_method(*args) + else: + return cmd_method + return None + + # Патчим все основные методы Redis mock_redis.execute = mock_execute mock_redis.get = fake_redis.get mock_redis.set = fake_redis.set mock_redis.delete = fake_redis.delete mock_redis.exists = fake_redis.exists mock_redis.ping = fake_redis.ping + mock_redis.hset = fake_redis.hset + mock_redis.hget = fake_redis.hget + mock_redis.hgetall = fake_redis.hgetall + mock_redis.hdel = fake_redis.hdel + mock_redis.expire = fake_redis.expire + mock_redis.ttl = fake_redis.ttl + mock_redis.keys = fake_redis.keys + mock_redis.scan = fake_redis.scan mock_redis.is_connected = True - # Добавляем async методы для connect/disconnect + # Async методы для connect/disconnect async def mock_connect(): return True @@ -623,29 +799,9 @@ def mock_redis_if_unavailable(): mock_redis.disconnect = mock_disconnect yield + except ImportError: - # fakeredis не установлен, используем базовый mock - with patch('storage.redis.redis') as mock_redis: - # Создаем базовый mock для Redis методов - mock_redis.execute.return_value = None - mock_redis.get.return_value = None - mock_redis.set.return_value = True - mock_redis.delete.return_value = True - mock_redis.exists.return_value = False - mock_redis.ping.return_value = True - mock_redis.is_connected = False - - # Добавляем async методы для connect/disconnect - async def mock_connect(): - return True - - async def mock_disconnect(): - pass - - mock_redis.connect = mock_connect - mock_redis.disconnect = mock_disconnect - - yield + pytest.skip("fakeredis не установлен - установите: pip install fakeredis[aioredis]") @pytest.fixture(autouse=True)