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 ASGITransport, AsyncClient from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker from dataloader.api import app_main from dataloader.config import APP_CONFIG from dataloader.context import get_session from dataloader.storage.engine import create_engine load_dotenv() if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) pytestmark = pytest.mark.asyncio @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"