"""Pytest fixtures for async tests.""" import asyncio from typing import AsyncGenerator import fakeredis.aioredis import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine import app.cache as cache_module from app.database import Base, get_db from app.main import app @pytest.fixture(scope="session") def event_loop(): """Create a single event loop for all tests.""" loop = asyncio.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture async def test_engine(): """Create a fresh in-memory SQLite engine for each test.""" engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest_asyncio.fixture async def db_session(test_engine) -> AsyncGenerator[AsyncSession, None]: """Yield a DB session bound to the test engine.""" session_factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async with session_factory() as session: yield session @pytest_asyncio.fixture async def fake_redis(): """Provide a fakeredis instance and patch the cache module global.""" fake = fakeredis.aioredis.FakeRedis(decode_responses=True) cache_module._cache_client = fake yield fake cache_module._cache_client = None await fake.flushall() @pytest_asyncio.fixture async def client(test_engine, fake_redis) -> AsyncGenerator[AsyncClient, None]: """Provide an async HTTP client with test DB and fake Redis.""" session_factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async def override_get_db() -> AsyncGenerator[AsyncSession, None]: async with session_factory() as session: yield session app.dependency_overrides[get_db] = override_get_db transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as ac: yield ac app.dependency_overrides.clear()