import contextlib import logging.config import pathlib from collections.abc import Iterator from typing import Any from unittest import mock import faker import pytest from anydi import Container from fansifter_common.adapters.graphql_router import GraphqlRouterClient from fansifter_common.adapters.ows_account import OwsAccountClient from fansifter_common.adapters.twilio.client import TwilioClient from fansifter_common.auth.account import Account, AccountAccess from fansifter_common.auth.services import AuthService from fansifter_common.encrypter import Encrypter, RawEncrypter from fansifter_common.testing.factories import FactoryService from pytest_docker.plugin import Services from sqlalchemy.engine import URL from ows_text_campaigns.adapters.aws.s3 import S3Client from ows_text_campaigns.adapters.aws.sts import STSClient from ows_text_campaigns.adapters.db import DB, Model, PgDB, PgModel from ows_text_campaigns.adapters.db.utils import check_db_alive from ows_text_campaigns.adapters.dynamodb import DynamoDBClient from ows_text_campaigns.adapters.kafka import KafkaClient from ows_text_campaigns.adapters.ows_preference_center import OwsPreferenceCenterClient from ows_text_campaigns.adapters.ows_url_shortener import OwsUrlShortenerClient from ows_text_campaigns.config import Settings, settings as global_settings from ows_text_campaigns.container import container as global_container from tests.unit.module import TestModule from tests.unit.types import BuildModel, CreateModel, CreatePgModel, 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(autouse=True) def anyio_backend() -> str: return "asyncio" @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 settings() -> Settings: return global_settings @pytest.fixture(scope="session") def container() -> Container: global_container.register_module(TestModule) return global_container @pytest.fixture(scope="session", autouse=True) def configure_logging(settings: Settings) -> None: logging.config.dictConfig(settings.logging_config) @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("ows_text_campaigns.config.settings.__wrapped__", new_settings), container.override(Settings, new_settings), ): yield return wrapper @pytest.fixture(scope="session") def _docker_postgres_setup( docker_services: Services, docker_ip: str, settings: Settings ) -> None: port = docker_services.port_for("postgres", 5432) assert port == 7432 docker_services.wait_until_responsive( timeout=60.0, pause=0.5, check=lambda: check_db_alive( settings.postgres_url.set( host=docker_ip, port=port, ) ), ) @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 = URL.create( drivername="postgresql+psycopg", username=settings.snowflake_user, password=settings.snowflake_password, 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[DB]: db = container.resolve(DB) 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(scope="session") def pg_db(request: pytest.FixtureRequest, container: Container) -> Iterator[PgDB]: db = container.resolve(PgDB) if ( db.engine.dialect.name.startswith("postgres") and request.config.getoption("docker_mode") == "auto" ): request.getfixturevalue("_docker_postgres_setup") PgModel.metadata.create_all(bind=db.engine) yield db db.close() PgModel.metadata.drop_all(bind=db.engine) @pytest.fixture(autouse=True) def _db_marker(request: pytest.FixtureRequest) -> Iterator[None]: marker = request.node.get_closest_marker("db") if marker is None: yield return db: DB = request.getfixturevalue("db") pg_db: PgDB = request.getfixturevalue("pg_db") with ( db.global_context(), db.rollback_transaction(), pg_db.global_context(), pg_db.rollback_transaction(), ): yield @pytest.fixture(scope="session", autouse=True) def ows_account_client_mock(container: Container) -> Iterator[mock.MagicMock]: ows_account_client_mock = mock.MagicMock(spec=OwsAccountClient) with container.override(OwsAccountClient, ows_account_client_mock): yield ows_account_client_mock @pytest.fixture(scope="session", autouse=True) def ows_url_shortener_client_mock(container: Container) -> Iterator[mock.MagicMock]: ows_url_shortener_client_mock = mock.MagicMock(spec=OwsUrlShortenerClient) with container.override(OwsUrlShortenerClient, ows_url_shortener_client_mock): yield ows_url_shortener_client_mock @pytest.fixture(autouse=True) def _reset_ows_url_shortener_client_mock( ows_url_shortener_client_mock: mock.MagicMock, ) -> None: ows_url_shortener_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def ows_preference_center_client_mock(container: Container) -> Iterator[mock.MagicMock]: ows_preference_center_client_mock = mock.MagicMock(spec=OwsPreferenceCenterClient) with container.override( OwsPreferenceCenterClient, ows_preference_center_client_mock ): yield ows_preference_center_client_mock @pytest.fixture(autouse=True) def _reset_ows_preference_center_client_mock( ows_preference_center_client_mock: mock.MagicMock, ) -> None: ows_preference_center_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def twilio_client_mock(container: Container) -> Iterator[mock.MagicMock]: twilio_client_mock = mock.MagicMock(spec=TwilioClient) with container.override(TwilioClient, twilio_client_mock): yield twilio_client_mock @pytest.fixture(autouse=True) def _reset_twilio_client_mock(twilio_client_mock: mock.MagicMock) -> None: twilio_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session") def kafka_client_mock(container: Container) -> Iterator[mock.MagicMock]: kafka_client_mock = mock.MagicMock(spec=KafkaClient) with container.override(KafkaClient, kafka_client_mock): yield kafka_client_mock @pytest.fixture(autouse=True) def _reset_kafka_client_mock(kafka_client_mock: mock.MagicMock) -> None: kafka_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session") def dynamodb_client_mock(container: Container) -> Iterator[mock.MagicMock]: dynamodb_client_mock = mock.MagicMock(spec=DynamoDBClient) with container.override(DynamoDBClient, dynamodb_client_mock): yield dynamodb_client_mock @pytest.fixture(autouse=True) def _reset_dynamodb_client_mock(dynamodb_client_mock: mock.MagicMock) -> None: dynamodb_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def dummy_encrypter(container: Container) -> Iterator[Encrypter]: encrypter = RawEncrypter() with container.override(Encrypter, encrypter): yield encrypter @pytest.fixture(scope="session", autouse=True) def graphql_router_client_mock(container: Container) -> Iterator[mock.MagicMock]: graphql_router_client_mock = mock.MagicMock(spec=GraphqlRouterClient) with container.override(GraphqlRouterClient, graphql_router_client_mock): yield graphql_router_client_mock @pytest.fixture(autouse=True) def _reset_graphql_router_client_mock( graphql_router_client_mock: mock.MagicMock, ) -> None: graphql_router_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def s3_client_mock(container: Container) -> Iterator[mock.MagicMock]: s3_client_mock = mock.MagicMock(spec=S3Client) with container.override(S3Client, s3_client_mock): yield s3_client_mock @pytest.fixture(autouse=True) def _reset_s3_client_mock(s3_client_mock: mock.MagicMock) -> None: s3_client_mock.reset_mock(return_value=True, side_effect=True) @pytest.fixture(scope="session", autouse=True) def sts_client_mock(container: Container) -> Iterator[mock.MagicMock]: sts_client_mock = mock.MagicMock(spec=STSClient) with container.override(STSClient, sts_client_mock): yield sts_client_mock @pytest.fixture def identity_id(faker: faker.Faker) -> str: return str(faker.uuid4(cast_to=str)) @pytest.fixture(scope="session") def account() -> Account: return Account(vendor_id=1, subaccount_id=0) @pytest.fixture(scope="session") def account_access(account: Account) -> AccountAccess: return AccountAccess(accounts=[account]) @pytest.fixture(scope="session", autouse=True) def auth_service_mock( container: Container, account_access: AccountAccess ) -> Iterator[mock.MagicMock]: auth_service_mock = mock.MagicMock(spec=AuthService) auth_service_mock.authorize_account.return_value = account_access auth_service_mock.authorize_optional_account.return_value = account_access auth_service_mock.authorize_for_permission.return_value = account_access with container.override(AuthService, instance=auth_service_mock): yield auth_service_mock @pytest.fixture(autouse=True) def _reset_auth_service_mock( auth_service_mock: mock.MagicMock, account_access: AccountAccess ) -> None: auth_service_mock.reset_mock(return_value=True, side_effect=True) auth_service_mock.authorize_account.return_value = account_access auth_service_mock.authorize_optional_account.return_value = account_access auth_service_mock.authorize_for_permission.return_value = account_access @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: DB) -> 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_pg_model(factory_service: FactoryService, pg_db: PgDB) -> CreatePgModel: def wrapper[T: PgModel](model: type[T], **kwargs: Any) -> T: return factory_service.create(pg_db.session, model, **kwargs) return wrapper