from datetime import timedelta import pytest from fansifter_common.utils import timezone from email_campaigns.audiences.enums import AudienceTarget from email_campaigns.audiences.models import Audience from email_campaigns.campaigns.enums import EmailCampaignStatus from email_campaigns.campaigns.exceptions import EmailCampaignNotFoundError from email_campaigns.campaigns.handlers import ( EstimateSendTimeHandler, EstimateSendTimeRequest, ) from email_campaigns.campaigns.models import CampaignBatch, DeliveryQuota, EmailCampaign from email_campaigns.emails.models import EmailDomain from tests.unit.types import CreateModel, CreatePgModel class TestEstimateSendTimeHandler: @pytest.mark.db def test_estimate_send_time_draft_campaign( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=1000, recipients_count_by_email_provider={"google": 1000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.DRAFT, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, send_at=timezone.now() + timedelta(hours=1), recipients_count=1000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.estimated_send_duration == 7199 assert response.emails_to_send == 1000 assert response.emails_count == 1000 @pytest.mark.db def test_estimate_send_time_scheduled_campaign( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: now = timezone.now() audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=1000, recipients_count_by_email_provider={"google": 1000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.SCHEDULED, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, scheduled_at=now - timedelta(hours=1), send_at=now + timedelta(hours=1), recipients_count=1000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.estimated_send_duration == 3600 assert response.emails_to_send == 1000 assert response.emails_count == 1000 @pytest.mark.db def test_estimate_send_time_scheduled_campaign_uses_campaign_send_at( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: now = timezone.now() audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=10000, recipients_count_by_email_provider={"google": 10000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign_future = create_model( EmailCampaign, status=EmailCampaignStatus.SCHEDULED, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, scheduled_at=now, send_at=now + timedelta(hours=2), recipients_count=10000, ) campaign_soon = create_model( EmailCampaign, status=EmailCampaignStatus.SCHEDULED, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, scheduled_at=now, send_at=now + timedelta(minutes=30), recipients_count=10000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) response_future = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign_future.id, ) ) response_soon = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign_soon.id, ) ) assert response_future.estimated_send_duration > 10000 assert response_soon.estimated_send_duration > 1000 assert ( response_future.estimated_send_duration != response_soon.estimated_send_duration ) @pytest.mark.db def test_estimate_send_time_in_progress_basic( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: now = timezone.now() audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=5000, recipients_count_by_email_provider={"google": 5000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.IN_PROGRESS, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, send_at=now - timedelta(minutes=30), recipients_count=5000, ) create_model( CampaignBatch, campaign_id=campaign.id, domain_id=email_domain.id, provider="google", batch_size=5000, batch_offset=2000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.emails_to_send == 3000 assert response.emails_count == 3000 assert response.estimated_send_duration == 18000 @pytest.mark.db def test_estimate_send_time_in_progress_multiple_providers( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: now = timezone.now() audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=8000, recipients_count_by_email_provider={"google": 5000, "yahoo": 3000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.IN_PROGRESS, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, send_at=now - timedelta(minutes=30), recipients_count=8000, ) create_model( CampaignBatch, campaign_id=campaign.id, domain_id=email_domain.id, provider="google", batch_size=5000, batch_offset=2000, ) create_model( CampaignBatch, campaign_id=campaign.id, domain_id=email_domain.id, provider="yahoo", batch_size=3000, batch_offset=1000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="yahoo", last_hourly_quota=500.0, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.emails_to_send == 5000 assert response.emails_count == 5000 assert response.estimated_send_duration == 21600 @pytest.mark.db def test_estimate_send_time_in_progress_no_batches( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: now = timezone.now() audience = create_pg_model( Audience, target=AudienceTarget.EMAIL, fan_count=5000, recipients_count_by_email_provider={"google": 5000}, ) email_domain = create_model( EmailDomain, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.IN_PROGRESS, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=email_domain, send_at=now, recipients_count=5000, ) create_model( DeliveryQuota, domain_id=email_domain.id, provider="google", last_hourly_quota=1000.0, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.emails_to_send == 5000 assert response.emails_count == 5000 assert response.estimated_send_duration == 18000 @pytest.mark.db def test_estimate_send_time_sent_returns_zero( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: audience = create_pg_model(Audience, target=AudienceTarget.EMAIL) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.SENT, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.estimated_send_duration == 0 assert response.emails_to_send == 0 assert response.emails_count == 0 @pytest.mark.db def test_estimate_send_time_no_audience_returns_zero( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: audience = create_pg_model(Audience, target=AudienceTarget.EMAIL) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.DRAFT, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=None, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.estimated_send_duration == 0 assert response.emails_to_send == 0 assert response.emails_count == 0 @pytest.mark.db def test_estimate_send_time_no_domain_returns_zero( self, handler: EstimateSendTimeHandler, create_model: CreateModel, create_pg_model: CreatePgModel, identity_id: str, ) -> None: audience = create_pg_model(Audience, target=AudienceTarget.EMAIL) campaign = create_model( EmailCampaign, status=EmailCampaignStatus.DRAFT, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, audience_id=audience.id, email_domain=None, ) response = handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id=campaign.id, ) ) assert response.estimated_send_duration == 0 assert response.emails_to_send == 0 assert response.emails_count == 0 @pytest.mark.db def test_estimate_send_time_campaign_not_found( self, handler: EstimateSendTimeHandler, identity_id: str, ) -> None: with pytest.raises(EmailCampaignNotFoundError): handler.handle( EstimateSendTimeRequest( identity_id=identity_id, campaign_id="non-existent-id", ) )