# tests/conftest.py from __future__ import annotations import asyncio import sys from typing import AsyncGenerator from uuid import uuid4 import pytest import pytest_asyncio from dotenv import load_dotenv from httpx import AsyncClient, ASGITransport from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker load_dotenv() from dataloader.api import app_main from dataloader.config import APP_CONFIG from dataloader.context import APP_CTX, get_session from dataloader.storage.models import Base from dataloader.storage.engine import create_engine, create_sessionmaker if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @pytest_asyncio.fixture(scope="function") async def db_engine() -> AsyncGenerator[AsyncEngine, None]: """ Создаёт тестовый движок для теста. Использует реальную БД из конфига. """ engine = create_engine(APP_CONFIG.pg.url) yield engine await engine.dispose() @pytest_asyncio.fixture(scope="function") async def db_session(db_engine: AsyncEngine) -> AsyncGenerator[AsyncSession, None]: """ Предоставляет сессию БД для каждого теста. НЕ использует транзакцию, чтобы работали advisory locks. """ sessionmaker = async_sessionmaker(bind=db_engine, expire_on_commit=False, class_=AsyncSession) async with sessionmaker() as session: yield session await session.rollback() @pytest_asyncio.fixture(scope="function") async def clean_queue_tables(db_session: AsyncSession) -> None: """ Очищает таблицы очереди перед каждым тестом. """ schema = APP_CONFIG.pg.schema_queue await db_session.execute(text(f"TRUNCATE TABLE {schema}.dl_job_events CASCADE")) await db_session.execute(text(f"TRUNCATE TABLE {schema}.dl_jobs CASCADE")) await db_session.commit() @pytest_asyncio.fixture async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]: """ HTTP клиент для тестирования API. """ async def override_get_session() -> AsyncGenerator[AsyncSession, None]: yield db_session app_main.dependency_overrides[get_session] = override_get_session transport = ASGITransport(app=app_main) async with AsyncClient(transport=transport, base_url="http://test") as c: yield c app_main.dependency_overrides.clear() @pytest.fixture def job_id() -> str: """ Генерирует уникальный job_id для тестов. """ return str(uuid4()) @pytest.fixture def queue_name() -> str: """ Возвращает имя тестовой очереди. """ return "test.queue" @pytest.fixture def task_name() -> str: """ Возвращает имя тестовой задачи. """ return "test.task"