import logging from datetime import timedelta from unittest import mock import faker import freezegun import pytest from fansifter_common.utils import timezone from app.enums import CampaignStatus from app.handler import TwilioSendMessagesHandler from app.models import AudienceFan, Campaign, FanPersonalDataDbt, MessageSendQueue from app.repositories import MessageSendQueueRepository from app.types import MessageSendResult, PreparedMessage from tests.unit.types import CreateModel class TestTwilioSendMessagesHandler: def test_handle( self, handler: TwilioSendMessagesHandler, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.INFO) sends = handler.handle() assert sends == [] assert "No schedules found." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_created( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, faker: faker.Faker, ) -> None: audience_snapshot_id = faker.uuid4(cast_to=str) campaign = create_model( Campaign, audience_snapshot_id=audience_snapshot_id, recipients_count=3, ) # Create 2 fans, fandata and pref center profiles for them audience_fan_1 = create_model(AudienceFan, snapshot_id=audience_snapshot_id) audience_fan_2 = create_model(AudienceFan, snapshot_id=audience_snapshot_id) _ = create_model(FanPersonalDataDbt, fan_id=audience_fan_1.fan_id) _ = create_model(FanPersonalDataDbt, fan_id=audience_fan_2.fan_id) _ = handler.handle() queues = send_queue_repository.all() assert len(queues) == 1 assert queues[0].campaign_id == campaign.id assert queues[0].offset == 0 assert queues[0].total == 2 assert queues[0].is_queued # Check that the campaign recipients count is updated assert campaign.recipients_count == 2 @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_creation_queue_disabled( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, faker: faker.Faker, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) audience_snapshot_id = faker.uuid4(cast_to=str) _ = create_model( Campaign, audience_snapshot_id=audience_snapshot_id, recipients_count=1, ) # Create fan create_model(AudienceFan, snapshot_id=audience_snapshot_id) _ = handler.handle() queues = send_queue_repository.all() assert queues == [] assert "Queue is disabled for the campaign." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_creation_campaign_not_found( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, faker: faker.Faker, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) audience_snapshot_id = faker.uuid4(cast_to=str) _ = str(faker.uuid4(cast_to=str)) # Create fan create_model(AudienceFan, snapshot_id=audience_snapshot_id) _ = handler.handle() queues = send_queue_repository.all() assert queues == [] assert "Campaign not found." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_creation_campaign_has_no_send_at( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, faker: faker.Faker, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) audience_snapshot_id = faker.uuid4(cast_to=str) create_model( Campaign, audience_snapshot_id=audience_snapshot_id, send_at=None, ) # Create fan create_model(AudienceFan, snapshot_id=audience_snapshot_id) _ = handler.handle() queues = send_queue_repository.all() assert queues == [] assert "Campaign is not processable." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_creation_campaign_has_no_audience_snapshot_id( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) _ = create_model( Campaign, audience_snapshot_id=None, ) _ = handler.handle() queues = send_queue_repository.all() assert queues == [] assert "Campaign audience snapshot is not set." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db def test_queues_creation_campaign_has_zero_fans( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, faker: faker.Faker, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) audience_snapshot_id = faker.uuid4(cast_to=str) campaign = create_model( Campaign, audience_snapshot_id=audience_snapshot_id, recipients_count=3, ) _ = handler.handle() queues = send_queue_repository.all() assert not queues assert campaign.recipients_count == 0 assert "Campaign has no fans." in caplog.messages @pytest.mark.db def test_queues_already_created( self, handler: TwilioSendMessagesHandler, send_queue_repository: MessageSendQueueRepository, create_model: CreateModel, ) -> None: now_at = timezone.now() campaign = create_model(Campaign, send_at=now_at) queue = create_model( MessageSendQueue, campaign_id=campaign.id, total=100, offset=0, send_at=now_at, ) _ = handler.handle() queues = send_queue_repository.all() assert len(queues) == 1 assert queues == [queue] @pytest.mark.db def test_process_empty_queues(self, handler: TwilioSendMessagesHandler) -> None: sends = handler.process_queues([]) assert sends == [] @pytest.mark.db @freezegun.freeze_time("2024-10-15 10:30:00") def test_processing_queues( self, handler: TwilioSendMessagesHandler, create_model: CreateModel, faker: faker.Faker, ows_text_campaigns_client_mock: mock.MagicMock, ) -> None: handler.max_messages_to_send = 10 sender = faker.phone_number() phone_number_1 = faker.phone_number() phone_number_2 = faker.phone_number() send_at = timezone.now() campaign_1 = create_model(Campaign) campaign_2 = create_model(Campaign) audience_fan_1 = create_model( AudienceFan, snapshot_id=campaign_1.audience_snapshot_id ) audience_fan_2 = create_model( AudienceFan, snapshot_id=campaign_2.audience_snapshot_id ) _ = create_model( FanPersonalDataDbt, fan_id=audience_fan_1.fan_id, fan_mobile_phone=phone_number_1, ) _ = create_model( FanPersonalDataDbt, fan_id=audience_fan_2.fan_id, fan_mobile_phone=phone_number_2, ) queue_1 = create_model( MessageSendQueue, campaign_id=campaign_1.id, total=20, offset=0, send_at=send_at, ) queue_2 = create_model( MessageSendQueue, campaign_id=campaign_2.id, total=20, offset=0, send_at=send_at, ) tick_1_time = send_at tick_2_time = send_at + timedelta(minutes=1) sends = [] assert queue_1.offset == 0 assert queue_1.is_queued assert campaign_1.is_processable assert queue_2.offset == 0 assert queue_2.is_queued assert campaign_2.is_processable ows_text_campaigns_client_mock.render_messages.side_effect = [ [ PreparedMessage( sender=sender, recipient=phone_number_1, channel="SMS", message="hello", media_url=None, ), ], [ PreparedMessage( sender=sender, recipient=phone_number_2, channel="SMS", message="hello", media_url=None, ), ], ] with freezegun.freeze_time(tick_1_time): sends.extend(handler.process_queues([queue_1])) with freezegun.freeze_time(tick_2_time): sends.extend(handler.process_queues([queue_2])) assert queue_1.offset == 10 assert queue_1.is_processing assert campaign_1.is_in_progress assert queue_2.offset == 10 assert queue_2.is_processing assert campaign_2.is_in_progress assert sends == [ MessageSendResult( queue_id=queue_1.id, campaign_id=queue_1.campaign_id, sent_messages=1, timestamp=tick_1_time, ), MessageSendResult( queue_id=queue_2.id, campaign_id=queue_2.campaign_id, sent_messages=1, timestamp=tick_2_time, ), ] @pytest.mark.db @freezegun.freeze_time("2024-10-15 10:30:00") def test_process_queues_campaign_not_processable( self, handler: TwilioSendMessagesHandler, create_model: CreateModel, caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.ERROR) campaign = create_model(Campaign, status=CampaignStatus.SENT) queue = create_model( MessageSendQueue, campaign_id=campaign.id, total=20, offset=0, send_at=timezone.now(), ) handler.process_queues([queue]) assert queue.is_canceled assert queue.offset == 0 assert "Campaign is already sent or not found." in caplog.messages @pytest.mark.xfail(reason="TODO") @pytest.mark.db @freezegun.freeze_time("2024-10-15 10:10:00") def test_send_completely_one_campaign( self, handler: TwilioSendMessagesHandler, create_model: CreateModel, ows_text_campaigns_client_mock: mock.MagicMock, faker: faker.Faker, ) -> None: total_fans = 10 sender = faker.unique.phone_number() recipient = faker.unique.phone_number() campaign = create_model(Campaign) audience_fans = [ create_model( AudienceFan, fan_id=faker.uuid4(), snapshot_id=campaign.audience_snapshot_id, ) for _ in range(total_fans) ] personal_data = [ create_model( FanPersonalDataDbt, fan_id=audience_fan.fan_id, fan_mobile_phone=recipient, ) for audience_fan in audience_fans ] queue = create_model( MessageSendQueue, campaign_id=campaign.id, total=total_fans, offset=0, ) ows_text_campaigns_client_mock.render_messages.return_value = [ PreparedMessage( sender=sender, recipient=item.fan_mobile_phone, channel="SMS", message="hello", media_url=None, ) for item in personal_data if item.fan_mobile_phone is not None ] sends = handler.process_queues([queue]) ows_text_campaigns_client_mock.disable_schedule.assert_called_once_with( campaign.id, campaign_status=CampaignStatus.SENT, ) # TODO: check twilio calls assert queue.is_completed assert sends == [ MessageSendResult( queue_id=queue.id, campaign_id=campaign.id, sent_messages=total_fans, timestamp=timezone.now(), ) ]