import pathlib from collections.abc import Iterator from unittest import mock import pytest import sqlalchemy as sa from fansifter_common.adapters.db import Database from fansifter_common.adapters.db.models.utils import autodiscover_models from fansifter_common.adapters.db.utils import check_db_alive from pytest_docker import Services from resonance_engine.adapters.db import Model, db from resonance_engine.config import settings def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( "--docker-mode", choices=["external", "auto"], dest="docker_mode", default="auto", help=( "Specify Docker usage mode: 'external' (Docker already running) " "or 'auto' (let tests start it)" ), ) def pytest_configure(config: pytest.Config) -> None: config.addinivalue_line( "markers", "db: mark test as using the db", ) @pytest.fixture(scope="session") def docker_compose_file(pytestconfig: pytest.Config) -> list[pathlib.Path]: base_dir = pytestconfig.rootpath / "tests/unit" return [base_dir / "docker-compose.yaml"] def get_db_url(host: str | None = None, port: int | None = None) -> sa.URL: return sa.URL.create( drivername="postgresql+psycopg", username=settings.db_user, password=settings.db_password.get_secret_value() if settings.db_password else None, host=host or settings.db_host, port=port or settings.db_port, database=settings.db_name, ) @pytest.fixture(scope="session") def _docker_postgres_setup(docker_services: Services, docker_ip: str) -> None: port = docker_services.port_for("postgres", 5432) assert port == 7432 url = get_db_url(host=docker_ip, port=port) docker_services.wait_until_responsive( timeout=60.0, pause=0.5, check=lambda: check_db_alive(url), ) @pytest.fixture(scope="session", autouse=True) def _db() -> Iterator[None]: with mock.patch( "resonance_engine.adapters.db.db.__wrapped__", new=Database( url=get_db_url(), session_args={ "expire_on_commit": False, "autoflush": True, }, ), ): yield @pytest.fixture(scope="session") def _db_setup(request: pytest.FixtureRequest) -> Iterator[None]: if ( db.engine.dialect.name.startswith("postgres") and request.config.getoption("docker_mode") == "auto" ): request.getfixturevalue("_docker_postgres_setup") autodiscover_models("resonance_engine") Model.metadata.create_all(bind=db.engine) yield Model.metadata.drop_all(bind=db.engine) @pytest.fixture(autouse=True) def _db_marker(request: pytest.FixtureRequest) -> Iterator[None]: """Use the `db` marker to run a test in a transaction.""" marker = request.node.get_closest_marker("db") if not marker: yield return _ = request.getfixturevalue("_db_setup") with db.global_context(), db.rollback_transaction(): yield