Registrant model + schemas + router + migration 0003, exercising the vendored framework end-to-end (CRUDService, RBAC, audit, version history). - SSN app-layer encrypted (AES-256-GCM, HKDF key, column-bound AAD, versioned format) in security/column_crypto.py; write-only on the model; serializer exposes only ssn_masked (XXX-XX-1234). C.5.7.50 / SC-28. - pii_read audit on GET, list (one row per page with ids), and history. C.5.6.9 #5 / AU-14. - Portal-only callers (YOUTH/PARENT/HOST) limited to own row via Registrant.user_id; out-of-scope is 404. PARENT_USER sees nothing until Guardian lands. History gated on VersionHistory, not View. - minor_flag derived from dob at read time. Framework changes (flag to OWC on re-vendor): - crud._to_dict honors __audit_redact__, keeping ciphertext out of audit_log and version_history. - crud._to_dict serializes date (not only datetime) for JSON columns. - CRUDService.history annotation uses builtins.list (method shadowed it). 98 tests passing, 94.83% coverage; ruff + mypy clean. Coverage trace at docs/coverage/registrant.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
"""Shared pytest fixtures.
|
|
|
|
Uses SQLite for speed (see ARCHITECTURE.md §4.8). Tests exercise the framework
|
|
against an in-memory Postgres-shaped SQLAlchemy Base — no Alembic migrations
|
|
in the test path; models create their tables from metadata.
|
|
|
|
Environment defaults are set at *module import* time (not via fixture) so
|
|
they are in place before pytest collects any test module that transitively
|
|
imports `ypms.main` (which instantiates the FastAPI app at import time).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from collections.abc import Iterator
|
|
|
|
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("JWT_SECRET", "test-secret")
|
|
os.environ.setdefault(
|
|
"COLUMN_ENCRYPTION_KEY", "test-col-enc-key-32bytes-______________"
|
|
)
|
|
os.environ.setdefault("ENV", "test")
|
|
|
|
import pytest # noqa: E402
|
|
from fastapi.testclient import TestClient # noqa: E402
|
|
from sqlalchemy import create_engine # noqa: E402
|
|
from sqlalchemy.orm import Session, sessionmaker # noqa: E402
|
|
from sqlalchemy.pool import StaticPool # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def db() -> Iterator[Session]:
|
|
"""Fresh in-memory SQLite DB per test with all model tables created.
|
|
|
|
Import happens inside the fixture so the model modules register themselves
|
|
on Base.metadata before create_all runs.
|
|
"""
|
|
import ypms.audit.models # noqa: F401
|
|
import ypms.framework.version_history # noqa: F401
|
|
import ypms.security.models # noqa: F401
|
|
import ypms.users.models # noqa: F401
|
|
import ypms.youth.models # noqa: F401
|
|
from ypms.db import Base
|
|
|
|
# StaticPool + check_same_thread=False share the same in-memory DB across
|
|
# threads. TestClient runs handlers in a worker thread, so without this
|
|
# the fixture-scoped session and the request-scoped session see different
|
|
# empty databases (SADeprecationWarning: SQLite objects created in a
|
|
# thread can only be used in that same thread).
|
|
engine = create_engine(
|
|
"sqlite:///:memory:",
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
future=True,
|
|
)
|
|
Base.metadata.create_all(engine)
|
|
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
|
|
session = SessionLocal()
|
|
try:
|
|
yield session
|
|
finally:
|
|
session.close()
|
|
engine.dispose()
|
|
|
|
|
|
@pytest.fixture
|
|
def client() -> Iterator[TestClient]:
|
|
from ypms.main import create_app
|
|
|
|
app = create_app()
|
|
with TestClient(app) as c:
|
|
yield c
|