Files
core/tests/conftest.py

489 lines
16 KiB
Python
Raw Normal View History

2025-02-09 22:26:50 +03:00
import pytest
2025-06-02 21:50:58 +03:00
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
2025-07-31 18:55:59 +03:00
import time
import uuid
from starlette.testclient import TestClient
2025-05-29 12:37:39 +03:00
2025-02-09 22:26:50 +03:00
from services.redis import redis
2025-07-31 18:55:59 +03:00
from orm.base import BaseModel as Base
def get_test_client():
"""
Создает и возвращает тестовый клиент для интеграционных тестов.
Returns:
TestClient: Клиент для выполнения тестовых запросов
"""
from starlette.testclient import TestClient
# Отложенный импорт для предотвращения циклических зависимостей
def _import_app():
from main import app
return app
return TestClient(_import_app())
2025-02-09 22:26:50 +03:00
2025-02-11 12:00:35 +03:00
2025-02-09 22:26:50 +03:00
@pytest.fixture(scope="session")
2025-06-02 21:50:58 +03:00
def test_engine():
"""
Создает тестовый engine для всей сессии тестирования.
Использует in-memory SQLite для быстрых тестов.
"""
2025-07-31 18:55:59 +03:00
# Импортируем все модели, чтобы они были зарегистрированы
from orm.base import BaseModel as Base
from orm.community import Community, CommunityAuthor
from auth.orm import Author
from orm.draft import Draft, DraftAuthor, DraftTopic
from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutReactionsFollower
from orm.topic import Topic
from orm.reaction import Reaction
from orm.invite import Invite
from orm.notification import Notification
2025-06-02 21:50:58 +03:00
engine = create_engine(
"sqlite:///:memory:", echo=False, poolclass=StaticPool, connect_args={"check_same_thread": False}
)
2025-07-31 18:55:59 +03:00
# Принудительно удаляем все таблицы и создаем заново
Base.metadata.drop_all(engine)
2025-06-02 21:50:58 +03:00
Base.metadata.create_all(engine)
yield engine
# Cleanup после всех тестов
Base.metadata.drop_all(engine)
@pytest.fixture(scope="session")
def test_session_factory(test_engine):
"""
Создает фабрику сессий для тестирования.
"""
return sessionmaker(bind=test_engine, expire_on_commit=False)
@pytest.fixture
2025-07-31 18:55:59 +03:00
def db_session(test_session_factory, test_engine):
2025-06-02 21:50:58 +03:00
"""
Создает новую сессию БД для каждого теста.
Простая реализация без вложенных транзакций.
"""
2025-07-31 18:55:59 +03:00
# Принудительно пересоздаем таблицы для каждого теста
from orm.base import BaseModel as Base
from sqlalchemy import inspect
# Удаляем все таблицы
Base.metadata.drop_all(test_engine)
# Создаем таблицы заново
Base.metadata.create_all(test_engine)
# Проверяем что таблица draft создана с правильной схемой
inspector = inspect(test_engine)
draft_columns = [col['name'] for col in inspector.get_columns('draft')]
print(f"Draft table columns: {draft_columns}")
# Убеждаемся что колонка shout существует
if 'shout' not in draft_columns:
print("WARNING: Column 'shout' not found in draft table!")
2025-06-02 21:50:58 +03:00
session = test_session_factory()
2025-07-02 22:30:21 +03:00
# Создаем дефолтное сообщество для тестов
from orm.community import Community
from auth.orm import Author
import time
# Создаем системного автора если его нет
2025-07-31 18:55:59 +03:00
system_author = session.query(Author).where(Author.slug == "system").first()
2025-07-02 22:30:21 +03:00
if not system_author:
system_author = Author(
name="System",
slug="system",
email="system@test.local",
created_at=int(time.time()),
updated_at=int(time.time()),
last_seen=int(time.time())
)
session.add(system_author)
session.flush()
# Создаем дефолтное сообщество если его нет
2025-07-31 18:55:59 +03:00
default_community = session.query(Community).where(Community.id == 1).first()
2025-07-02 22:30:21 +03:00
if not default_community:
default_community = Community(
id=1,
name="Главное сообщество",
slug="main",
desc="Основное сообщество для тестов",
pic="",
created_at=int(time.time()),
created_by=system_author.id,
settings={"default_roles": ["reader", "author"], "available_roles": ["reader", "author", "artist", "expert", "editor", "admin"]},
private=False
)
session.add(default_community)
session.commit()
2025-06-02 21:50:58 +03:00
yield session
# Очищаем все данные после теста
try:
for table in reversed(Base.metadata.sorted_tables):
session.execute(table.delete())
session.commit()
except Exception:
session.rollback()
finally:
session.close()
2025-02-09 22:26:50 +03:00
2025-02-11 12:00:35 +03:00
2025-02-09 22:26:50 +03:00
@pytest.fixture
2025-06-02 21:50:58 +03:00
def db_session_commit(test_session_factory):
"""
Создает сессию БД с реальными commit'ами для интеграционных тестов.
Используется когда нужно тестировать реальные транзакции.
"""
session = test_session_factory()
2025-02-11 12:00:35 +03:00
2025-07-31 18:55:59 +03:00
# Создаем дефолтное сообщество для тестов
2025-07-02 22:30:21 +03:00
from orm.community import Community
from auth.orm import Author
# Создаем системного автора если его нет
2025-07-31 18:55:59 +03:00
system_author = session.query(Author).where(Author.slug == "system").first()
2025-07-02 22:30:21 +03:00
if not system_author:
system_author = Author(
name="System",
slug="system",
email="system@test.local",
created_at=int(time.time()),
updated_at=int(time.time()),
last_seen=int(time.time())
)
session.add(system_author)
2025-07-31 18:55:59 +03:00
session.commit()
2025-07-02 22:30:21 +03:00
# Создаем дефолтное сообщество если его нет
2025-07-31 18:55:59 +03:00
default_community = session.query(Community).where(Community.id == 1).first()
2025-07-02 22:30:21 +03:00
if not default_community:
default_community = Community(
id=1,
name="Главное сообщество",
slug="main",
desc="Основное сообщество для тестов",
pic="",
created_at=int(time.time()),
created_by=system_author.id,
settings={"default_roles": ["reader", "author"], "available_roles": ["reader", "author", "artist", "expert", "editor", "admin"]},
private=False
)
session.add(default_community)
session.commit()
2025-02-09 22:26:50 +03:00
yield session
2025-02-11 12:00:35 +03:00
2025-06-02 21:50:58 +03:00
# Очищаем все данные после теста
try:
for table in reversed(Base.metadata.sorted_tables):
session.execute(table.delete())
session.commit()
except Exception:
session.rollback()
finally:
session.close()
@pytest.fixture(scope="session")
def test_app():
2025-07-31 18:55:59 +03:00
"""Создает тестовое приложение"""
from main import app
return app
2025-05-16 09:11:39 +03:00
@pytest.fixture
def test_client(test_app):
2025-07-31 18:55:59 +03:00
"""Создает тестовый клиент"""
from starlette.testclient import TestClient
return TestClient(test_app)
2025-02-09 22:26:50 +03:00
2025-02-11 12:00:35 +03:00
2025-02-09 22:26:50 +03:00
@pytest.fixture
async def redis_client():
2025-07-31 18:55:59 +03:00
"""Создает тестовый Redis клиент"""
from services.redis import redis
# Очищаем тестовые данные
await redis.execute("FLUSHDB")
yield redis
# Очищаем после тестов
await redis.execute("FLUSHDB")
2025-06-02 21:50:58 +03:00
@pytest.fixture
def oauth_db_session(test_session_factory):
"""
2025-07-31 18:55:59 +03:00
Создает сессию БД для OAuth тестов.
2025-06-02 21:50:58 +03:00
"""
2025-07-31 18:55:59 +03:00
session = test_session_factory()
yield session
session.close()
2025-06-02 21:50:58 +03:00
2025-07-31 18:55:59 +03:00
# ============================================================================
# ОБЩИЕ ФИКСТУРЫ ДЛЯ RBAC ТЕСТОВ
# ============================================================================
2025-06-02 21:50:58 +03:00
2025-07-31 18:55:59 +03:00
@pytest.fixture
def unique_email():
"""Генерирует уникальный email для каждого теста"""
return f"test-{uuid.uuid4()}@example.com"
2025-07-02 22:30:21 +03:00
2025-07-31 18:55:59 +03:00
@pytest.fixture
def test_users(db_session):
"""Создает тестовых пользователей для RBAC тестов"""
2025-07-02 22:30:21 +03:00
from auth.orm import Author
2025-07-31 18:55:59 +03:00
users = []
2025-07-02 22:30:21 +03:00
2025-07-31 18:55:59 +03:00
# Создаем пользователей с ID 1-5
for i in range(1, 6):
user = db_session.query(Author).where(Author.id == i).first()
if not user:
user = Author(
id=i,
email=f"user{i}@example.com",
name=f"Test User {i}",
slug=f"test-user-{i}",
created_at=int(time.time())
)
user.set_password("password123")
db_session.add(user)
users.append(user)
db_session.commit()
return users
@pytest.fixture
def test_community(db_session, test_users):
"""Создает тестовое сообщество для RBAC тестов"""
from orm.community import Community
community = db_session.query(Community).where(Community.id == 1).first()
if not community:
community = Community(
2025-07-02 22:30:21 +03:00
id=1,
2025-07-31 18:55:59 +03:00
name="Test Community",
slug="test-community",
desc="Test community for RBAC tests",
created_by=test_users[0].id,
created_at=int(time.time())
2025-07-02 22:30:21 +03:00
)
2025-07-31 18:55:59 +03:00
db_session.add(community)
db_session.commit()
2025-07-02 22:30:21 +03:00
2025-07-31 18:55:59 +03:00
return community
@pytest.fixture
def simple_user(db_session):
"""Создает простого тестового пользователя"""
from auth.orm import Author
from orm.community import CommunityAuthor
# Очищаем любые существующие записи с этим ID/email
db_session.query(Author).where(
(Author.id == 200) | (Author.email == "simple_user@example.com")
).delete()
db_session.commit()
user = Author(
id=200,
email="simple_user@example.com",
name="Simple User",
slug="simple-user",
created_at=int(time.time())
)
user.set_password("password123")
db_session.add(user)
db_session.commit()
2025-06-02 21:50:58 +03:00
2025-07-31 18:55:59 +03:00
yield user
# Очистка после теста
2025-06-02 21:50:58 +03:00
try:
2025-07-31 18:55:59 +03:00
# Удаляем связанные записи CommunityAuthor
db_session.query(CommunityAuthor).where(CommunityAuthor.author_id == user.id).delete(synchronize_session=False)
# Удаляем самого пользователя
db_session.query(Author).where(Author.id == user.id).delete()
db_session.commit()
2025-06-02 21:50:58 +03:00
except Exception:
2025-07-31 18:55:59 +03:00
db_session.rollback()
@pytest.fixture
def simple_community(db_session, simple_user):
"""Создает простое тестовое сообщество"""
from orm.community import Community, CommunityAuthor
# Очищаем любые существующие записи с этим ID/slug
db_session.query(Community).where(Community.slug == "simple-test-community").delete()
db_session.commit()
community = Community(
name="Simple Test Community",
slug="simple-test-community",
desc="Simple community for tests",
created_by=simple_user.id,
created_at=int(time.time()),
settings={
"default_roles": ["reader", "author"],
"available_roles": ["reader", "author", "editor"]
}
)
db_session.add(community)
db_session.commit()
yield community
# Очистка после теста
try:
# Удаляем связанные записи CommunityAuthor
db_session.query(CommunityAuthor).where(CommunityAuthor.community_id == community.id).delete()
# Удаляем само сообщество
db_session.query(Community).where(Community.id == community.id).delete()
db_session.commit()
except Exception:
db_session.rollback()
@pytest.fixture
def community_without_creator(db_session):
"""Создает сообщество без создателя (created_by = None)"""
from orm.community import Community
community = Community(
id=100,
name="Community Without Creator",
slug="community-without-creator",
desc="Test community without creator",
created_by=None, # Ключевое изменение - создатель отсутствует
created_at=int(time.time())
)
db_session.add(community)
db_session.commit()
return community
@pytest.fixture
def admin_user_with_roles(db_session, test_users, test_community):
"""Создает пользователя с ролями администратора"""
from orm.community import CommunityAuthor
user = test_users[0]
# Создаем CommunityAuthor с ролями администратора
ca = CommunityAuthor(
community_id=test_community.id,
author_id=user.id,
roles="admin,editor,author"
)
db_session.add(ca)
db_session.commit()
return user
@pytest.fixture
def regular_user_with_roles(db_session, test_users, test_community):
"""Создает обычного пользователя с ролями"""
from orm.community import CommunityAuthor
user = test_users[1]
# Создаем CommunityAuthor с обычными ролями
ca = CommunityAuthor(
community_id=test_community.id,
author_id=user.id,
roles="reader,author"
)
db_session.add(ca)
db_session.commit()
return user
# ============================================================================
# УТИЛИТЫ ДЛЯ ТЕСТОВ
# ============================================================================
def create_test_user(db_session, user_id, email, name, slug, roles=None):
"""Утилита для создания тестового пользователя с ролями"""
from auth.orm import Author
from orm.community import CommunityAuthor
# Создаем пользователя
user = Author(
id=user_id,
email=email,
name=name,
slug=slug,
created_at=int(time.time())
)
user.set_password("password123")
db_session.add(user)
db_session.commit()
# Добавляем роли если указаны
if roles:
ca = CommunityAuthor(
community_id=1, # Используем основное сообщество
author_id=user.id,
roles=",".join(roles)
)
db_session.add(ca)
db_session.commit()
return user
def create_test_community(db_session, community_id, name, slug, created_by=None, settings=None):
"""Утилита для создания тестового сообщества"""
from orm.community import Community
community = Community(
id=community_id,
name=name,
slug=slug,
desc=f"Test community {name}",
created_by=created_by,
created_at=int(time.time()),
settings=settings or {"default_roles": ["reader"], "available_roles": ["reader", "author", "editor", "admin"]}
)
db_session.add(community)
db_session.commit()
return community
def cleanup_test_data(db_session, user_ids=None, community_ids=None):
"""Утилита для очистки тестовых данных"""
from orm.community import CommunityAuthor
# Очищаем CommunityAuthor записи
if user_ids:
db_session.query(CommunityAuthor).where(CommunityAuthor.author_id.in_(user_ids)).delete(synchronize_session=False)
if community_ids:
db_session.query(CommunityAuthor).where(CommunityAuthor.community_id.in_(community_ids)).delete(synchronize_session=False)
db_session.commit()