import logging import uuid from datetime import datetime from unittest.mock import AsyncMock, patch import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession from sqlalchemy.pool import StaticPool from app.database import Base, get_db from app.main import app from app.api.deps import get_current_user from app.middleware.logging_filter import APIKeyFilter from app.models.user import User from app.services.auth import create_access_token from tests.fixtures.auth import _make_user, _to_uuid pytest_plugins = [ "tests.fixtures.database", "tests.fixtures.brands", "tests.fixtures.client", "tests.fixtures.content", ] @pytest.fixture(autouse=True) def _cleanup_dependency_overrides(): """Ensure dependency_overrides are cleaned up after each test.""" from app.main import app yield app.dependency_overrides.clear() @pytest.fixture(autouse=True) def _reset_rate_limiter(): """Reset rate limiter state between tests to prevent cross-test contamination.""" yield # Clear rate limiter's in-memory request records try: from app.middleware.rate_limit import MemoryRateLimitBackend # Find all MemoryRateLimitBackend instances and clear them import gc for obj in gc.get_objects(): if isinstance(obj, MemoryRateLimitBackend): obj._requests.clear() except Exception: pass @pytest.fixture(autouse=True) def add_api_key_filter(): root_logger = logging.getLogger() api_key_filter = APIKeyFilter() root_logger.addFilter(api_key_filter) yield root_logger.removeFilter(api_key_filter) @pytest.fixture(scope="session", autouse=True) def mock_scheduler(): with patch("app.main.query_scheduler") as mock_sched: mock_sched.start = lambda: None mock_sched.shutdown = AsyncMock() yield @pytest_asyncio.fixture async def test_engine(): engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest_asyncio.fixture async def test_session(test_engine): async_session_maker = async_sessionmaker( test_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, autocommit=False, ) async with async_session_maker() as session: yield session @pytest_asyncio.fixture async def override_get_db(test_session): async def _get_db(): yield test_session app.dependency_overrides[get_db] = _get_db yield test_session app.dependency_overrides.pop(get_db, None) @pytest.fixture def mock_user(): user = AsyncMock() user.id = uuid.UUID("12345678-1234-1234-1234-123456789abc") user.email = "test@example.com" user.name = "Test User" user.plan = "free" user.max_queries = 5 user.is_active = True user.created_at = datetime.now() return user @pytest.fixture def override_get_current_user(mock_user): async def _override(): return mock_user app.dependency_overrides[get_current_user] = _override yield app.dependency_overrides.pop(get_current_user, None) @pytest.fixture def auth_token(mock_user): return create_access_token(data={"sub": str(mock_user.id)}) @pytest.fixture def auth_headers(auth_token): return {"Authorization": f"Bearer {auth_token}"} @pytest_asyncio.fixture async def plain_client(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: yield client