import contextlib import logging.config from collections.abc import Iterator from pathlib import Path from typing import Any from unittest import mock import pytest from anydi import Container from cachelib import BaseCache, NullCache from fansifter_common.adapters.db import Database from fansifter_common.encrypter import Encrypter, RawEncrypter from fansifter_common.identifiers.types import ( DoubleOptInIdentifier, DoubleOptInToken, ProfileIdentifier, ProfileToken, ) from fansifter_common.identifiers.utils import encrypt_double_opt_in_token from fansifter_common.legal_info.constants import RESTRICTED_DATE_OF_BIRTH_COUNTRIES from fansifter_common.testing.factories import FactoryService from preference_center.adapters.db import Model from preference_center.adapters.kafka import KafkaProducer from preference_center.config import Settings, settings as global_settings from preference_center.container import container as global_container from preference_center.profile.services import ProfileService from tests.unit.faker import FakerTyped from tests.unit.types import ( BuildModel, CreateDoubleOptInToken, CreateModel, CreateProfileToken, OverrideSettings, ) @pytest.fixture(scope="session") def container() -> Container: return global_container @pytest.fixture(scope="session") def settings() -> Settings: return global_settings @pytest.fixture(scope="session", autouse=True) def configure_logging(settings: Settings) -> None: logging.config.dictConfig(settings.logging_config) @pytest.fixture(scope="session", autouse=True) def dummy_encrypter(container: Container) -> Encrypter: encrypter = RawEncrypter() container.register(Encrypter, lambda: encrypter, scope="singleton", override=True) return encrypter @pytest.fixture(scope="session", autouse=True) def null_cache(container: Container) -> BaseCache: cache = NullCache() container.register(BaseCache, lambda: cache, scope="singleton", override=True) return cache @pytest.fixture(scope="session", autouse=True) def producer_mock(container: Container) -> Iterator[mock.MagicMock]: producer_mock = mock.MagicMock(spec=KafkaProducer) with container.override(KafkaProducer, producer_mock): yield producer_mock @pytest.fixture(autouse=True) def reset_producer(producer_mock: mock.MagicMock) -> None: producer_mock.reset_mock() @pytest.fixture(scope="session") def db(container: Container) -> Iterator[Database]: # Create the database file before the tests db_path = Path(__file__).parent.parent.parent / "test.db" db = Database( url="sqlite:///" + str(db_path), session_args={ "expire_on_commit": False, "autoflush": True, }, ) Model.metadata.create_all(bind=db.engine) with container.override(Database, db): yield db db.close() Model.metadata.drop_all(bind=db.engine) # Delete the database file after the tests db_path.unlink(missing_ok=True) @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.global_context(), 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("preference_center.config.settings.__wrapped__", new_settings), container.override(Settings, new_settings), ): yield return wrapper @pytest.fixture(scope="session") def factory_service() -> FactoryService: factory_service = FactoryService() factory_service.scan("tests.unit.factories") return factory_service @pytest.fixture(scope="session") def fake( restricted_date_of_birth_countries: list[str] = RESTRICTED_DATE_OF_BIRTH_COUNTRIES, ) -> FakerTyped: return FakerTyped( restricted_date_of_birth_countries=restricted_date_of_birth_countries, ) @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 @pytest.fixture def create_profile_token(container: Container) -> CreateProfileToken: """Create a profile token.""" service = container.resolve(ProfileService) def factory(identifier: ProfileIdentifier) -> ProfileToken: return service.encode_profile_identifier(identifier) return factory @pytest.fixture def create_double_opt_in_token( dummy_encrypter: Encrypter, ) -> CreateDoubleOptInToken: """Create a double opt-in token.""" def factory(identifier: DoubleOptInIdentifier) -> DoubleOptInToken: return encrypt_double_opt_in_token(dummy_encrypter, identifier=identifier) return factory