""" Fixtures per test di integrazione – DB in-memory SQLite + app FastAPI. Per i test di integrazione si usa SQLite async invece di PostgreSQL per semplicità e velocità. In CI si può aggiungere un servizio PostgreSQL reale. """ import os import uuid from typing import AsyncGenerator import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine # Override variabili d'ambiente prima di importare l'app os.environ["ENCRYPTION_KEY"] = "b" * 64 os.environ["SECRET_KEY"] = "integration-test-secret-key-only-for-tests" os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///./test_integration.db" os.environ["DATABASE_URL_SYNC"] = "sqlite:///./test_integration.db" os.environ["APP_ENV"] = "development" os.environ["APP_DEBUG"] = "false" from app.database import Base from app.main import app # Engine SQLite per test TEST_DATABASE_URL = "sqlite+aiosqlite:///./test_integration.db" test_engine = create_async_engine( TEST_DATABASE_URL, echo=False, connect_args={"check_same_thread": False}, ) TestAsyncSessionLocal = async_sessionmaker( bind=test_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, ) @pytest_asyncio.fixture(scope="session", autouse=True) async def setup_database(): """Crea tutte le tabelle nel DB di test.""" # Import modelli per registrarli nel metadata import app.models # noqa: F401 async with test_engine.begin() as conn: # SQLite non supporta tutti i tipi PostgreSQL, usiamo tabelle semplici await conn.run_sync(Base.metadata.drop_all) await conn.run_sync(Base.metadata.create_all) yield async with test_engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await test_engine.dispose() # Pulisci file DB import os if os.path.exists("test_integration.db"): os.remove("test_integration.db") @pytest_asyncio.fixture async def db_session() -> AsyncGenerator[AsyncSession, None]: """Session DB isolata per ogni test (rollback automatico).""" async with TestAsyncSessionLocal() as session: yield session await session.rollback() @pytest_asyncio.fixture async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]: """HTTP client per test API con override del DB.""" from app.database import get_db async def override_get_db(): yield db_session app.dependency_overrides[get_db] = override_get_db async with AsyncClient( transport=ASGITransport(app=app), base_url="http://testserver", ) as c: yield c app.dependency_overrides.clear() @pytest_asyncio.fixture async def demo_tenant(db_session: AsyncSession): """Crea un tenant di test.""" from app.models.tenant import Tenant tenant = Tenant( id=uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), slug="test-tenant", name="Test Tenant", plan="pro", max_mailboxes=10, max_users=10, ) db_session.add(tenant) await db_session.commit() await db_session.refresh(tenant) return tenant @pytest_asyncio.fixture async def admin_user(db_session: AsyncSession, demo_tenant): """Crea un utente admin nel tenant di test.""" from app.core.security import hash_password from app.models.user import User user = User( tenant_id=demo_tenant.id, email="admin@test.com", password_hash=hash_password("AdminPass1!"), full_name="Test Admin", role="admin", is_active=True, ) db_session.add(user) await db_session.commit() await db_session.refresh(user) return user @pytest_asyncio.fixture async def admin_token(client: AsyncClient, admin_user, db_session: AsyncSession) -> str: """ Token JWT per l'utente admin. Nota: il login usa il DB sovrapposto dalla fixture, quindi il seed_user deve già esistere. """ from app.core.security import create_access_token token = create_access_token( subject=admin_user.id, tenant_id=admin_user.tenant_id, role=admin_user.role, ) return token