"""Pytest fixtures: test database, client, mock Redis, mock admin user.""" import asyncio import pytest import pytest_asyncio from httpx import AsyncClient, ASGITransport from unittest.mock import AsyncMock, patch, MagicMock from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from app.database import Base, get_db from app.cache import cache from app.models import EquipmentCache, RentalRequest, RentalRequestItem, Contact, AdminUser, SyncLog from app.auth import get_password_hash TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" @pytest.fixture(scope="session") def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture(scope="function") async def test_engine(): """Create an in-memory SQLite engine for tests.""" engine = create_async_engine(TEST_DATABASE_URL, echo=False) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await engine.dispose() @pytest_asyncio.fixture(scope="function") async def test_db(test_engine): """Yield a test database session.""" session_maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async with session_maker() as session: yield session @pytest_asyncio.fixture(scope="function") async def client(test_engine): """Yield an async test client with test database and mock cache.""" session_maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async def override_get_db(): async with session_maker() as session: yield session # Mock cache to avoid Redis dependency mock_cache = MagicMock() mock_cache.get = AsyncMock(return_value=None) mock_cache.set = AsyncMock(return_value=None) mock_cache.delete_pattern = AsyncMock(return_value=0) mock_cache.incr_rate = AsyncMock(return_value=1) mock_cache.connect = AsyncMock(return_value=None) mock_cache._redis = MagicMock() mock_cache._redis.ping = AsyncMock(return_value=True) with patch("app.cache.cache", mock_cache), \ patch("app.routers.equipment.cache", mock_cache), \ patch("app.routers.admin.cache", mock_cache), \ patch("app.routers.rental_requests.cache", mock_cache), \ patch("app.routers.contact.cache", mock_cache), \ patch("app.services.sync_service.cache", mock_cache): from app.main import app 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() @pytest_asyncio.fixture(scope="function") async def seeded_admin(test_engine): """Seed an admin user into the test database and return credentials.""" session_maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async with session_maker() as session: admin = AdminUser( username="admin", password_hash=get_password_hash("testpassword"), ) session.add(admin) await session.commit() return {"username": "admin", "password": "testpassword"} @pytest_asyncio.fixture(scope="function") async def seeded_equipment(test_engine): """Seed sample equipment into the test database.""" session_maker = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) async with session_maker() as session: items = [ EquipmentCache(rentman_id="101", name="L-Acoustics K2", number="K2-001", category="Lautsprecher", brand="L-Acoustics", available=True), EquipmentCache(rentman_id="102", name="L-Acoustics KS28", number="KS28-001", category="Subwoofer", brand="L-Acoustics", available=True), EquipmentCache(rentman_id="103", name="d&b T10", number="T10-001", category="Lautsprecher", brand="d&b audiotechnik", available=True), ] for item in items: session.add(item) await session.commit() return items