ppt-tool/backend/tests/conftest.py
Vadym Samoilenko 76a4e41e3b Phase 7: Testing Suite — backend unit tests + Cypress E2E framework
Backend:
- conftest with async SQLite DB, factory fixtures for all models
- pytest-asyncio config in pyproject.toml
- Tests: auth (JWT, dev login), RBAC (access service), audit (query, export),
  brand enforcement (colors, fonts, logos, contrast), retention (cleanup, purge),
  content intelligence (regex classifiers), slide mapping, review workflow,
  analytics data queries

Frontend:
- Cypress E2E config with baseUrl and viewport settings
- Custom commands (devLogin, createPresentation)
- E2E specs: login flow, wizard navigation, admin panel, review workflow
- Test scripts in package.json

Infrastructure:
- Makefile: test-e2e and test-all targets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:49:23 +00:00

215 lines
6.8 KiB
Python

"""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(autouse=True)
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() -> 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