"""Shared test fixtures for all test categories.""" import asyncio import os from typing import AsyncGenerator, Generator from unittest.mock import patch import pytest import pytest_asyncio from fastapi.testclient import TestClient from sqlalchemy import create_engine, text from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import sessionmaker # Set test environment BEFORE importing app modules os.environ["APP_ENV"] = "testing" os.environ["SECRET_KEY"] = "test-secret-key-for-testing-only-do-not-use-in-production" os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///:memory:" from src.config import Settings, build_database_url from src.models.base import Base from src.main import app from src.auth.dependencies import get_db_session @pytest.fixture def test_client() -> Generator[TestClient, None, None]: """Provide a FastAPI test client with SQLite database.""" # Create a single engine for this test engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, ) # Create tables async def init_db(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) asyncio.run(init_db()) async def override_get_db_session() -> AsyncGenerator[AsyncSession, None]: async with async_sessionmaker(engine, expire_on_commit=False)() as session: yield session # Override the dependency app.dependency_overrides[get_db_session] = override_get_db_session # Patch startup events to prevent PostgreSQL connection attempts with patch("src.main.init_database") as mock_init: mock_init.return_value = True try: with TestClient(app) as client: yield client finally: # Clean up overrides app.dependency_overrides.pop(get_db_session, None) asyncio.run(engine.dispose()) @pytest_asyncio.fixture async def db_session(test_client) -> AsyncGenerator[AsyncSession, None]: """Provide an async database session for unit tests.""" # Get the override function from the test_client fixture override_fn = app.dependency_overrides.get(get_db_session) if override_fn: gen = override_fn() session = await gen.asend(None) try: yield session finally: await gen.aclose() else: # Fallback: create a new engine and session engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, ) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async with async_sessionmaker(engine, expire_on_commit=False)() as session: yield session await engine.dispose() @pytest.fixture def authenticated_client(test_client) -> Generator[TestClient, None, None]: """Provide an authenticated test client with a test user.""" import uuid from src.auth.session import create_session_cookie from src.models.user import User user_id = str(uuid.uuid4()) settings = Settings() # Create user in database using the same engine as test_client # We need to access the engine from the test_client fixture # Since we can't easily do that, we'll create the user via API call # But we need the user to exist before any API calls # So we need to create the user using the overridden dependency async def create_test_user(): # Get the override function override_fn = app.dependency_overrides.get(get_db_session) if override_fn: gen = override_fn() session = await gen.asend(None) try: user = User( id=uuid.UUID(user_id), email="test@headquarter.local", name="Test User", authentik_id=f"authentik-{user_id}", avatar_url=None, ) session.add(user) await session.commit() finally: await gen.aclose() asyncio.run(create_test_user()) # Create session cookie session_cookie = create_session_cookie( settings=settings, user_id=user_id, ) # Set cookie on client test_client.cookies.set("session", session_cookie) yield test_client @pytest.fixture def admin_client(test_client) -> Generator[TestClient, None, None]: """Provide an authenticated test client with an admin user.""" import uuid from src.auth.session import create_session_cookie from src.models.user import User user_id = str(uuid.uuid4()) settings = Settings() async def create_admin_user(): override_fn = app.dependency_overrides.get(get_db_session) if override_fn: gen = override_fn() session = await gen.asend(None) try: user = User( id=uuid.UUID(user_id), email="admin@headquarter.local", name="Admin User", authentik_id=f"authentik-admin-{user_id}", avatar_url=None, is_admin=True, ) session.add(user) await session.commit() finally: await gen.aclose() asyncio.run(create_admin_user()) # Create session cookie session_cookie = create_session_cookie( settings=settings, user_id=user_id, ) # Set cookie on client test_client.cookies.set("session", session_cookie) yield test_client