from collections.abc import Iterator from datetime import UTC, datetime import httpx import pytest import respx from faker import Faker from pydantic import ValidationError from fansifter_common.adapters.twilio.client import TwilioClient from fansifter_common.adapters.twilio.models import ( APIKeyResponse, MessageCountryPricing, SendMessageRequest, ) class TestTwilioClient: @pytest.fixture def client(self) -> Iterator[TwilioClient]: with TwilioClient( account_sid="account_sid", auth_token="auth_token", ) as client: yield client def test_get_message_country_pricing( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: country_code = faker.country_code() message_pricing = MessageCountryPricing( iso_country=country_code, price_unit="USD", outbound_sms_prices=[] ) respx_mock.get( f"https://pricing.twilio.com/v1/Messaging/Countries/{country_code}" ).mock( return_value=httpx.Response( status_code=200, json=message_pricing.model_dump() ) ) result = client.get_message_country_pricing(country_code) assert result == message_pricing def test_send_message( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: to = faker.phone_number() from_ = faker.phone_number() body = faker.text() sid = faker.uuid4() account_sid = faker.pystr() messaging_service_sid = faker.pystr() respx_mock.post( "https://api.twilio.com/2010-04-01/Accounts/account_sid/Messages.json", data={ "To": to, "From": from_, "Body": body, }, ).mock( return_value=httpx.Response( status_code=200, json={ "sid": sid, "account_sid": account_sid, "messaging_service_sid": messaging_service_sid, "date_created": "Thu, 24 Aug 2023 05:01:45 +0000", "num_media": "0", "num_segments": "1", "price": "0.1", "price_unit": "usd", "status": "queued", "error_code": None, "error_message": None, }, ) ) response = client.send_message( SendMessageRequest(to=to, from_=from_, body=body) ) assert response.sid == sid assert response.account_sid == account_sid assert response.messaging_service_sid == messaging_service_sid assert response.date_created == datetime( year=2023, month=8, day=24, hour=5, minute=1, second=45, tzinfo=UTC ) assert response.num_media == "0" assert response.num_segments == "1" assert response.price == "0.1" assert response.price_unit == "usd" assert response.status == "queued" assert response.error_code is None assert response.error_message is None def test_send_message_with_media_url( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: to = faker.phone_number() from_ = faker.phone_number() body = faker.text() media_url = faker.image_url() sid = faker.uuid4() respx_mock.post( "https://api.twilio.com/2010-04-01/Accounts/account_sid/Messages.json", data={ "To": to, "From": from_, "Body": body, "MediaUrl": media_url, }, ).mock( return_value=httpx.Response( status_code=200, json={ "sid": sid, }, ) ) response = client.send_message( SendMessageRequest( to=to, from_=from_, body=body, media_url=media_url, ) ) assert response.sid == sid def test_send_message_with_media_url_as_list( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: to = faker.phone_number() from_ = faker.phone_number() body = faker.text() media_url = faker.image_url() sid = faker.uuid4() respx_mock.post( "https://api.twilio.com/2010-04-01/Accounts/account_sid/Messages.json", data={ "To": to, "From": from_, "Body": body, "MediaUrl": media_url, }, ).mock( return_value=httpx.Response( status_code=200, json={ "sid": sid, }, ) ) response = client.send_message( SendMessageRequest( to=to, from_=from_, body=body, media_url=[media_url], ) ) assert response.sid == sid def test_send_message_with_multiple_media_urls( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: to = faker.phone_number() from_ = faker.phone_number() body = faker.text() media_url_1 = faker.image_url() media_url_2 = faker.image_url() sid = faker.uuid4() respx_mock.post( "https://api.twilio.com/2010-04-01/Accounts/account_sid/Messages.json", data={ "To": to, "From": from_, "Body": body, "MediaUrl[0]": media_url_1, "MediaUrl[1]": media_url_2, }, ).mock( return_value=httpx.Response( status_code=200, json={ "sid": sid, }, ) ) response = client.send_message( SendMessageRequest( to=to, from_=from_, body=body, media_url=[ media_url_1, media_url_2, ], ) ) assert response.sid == sid def test_send_message_sender_required( self, client: TwilioClient, faker: Faker ) -> None: to = faker.phone_number() body = faker.text() with pytest.raises(ValidationError): client.send_message(SendMessageRequest(to=to, body=body)) def test_create_api_key( self, client: TwilioClient, respx_mock: respx.MockRouter, faker: Faker, ) -> None: account_sid = faker.pystr() friendly_name = faker.name() api_key = APIKeyResponse( sid=faker.pystr(), secret=faker.pystr(), friendly_name=friendly_name, ) respx_mock.post( "https://iam.twilio.com/v1/Keys", data={ "AccountSid": account_sid, "FriendlyName": friendly_name, }, ).mock(return_value=httpx.Response(status_code=200, json=api_key.model_dump())) result = client.create_api_key( account_sid=account_sid, friendly_name=friendly_name, ) assert result.sid == api_key.sid assert result.secret == api_key.secret assert result.friendly_name == friendly_name