"""Shared test fixtures. The suite runs on SQLite in-memory (StaticPool, see app.core.database), so the environment MUST be configured before any app import. Works with any LIFETRACK_MODULES subset: `auth` and `imports` are always forced in because the shared fixtures (user, tokens, device keys, import runs) rely on them. """ import os os.environ.setdefault("LIFETRACK_DATABASE_URL", "sqlite+pysqlite:///:memory:") os.environ.setdefault("LIFETRACK_JWT_SECRET", "test-secret-not-for-production-32byte") _mods = os.environ.get("LIFETRACK_MODULES", "").strip() if _mods: _names = {part.strip() for part in _mods.split(",") if part.strip()} _names.update({"auth", "imports"}) os.environ["LIFETRACK_MODULES"] = ",".join(sorted(_names)) from collections.abc import Callable, Iterator from dataclasses import dataclass import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session from app.core.database import Base, SessionLocal, engine from app.core.module_loader import import_side_modules from app.core.security import create_access_token from app.main import app from app.modules.auth import service as auth_service from app.modules.auth.models import User from app.modules.auth.schemas import DeviceKeyCreate, SetupRequest TEST_USER_EMAIL = "test@lifetrack.local" TEST_USER_PASSWORD = "correct-horse-battery" @pytest.fixture() def _schema() -> Iterator[None]: """Fresh tables for every test (all modules' models are in the metadata).""" import_side_modules() Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) yield @pytest.fixture() def client(_schema: None) -> Iterator[TestClient]: with TestClient(app) as test_client: yield test_client @pytest.fixture() def db(_schema: None) -> Iterator[Session]: with SessionLocal() as session: yield session @pytest.fixture() def user(db: Session) -> User: return auth_service.create_first_user( db, SetupRequest( email=TEST_USER_EMAIL, password=TEST_USER_PASSWORD, display_name="Testeur", ), ) @pytest.fixture() def make_auth_headers() -> Callable[[int], dict[str, str]]: """Helper building an Authorization header for any user id.""" def _make(user_id: int) -> dict[str, str]: return {"Authorization": f"Bearer {create_access_token(user_id)}"} return _make @pytest.fixture() def auth_headers( user: User, make_auth_headers: Callable[[int], dict[str, str]] ) -> dict[str, str]: return make_auth_headers(user.id) @dataclass class DeviceKeyFixture: id: int plaintext: str headers: dict[str, str] @pytest.fixture() def device_key(db: Session, user: User) -> DeviceKeyFixture: """A device API key holding the wildcard ingest scope.""" key, plaintext = auth_service.create_device_key( db, user.id, DeviceKeyCreate(name="Clé de test", scopes=["ingest:*"]) ) return DeviceKeyFixture( id=key.id, plaintext=plaintext, headers={"X-API-Key": plaintext} )