from unittest import mock import pytest from campaigns.auth.dtos import User from campaigns.connectors.db import Database from campaigns.connectors.facebook import models as fb_models from campaigns.connectors.facebook.enums import ( AudienceEventSourceType, EngagementAudienceEventValue, ) from campaigns.meta.dtos import EngagementAudienceEvent from campaigns.meta.enums import CampaignPlatform from campaigns.meta.exceptions import AudienceEventDoesNotExist from campaigns.meta.handlers import ( CreateCampaignEngagementAudienceHandler, CreateCampaignEngagementAudienceRequest, ) from campaigns.meta.models import Campaign, EngagementAudience, FacebookPage from campaigns.meta.repositories import CampaignRepository, EngagementAudienceRepository from campaigns.meta.repositories.engagement_audience_event import ( EngagementAudienceEventRepository, ) from campaigns.meta.services import CampaignService from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel test_events = [ EngagementAudienceEvent( id="instagram_profile_follow", name="Followed Instagram Profile", platform=CampaignPlatform.INSTAGRAM, source_type=AudienceEventSourceType.IG_BUSINESS, value=EngagementAudienceEventValue.IG_PROFILE_FOLLOW, ), EngagementAudienceEvent( id="page_post_interaction", name="Interacted With Facebook Post", platform=CampaignPlatform.FACEBOOK, source_type=AudienceEventSourceType.PAGE, value=EngagementAudienceEventValue.FB_PAGE_POST_INTERACTION, ), ] @pytest.fixture def engagement_audience_event_repository() -> EngagementAudienceEventRepository: return EngagementAudienceEventRepository(events=test_events) @pytest.fixture def handler( db: Database, campaign_service: CampaignService, facebook_client_mock: mock.MagicMock, engagement_audience_event_repository: EngagementAudienceEventRepository, engagement_audience_repository: EngagementAudienceRepository, campaign_repository: CampaignRepository, ) -> CreateCampaignEngagementAudienceHandler: return CreateCampaignEngagementAudienceHandler( db=db, campaign_service=campaign_service, facebook_client=facebook_client_mock, engagement_audience_event_repository=engagement_audience_event_repository, engagement_audience_repository=engagement_audience_repository, campaign_repository=campaign_repository, ) async def test_create_campaign_engagement_audience( handler: CreateCampaignEngagementAudienceHandler, facebook_client_mock: mock.MagicMock, build_model: BuildModel, create_model: CreateModel, fake: FakerTyped, user: User, engagement_audience_repository: EngagementAudienceRepository, campaign_repository: CampaignRepository, ) -> None: facebook_page = await create_model(FacebookPage) campaign = await create_model( Campaign, facebook_page=facebook_page, instagram_account_id=fake.pystr(), engagement_audience_id=None, ) fb_audience = build_model(fb_models.EngagementAudience) facebook_client_mock.create_engagement_audience.return_value = fb_audience event_ids = ["instagram_profile_follow", "page_post_interaction"] result = await handler.handle( request=CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign.id, event_ids=event_ids, ), ) created_audience = await engagement_audience_repository.get(result.id) assert created_audience assert result assert result.lower_bound == fb_audience.approximate_count_lower_bound assert result.upper_bound == fb_audience.approximate_count_upper_bound assert result.event_ids == event_ids assert created_audience.event_ids == event_ids assert created_audience.facebook_page_external_id == facebook_page.external_id assert ( created_audience.instagram_account_external_id == campaign.instagram_account_external_id ) updated_campaign = await campaign_repository.get(campaign.id) assert updated_campaign assert updated_campaign.engagement_audience_id == result.id async def test_create_campaign_engagement_audience_facebook_only( handler: CreateCampaignEngagementAudienceHandler, facebook_client_mock: mock.MagicMock, build_model: BuildModel, create_model: CreateModel, fake: FakerTyped, user: User, engagement_audience_repository: EngagementAudienceRepository, ) -> None: facebook_page = await create_model(FacebookPage) campaign = await create_model( Campaign, facebook_page=facebook_page, instagram_account_id=fake.pystr() ) fb_audience = build_model(fb_models.EngagementAudience) facebook_client_mock.create_engagement_audience.return_value = fb_audience event_ids = ["page_post_interaction"] result = await handler.handle( request=CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign.id, event_ids=event_ids, ), ) created_audience = await engagement_audience_repository.get(ident=result.id) assert created_audience assert created_audience.instagram_account_external_id is None async def test_create_campaign_engagement_audience_instagram_only( handler: CreateCampaignEngagementAudienceHandler, facebook_client_mock: mock.MagicMock, build_model: BuildModel, create_model: CreateModel, fake: FakerTyped, user: User, engagement_audience_repository: EngagementAudienceRepository, ) -> None: facebook_page = await create_model(FacebookPage) campaign = await create_model( Campaign, facebook_page=facebook_page, instagram_account_external_id=fake.pystr(), ) fb_audience = build_model(fb_models.EngagementAudience) facebook_client_mock.create_engagement_audience.return_value = fb_audience event_ids = ["instagram_profile_follow"] result = await handler.handle( request=CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign.id, event_ids=event_ids, ), ) created_audience = await engagement_audience_repository.get(ident=result.id) assert created_audience assert created_audience.facebook_page_external_id is None async def test_create_campaign_engagement_audience_reuse_existing_not_connected( handler: CreateCampaignEngagementAudienceHandler, facebook_client_mock: mock.MagicMock, build_model: BuildModel, create_model: CreateModel, fake: FakerTyped, user: User, engagement_audience_repository: EngagementAudienceRepository, campaign_repository: CampaignRepository, ) -> None: facebook_page = await create_model(FacebookPage) instagram_account_external_id = fake.pystr() campaign = await create_model( Campaign, facebook_page=facebook_page, instagram_account_external_id=instagram_account_external_id, engagement_audience_id=None, ) fb_audience = build_model(fb_models.EngagementAudience) facebook_client_mock.get_engagement_audience.return_value = fb_audience event_ids = ["instagram_profile_follow", "page_post_interaction"] reuse_audience = await create_model( EngagementAudience, external_id=fb_audience.id, event_ids=event_ids, facebook_page_external_id=facebook_page.external_id, instagram_account_external_id=instagram_account_external_id, ) result = await handler.handle( request=CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign.id, event_ids=event_ids, ), ) created_audiences = await engagement_audience_repository.all() facebook_client_mock.create_engagement_audience.assert_not_called() assert result assert result.lower_bound == fb_audience.approximate_count_lower_bound assert result.upper_bound == fb_audience.approximate_count_upper_bound assert result.event_ids == event_ids assert created_audiences assert len(created_audiences) == 1 updated_campaign = await campaign_repository.get(ident=campaign.id) assert updated_campaign # for linter assert updated_campaign.engagement_audience_id == reuse_audience.id async def test_create_campaign_engagement_audience_wrong_event_id( handler: CreateCampaignEngagementAudienceHandler, facebook_client_mock: mock.MagicMock, build_model: BuildModel, create_model: CreateModel, fake: FakerTyped, user: User, engagement_audience_repository: EngagementAudienceRepository, ) -> None: facebook_page = await create_model(FacebookPage) instagram_account_external_id = fake.pystr() campaign = await create_model( Campaign, facebook_page=facebook_page, instagram_account_id=instagram_account_external_id, ) fb_audience = build_model(fb_models.EngagementAudience) facebook_client_mock.get_engagement_audience.return_value = fb_audience event_ids = ["wrong_event_id", "instagram_profile_follow", "page_post_interaction"] with pytest.raises(AudienceEventDoesNotExist): await handler.handle( request=CreateCampaignEngagementAudienceRequest( user=user, campaign_id=campaign.id, event_ids=event_ids, ), )