"""Test fixtures: async SQLite database, factory functions for models.""" import os import uuid from datetime import datetime, timedelta, timezone import pytest import pytest_asyncio from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from sqlmodel import SQLModel # Force SQLite for tests (must be set before any app imports) os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///:memory:") os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key") os.environ.setdefault("DEV_AUTH_PASSWORD", "testpass") # Import all SQLModel table classes so metadata is populated from models.sql.user import UserModel # noqa: E402 from models.sql.client import ClientModel # noqa: E402 from models.sql.team import TeamModel # noqa: E402 from models.sql.team_membership import TeamMembershipModel # noqa: E402 from models.sql.brand_config import BrandConfigModel # noqa: E402 from models.sql.audit_log import AuditLogModel # noqa: E402 from models.sql.presentation import PresentationModel # noqa: E402 from models.sql.job import JobModel # noqa: E402 from models.sql.master_deck import MasterDeckModel # noqa: E402 _test_engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False) _test_session_maker = async_sessionmaker(_test_engine, expire_on_commit=False) @pytest_asyncio.fixture async def _setup_database(): """Create all tables before each test, drop after.""" async with _test_engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) yield async with _test_engine.begin() as conn: await conn.run_sync(SQLModel.metadata.drop_all) @pytest_asyncio.fixture async def session(_setup_database) -> AsyncSession: async with _test_session_maker() as s: yield s # --------------- Factory Fixtures --------------- @pytest_asyncio.fixture async def make_user(session: AsyncSession): async def _factory( email: str = "test@oliver.com", role: str = "user", display_name: str = "Test User", is_active: bool = True, azure_oid: str | None = None, ) -> UserModel: user = UserModel( email=email, display_name=display_name, role=role, is_active=is_active, azure_oid=azure_oid, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) session.add(user) await session.commit() await session.refresh(user) return user return _factory @pytest_asyncio.fixture async def make_client(session: AsyncSession): async def _factory( name: str = "Acme Corp", slug: str | None = None, retention_days: int | None = None, is_active: bool = True, ) -> ClientModel: client = ClientModel( name=name, slug=slug or name.lower().replace(" ", "-"), retention_days=retention_days, is_active=is_active, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) session.add(client) await session.commit() await session.refresh(client) return client return _factory @pytest_asyncio.fixture async def make_team(session: AsyncSession): async def _factory( name: str = "Dev Team", client_id: uuid.UUID | None = None, is_default: bool = False, ) -> TeamModel: team = TeamModel( name=name, client_id=client_id, is_default=is_default, created_at=datetime.now(timezone.utc), ) session.add(team) await session.commit() await session.refresh(team) return team return _factory @pytest_asyncio.fixture async def make_membership(session: AsyncSession): async def _factory(user_id: uuid.UUID, team_id: uuid.UUID) -> TeamMembershipModel: membership = TeamMembershipModel( user_id=user_id, team_id=team_id, assigned_at=datetime.now(timezone.utc), ) session.add(membership) await session.commit() await session.refresh(membership) return membership return _factory @pytest_asyncio.fixture async def make_brand_config(session: AsyncSession): async def _factory( client_id: uuid.UUID, primary_colors: list | None = None, secondary_colors: list | None = None, fonts: dict | None = None, logo_paths: list | None = None, voice_rules: str | None = None, voice_examples: list | None = None, ) -> BrandConfigModel: brand = BrandConfigModel( client_id=client_id, primary_colors=primary_colors or ["#5146E5", "#3D35B0"], secondary_colors=secondary_colors or ["#E9E8F8"], fonts=fonts or {"heading": "Inter", "body": "Roboto"}, logo_paths=logo_paths, voice_rules=voice_rules, voice_examples=voice_examples, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) session.add(brand) await session.commit() await session.refresh(brand) return brand return _factory @pytest_asyncio.fixture async def make_presentation(session: AsyncSession): async def _factory( owner_id: uuid.UUID | None = None, client_id: uuid.UUID | None = None, title: str = "Test Deck", status: str = "draft", created_at: datetime | None = None, deleted_at: datetime | None = None, ) -> PresentationModel: pres = PresentationModel( content="Test content", n_slides=5, language="English", title=title, owner_id=owner_id, client_id=client_id, status=status, created_at=created_at or datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), deleted_at=deleted_at, ) session.add(pres) await session.commit() await session.refresh(pres) return pres return _factory @pytest_asyncio.fixture async def make_job(session: AsyncSession): async def _factory( user_id: uuid.UUID, client_id: uuid.UUID, job_type: str = "generate_presentation", status: str = "queued", progress: int = 0, presentation_id: uuid.UUID | None = None, ) -> JobModel: job = JobModel( user_id=user_id, client_id=client_id, job_type=job_type, status=status, progress=progress, presentation_id=presentation_id, created_at=datetime.now(timezone.utc), ) session.add(job) await session.commit() await session.refresh(job) return job return _factory