import contextlib import pathlib from collections.abc import Iterator from typing import Any from unittest import mock import pytest import redis import sqlalchemy as sa from anydi import Container from fansifter_common.testing.factories import FactoryService from pytest_docker import Services from app.adapters.aws import LambdaClient from app.adapters.db import Database, Model from app.adapters.db.utils import check_db_alive from app.adapters.ows_dmp import OwsDmpClient from app.config import Settings, settings as global_settings from app.container import container as global_container from tests.unit.module import TestModule from tests.unit.types import BuildModel, CreateModel, OverrideSettings 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 container() -> Container: global_container.register_module(TestModule) global_container.enable_test_mode() return global_container @pytest.fixture(scope="session") def settings() -> Settings: return global_settings @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"] @pytest.fixture(scope="session") def _docker_snowflake_setup( docker_services: Services, docker_ip: str, settings: Settings ) -> None: port = docker_services.port_for("snowflake", 5443) assert port == 7443 url = sa.URL.create( drivername="postgresql+psycopg", username=settings.snowflake_user, password=settings.snowflake_password.get_secret_value() if settings.snowflake_password else None, host=docker_ip, port=port, database=settings.snowflake_database, ) docker_services.wait_until_responsive( timeout=60.0, pause=0.5, check=lambda: check_db_alive(url), ) @pytest.fixture(scope="session") def db(request: pytest.FixtureRequest, container: Container) -> Iterator[Database]: db = container.resolve(Database) if ( db.engine.dialect.name.startswith("postgres") and request.config.getoption("docker_mode") == "auto" ): request.getfixturevalue("_docker_snowflake_setup") Model.metadata.create_all(bind=db.engine) yield db db.close() 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 db: Database = request.getfixturevalue("db") with db.rollback_transaction(): yield @pytest.fixture(scope="session") def override_settings(settings: Settings, container: Container) -> OverrideSettings: @contextlib.contextmanager def wrapper(**kwargs: Any) -> Iterator[None]: new_settings = settings.model_copy(update=kwargs) with ( mock.patch("app.config.settings.__wrapped__", new_settings), container.override(Settings, new_settings), ): yield return wrapper @pytest.fixture(scope="session") def lambda_client_mock(container: Container) -> Iterator[mock.MagicMock]: lambda_client_mock = mock.MagicMock(spec=LambdaClient) with container.override(LambdaClient, lambda_client_mock): yield lambda_client_mock @pytest.fixture(autouse=True) def _reset_lambda_client_mock(lambda_client_mock: mock.MagicMock) -> None: lambda_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def ows_dmp_client_mock(container: Container) -> Iterator[mock.MagicMock]: ows_dmp_client_mock = mock.MagicMock(spec=OwsDmpClient) with container.override(OwsDmpClient, ows_dmp_client_mock): yield ows_dmp_client_mock @pytest.fixture(autouse=True) def _reset_ows_dmp_client_mock( ows_dmp_client_mock: mock.MagicMock, ) -> None: ows_dmp_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session") def redis_client_mock(container: Container) -> Iterator[redis.Redis]: redis_client_mock = mock.MagicMock(spec=redis.Redis) with container.override(redis.Redis, redis_client_mock): yield redis_client_mock @pytest.fixture(autouse=True) def _reset_redis_client_mock(redis_client_mock: mock.MagicMock) -> None: redis_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session") def factory_service() -> FactoryService: factory_service = FactoryService() factory_service.scan("tests.unit.factories") return factory_service @pytest.fixture def build_model(factory_service: FactoryService) -> BuildModel: def wrapper[T](model: type[T], **kwargs: Any) -> T: return factory_service.build(model, **kwargs) return wrapper @pytest.fixture def create_model(factory_service: FactoryService, db: Database) -> CreateModel: def wrapper[T: Model](model: type[T], **kwargs: Any) -> T: return factory_service.create(db.session, model, **kwargs) return wrapper