import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from backend.app.db.base import Base from sqlmodel import SQLModel import backend.app.db.models.organisation # noqa: F401 — registers Organisation with SQLModel.metadata @pytest.fixture(scope="function") def engine(postgresql): """ Create a SQLAlchemy engine bound to the ephemeral pytest-postgresql database. """ # Build SQLAlchemy URL from psycopg connection info connection_string = ( f"postgresql+psycopg://" f"{postgresql.info.user}:" f"{postgresql.info.password}@" f"{postgresql.info.host}:" f"{postgresql.info.port}/" f"{postgresql.info.dbname}" ) engine = create_engine(connection_string) # Create tables once per test session. SQLModel first: the Modelling tables # (`plan` / `recommendation` / …) are SQLModel definitions, and Base tables # FK them (`funding_package` → `plan`), so they must exist before Base's # create_all runs (ADR-0017 amendment — single model per table). SQLModel.metadata.create_all(engine) Base.metadata.create_all(engine) # Yeild will split this function into two phase. 1) setup and 2) teardown, the latter of which will run after all # tests have completed yield engine # The `postgresql` fixture is function-scoped — a fresh, throwaway database # per test — so an explicit drop_all is redundant. We skip it: the `epc` # Postgres enum type is now shared across both metadatas (Base `portfolio` # tables and the SQLModel `plan`), and a two-phase metadata drop cannot drop # a cross-metadata type cleanly (ADR-0017 amendment). Disposing the engine # and letting the fixture discard the database is correct and conflict-free. engine.dispose() @pytest.fixture(scope="function") def db_session(engine): """ Provides a clean transactional session per test. Rolls back after each test to keep isolation. """ connection = engine.connect() transaction = connection.begin() session = sessionmaker(bind=connection)() yield session session.close() transaction.rollback() connection.close()