import base64 import contextlib from collections.abc import Callable, Iterator from pathlib import Path from typing import Any from unittest import mock from urllib.parse import urlencode import pytest from anydi import Container from fansifter_common.adapters.db.base import Database from fansifter_common.testing.factories import FactoryService from mypy_boto3_kms import KMSClient from app.adapters.db.orm import Model from app.adapters.ows_text_campaigns import OwsTextCampaignsClient from app.container import container as _container from tests.unit.adapters.db.base import TestDatabase from tests.unit.types import CreateModel from tests.unit.utils import twilio_signature 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: return _container @pytest.fixture(scope="session", autouse=True) def kms_client_mock( container: Container, test_auth_token: str ) -> Iterator[mock.MagicMock]: mock_client = mock.MagicMock() mock_client.decrypt.return_value = {"Plaintext": test_auth_token.encode()} with container.override(KMSClient, mock_client): yield mock_client @pytest.fixture(scope="session", autouse=True) def ows_text_campaigns_client_mock(container: Container) -> Iterator[mock.MagicMock]: mock_client = mock.MagicMock() with container.override(OwsTextCampaignsClient, mock_client): yield mock_client @pytest.fixture(scope="function", autouse=True) def reset_ows_text_campaigns_client_mock( ows_text_campaigns_client_mock: mock.MagicMock, ) -> None: ows_text_campaigns_client_mock.reset_mock() @pytest.fixture(scope="session", autouse=True) def db(container: Container) -> Iterator[Database]: # Create the database file before the tests db_path = Path(__file__).parent.parent.parent / "test.db" db = TestDatabase( 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) @contextlib.contextmanager def _transaction_context(db: Database) -> Iterator[None]: with db.engine.begin() as conn, db.session_factory(bind=conn): try: yield finally: conn.rollback() @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: Database = request.getfixturevalue("db") with _transaction_context(db): yield @pytest.fixture(scope="session") def factory_service() -> FactoryService: factory_service = FactoryService() return factory_service @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(scope="session") def test_event_common_message_fields(test_account_sid: str) -> dict[str, Any]: return { "MessageStatus": "received", "To": "+1111", "MessagingServiceSid": "test", "NumSegments": 1, "MessageSid": "test", "AccountSid": test_account_sid, "From": "+1111", } @pytest.fixture(scope="session") def test_auth_token() -> str: return "test_auth_token" @pytest.fixture(scope="session") def test_account_sid() -> str: return "test_account_sid" @pytest.fixture(scope="session") def generate_test_event( test_auth_token: str, test_event_common_message_fields: dict[str, Any], ) -> Callable[[str], dict[str, Any]]: def test_event_generator(message_text: str) -> dict[str, Any]: opt_out_type = "STOP" if "stop" in message_text.lower() else "" body_params = { "Body": message_text, "OptOutType": opt_out_type, **test_event_common_message_fields, } signature = twilio_signature( test_auth_token, "https://example.com/inbound", body_params ) return { "path": "/inbound", "headers": { "host": "example.com", "x-twilio-signature": signature, "x-forwarded-proto": "https", }, "body": base64.b64encode(urlencode(body_params).encode()).decode(), } return test_event_generator