""" Shared test fixtures and configuration. """ from __future__ import annotations import uuid from datetime import timedelta from typing import AsyncGenerator from unittest.mock import AsyncMock, MagicMock import pytest from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from cpv3.db.base import Base from cpv3.db.session import get_db from cpv3.infrastructure.auth import get_current_user from cpv3.infrastructure.deps import get_storage from cpv3.infrastructure.security import create_token, hash_password from cpv3.main import app from cpv3.modules.users.models import User # Use in-memory SQLite for tests (or configure a test database) TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" @pytest.fixture async def test_engine(): """Create a test database engine with tables.""" 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.fixture async def test_db_session(test_engine) -> AsyncGenerator[AsyncSession, None]: """Create a test database session with per-test transaction isolation.""" async_session = async_sessionmaker( bind=test_engine, class_=AsyncSession, expire_on_commit=False ) async with async_session() as session: yield session @pytest.fixture async def test_user(test_db_session: AsyncSession) -> User: """Create a regular test user.""" user = User( id=uuid.uuid4(), username="testuser", email="test@example.com", password_hash=hash_password("testpassword"), first_name="Test", last_name="User", is_active=True, is_staff=False, is_superuser=False, ) test_db_session.add(user) await test_db_session.commit() await test_db_session.refresh(user) return user @pytest.fixture async def staff_user(test_db_session: AsyncSession) -> User: """Create a staff test user.""" user = User( id=uuid.uuid4(), username="staffuser", email="staff@example.com", password_hash=hash_password("staffpassword"), first_name="Staff", last_name="User", is_active=True, is_staff=True, is_superuser=False, ) test_db_session.add(user) await test_db_session.commit() await test_db_session.refresh(user) return user @pytest.fixture async def other_user(test_db_session: AsyncSession) -> User: """Create another regular user for permission testing.""" user = User( id=uuid.uuid4(), username="otheruser", email="other@example.com", password_hash=hash_password("otherpassword"), first_name="Other", last_name="User", is_active=True, is_staff=False, is_superuser=False, ) test_db_session.add(user) await test_db_session.commit() await test_db_session.refresh(user) return user @pytest.fixture def auth_headers(test_user: User) -> dict[str, str]: """Generate auth headers with valid JWT for the test user.""" token = create_token( subject=str(test_user.id), token_type="access", expires_in=timedelta(hours=1), ) return {"Authorization": f"Bearer {token}"} @pytest.fixture def staff_auth_headers(staff_user: User) -> dict[str, str]: """Generate auth headers with valid JWT for the staff user.""" token = create_token( subject=str(staff_user.id), token_type="access", expires_in=timedelta(hours=1), ) return {"Authorization": f"Bearer {token}"} @pytest.fixture def other_auth_headers(other_user: User) -> dict[str, str]: """Generate auth headers with valid JWT for the other user.""" token = create_token( subject=str(other_user.id), token_type="access", expires_in=timedelta(hours=1), ) return {"Authorization": f"Bearer {token}"} @pytest.fixture def mock_storage() -> MagicMock: """Create a mock storage service.""" storage = MagicMock() storage.upload_fileobj = AsyncMock(return_value="uploads/test-file.txt") storage.exists = AsyncMock(return_value=True) file_info = MagicMock() file_info.file_path = "uploads/test-file.txt" file_info.file_url = "http://example.com/uploads/test-file.txt" file_info.file_size = 1024 file_info.filename = "test-file.txt" storage.get_file_info = AsyncMock(return_value=file_info) return storage @pytest.fixture async def async_client( test_db_session: AsyncSession, mock_storage: MagicMock, ) -> AsyncGenerator[AsyncClient, None]: """Create async test client with dependency overrides (no auth override).""" async def override_get_db(): yield test_db_session async def override_get_storage(): return mock_storage app.dependency_overrides[get_db] = override_get_db app.dependency_overrides[get_storage] = override_get_storage async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test", ) as client: yield client app.dependency_overrides.clear() @pytest.fixture async def auth_client( test_db_session: AsyncSession, test_user: User, mock_storage: MagicMock, ) -> AsyncGenerator[AsyncClient, None]: """Create async test client with auth dependency overridden to return test_user.""" async def override_get_db(): yield test_db_session def override_get_current_user(): return test_user async def override_get_storage(): return mock_storage app.dependency_overrides[get_db] = override_get_db app.dependency_overrides[get_current_user] = override_get_current_user app.dependency_overrides[get_storage] = override_get_storage async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test", ) as client: yield client app.dependency_overrides.clear() @pytest.fixture async def staff_client( test_db_session: AsyncSession, staff_user: User, mock_storage: MagicMock, ) -> AsyncGenerator[AsyncClient, None]: """Create async test client with auth dependency overridden to return staff_user.""" async def override_get_db(): yield test_db_session def override_get_current_user(): return staff_user async def override_get_storage(): return mock_storage app.dependency_overrides[get_db] = override_get_db app.dependency_overrides[get_current_user] = override_get_current_user app.dependency_overrides[get_storage] = override_get_storage async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test", ) as client: yield client app.dependency_overrides.clear()