# mypy: disable-error-code=operator import decimal from decimal import Decimal import pytest from dirty_equals import IsApprox from fansifter_common.auth.account import Account from dmp.ad_reporting.enums import ( AdReportingBudgetType, AdReportingObjective, AdReportingPlatform, ) from dmp.ad_reporting.handlers import ( GetAdReportingAdSetsHandler, GetAdReportingAdSetsRequest, ) from dmp.ad_reporting.models import ( AdReportingAdSetCountryDbt, AdReportingAdSetDbt, AdReportingCampaignBenchmarkByAccountArtistCountryDbt, AdReportingCampaignBenchmarkByAccountCountryDbt, AdReportingCampaignDbt, ) from dmp.meta.models import MetaAdAccount, MetaUserAdAccount from dmp.tiktok.models import TikTokAdAccount, TikTokUserAdAccount from tests.unit.faker import FakerTyped from tests.unit.types import CreateModel, CreateReportingModel class TestGetAdReportingAdSetsHandler: @pytest.mark.db def test_get_ad_sets( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ) -> None: ad_account = create_model(MetaAdAccount) user_ad_account = create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=AdReportingPlatform.META, ) ad_set = create_reporting_model( AdReportingAdSetDbt, campaign_id=campaign.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.summary.total == 1 assert response.items[0].id == ad_set.id assert response.items[0].accounts == {user_ad_account.account} @pytest.mark.db def test_get_ad_sets_with_country_filter_empty( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ) -> None: ad_account = create_model(MetaAdAccount) user_ad_account = create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) ad_set = create_reporting_model( AdReportingAdSetDbt, platform=AdReportingPlatform.META, account_id=ad_account.external_id, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, ) create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set.campaign_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, country_code="GB", ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, countries=["US"], ) ) assert response.summary.total == 1 assert response.summary.clicks == 0 assert response.summary.spend_usd == 0 assert response.summary.reach is None assert response.summary.actions is None assert response.summary.rate is None assert len(response.items) == 1 assert response.items[0].accounts == {user_ad_account.account} assert response.items[0].clicks == 0 assert response.items[0].spend_usd == 0 assert response.items[0].reach is None assert response.items[0].frequency is None assert response.items[0].artist_benchmark is None assert response.items[0].benchmark is None assert response.items[0].vs_artist_benchmark is None @pytest.mark.db def test_get_ad_sets_with_country_filter_recalculate( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, fake: FakerTyped, ) -> None: ad_account = create_model(MetaAdAccount) user_ad_account = create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) ad_set = create_reporting_model( AdReportingAdSetDbt, name="a", account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, ) create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set.campaign_id, external_id=ad_set.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, country_code="GB", ) ad_set_country_1 = create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set.campaign_id, external_id=ad_set.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, country_code="US", estimated_ad_recallers=fake.integer(), ) ad_set_country_2 = create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set.campaign_id, external_id=ad_set.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, country_code="EE", estimated_ad_recallers=fake.integer(), ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, countries=["US", "EE"], ) ) assert response.summary.total == 1 assert ( response.summary.clicks == ad_set_country_1.clicks + ad_set_country_2.clicks ) assert ad_set_country_1.reach and ad_set_country_2.reach assert ad_set_country_1.estimated_ad_recallers is not None assert ad_set_country_2.estimated_ad_recallers is not None assert response.summary.reach == ad_set_country_1.reach + ad_set_country_2.reach assert ( response.summary.spend_usd == ad_set_country_1.spend_usd + ad_set_country_2.spend_usd ) assert response.summary.rate == IsApprox( Decimal( ( ad_set_country_1.estimated_ad_recallers + ad_set_country_2.estimated_ad_recallers ) / (ad_set_country_1.reach + ad_set_country_2.reach) ), delta=Decimal("0.001"), ) assert len(response.items) == 1 assert response.items[0].accounts == {user_ad_account.account} assert ( response.items[0].clicks == ad_set_country_1.clicks + ad_set_country_2.clicks ) assert ( response.items[0].reach == ad_set_country_1.reach + ad_set_country_2.reach ) assert ( response.items[0].spend_usd == ad_set_country_1.spend_usd + ad_set_country_2.spend_usd ) assert response.items[0].rate == IsApprox( Decimal( ( ad_set_country_1.estimated_ad_recallers + ad_set_country_2.estimated_ad_recallers ) / (ad_set_country_1.reach + ad_set_country_2.reach) ), delta=decimal.Decimal("0.001"), ) assert response.items[0].artist_benchmark is None assert response.items[0].benchmark is None assert response.items[0].vs_artist_benchmark is None @pytest.mark.db def test_get_ad_sets_with_country_filter_multiple_ads_summary_matching( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ) -> None: ad_account = create_model(MetaAdAccount) create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_AWARENESS, ) ad_set_1 = create_reporting_model( AdReportingAdSetDbt, campaign_id=campaign.id, account_id=ad_account.external_id, platform=campaign.platform, campaign_objective=campaign.objective, action_type=campaign.action_type, ) ad_set_2 = create_reporting_model( AdReportingAdSetDbt, campaign_id=campaign.id, account_id=ad_account.external_id, platform=campaign.platform, campaign_objective=campaign.objective, currency=ad_set_1.currency, ) ad_set_country_1 = create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set_1.campaign_id, external_id=ad_set_1.external_id, name=ad_set_1.name, account_id=ad_account.external_id, platform=ad_set_1.platform, campaign_objective=ad_set_1.campaign_objective, country_code="GB", action_type=campaign.action_type, ) ad_set_country_2 = create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set_2.campaign_id, external_id=ad_set_2.external_id, name=ad_set_2.name, account_id=ad_account.external_id, platform=ad_set_2.platform, campaign_objective=ad_set_2.campaign_objective, country_code="US", action_type=campaign.action_type, ) create_reporting_model( AdReportingAdSetCountryDbt, campaign_id=ad_set_1.campaign_id, external_id=ad_set_1.external_id, name=ad_set_1.name, account_id=ad_account.external_id, platform=ad_set_1.platform, campaign_objective=ad_set_1.campaign_objective, country_code="EE", ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, countries=["US", "GB"], ) ) assert response.summary.total == 2 assert ( response.summary.clicks == ad_set_country_1.clicks + ad_set_country_2.clicks ) assert ( response.summary.impressions == ad_set_country_1.impressions + ad_set_country_2.impressions ) assert ad_set_country_1.reach and ad_set_country_2.reach assert response.summary.reach == ad_set_country_1.reach + ad_set_country_2.reach assert ( response.summary.estimated_ad_recallers and ad_set_country_1.estimated_ad_recallers and ad_set_country_2.estimated_ad_recallers ) assert ( response.summary.estimated_ad_recallers == ad_set_country_1.estimated_ad_recallers + ad_set_country_2.estimated_ad_recallers ) assert ( response.summary.views_p25 == ad_set_country_1.views_p25 + ad_set_country_2.views_p25 ) assert response.summary.views == ad_set_country_1.views + ad_set_country_2.views assert ( response.summary.purchases and ad_set_country_1.purchases and ad_set_country_2.purchases ) assert ( response.summary.purchases == int(ad_set_country_1.purchases) + ad_set_country_2.purchases ) assert ( response.summary.follows and ad_set_country_1.follows and ad_set_country_2.follows ) assert ( response.summary.follows == ad_set_country_1.follows + ad_set_country_2.follows ) assert ( response.summary.spend_usd == ad_set_country_1.spend_usd + ad_set_country_2.spend_usd ) assert ( response.summary.actions == ad_set_country_1.actions + ad_set_country_2.actions ) assert ad_set_country_1.reach and ad_set_country_2.reach assert response.summary.rate == IsApprox( Decimal( ( ad_set_country_1.estimated_ad_recallers + ad_set_country_2.estimated_ad_recallers ) / (ad_set_country_1.reach + ad_set_country_2.reach) ) ) assert response.summary.cost_per_action_usd == IsApprox( (ad_set_country_1.spend_usd + ad_set_country_2.spend_usd) / (ad_set_country_1.actions + ad_set_country_2.actions) ) assert response.summary.cost_per_action is None assert response.summary.frequency == IsApprox( decimal.Decimal(ad_set_country_1.impressions + ad_set_country_2.impressions) / (ad_set_country_1.reach + ad_set_country_2.reach) ) assert ad_set_1.daily_budget is not None assert ad_set_2.daily_budget is not None assert ad_set_1.daily_budget_usd is not None assert ad_set_2.daily_budget_usd is not None assert response.summary.budget == ad_set_1.daily_budget + ad_set_2.daily_budget assert response.summary.budget_usd == ( ad_set_1.daily_budget_usd + ad_set_2.daily_budget_usd ) assert response.summary.currency == ad_set_1.currency assert response.summary.budget_type == AdReportingBudgetType.DAILY_BUDGET assert len(response.items) == 2 @pytest.mark.db def test_get_ad_sets_with_country_filter_showing_benchmarks( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, fake: FakerTyped, ) -> None: ad_account = create_model(MetaAdAccount) global_participant_id = fake.uuid4_string() country_code = fake.country() platform = AdReportingPlatform.META objective = AdReportingObjective.OUTCOME_AWARENESS user_ad_account = create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=platform, objective=objective, global_participant_id=global_participant_id, ) create_reporting_model( AdReportingAdSetDbt, account_id=ad_account.external_id, platform=platform, campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, ) create_reporting_model( AdReportingAdSetCountryDbt, account_id=ad_account.external_id, platform=platform, country_code=country_code, campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, ) benchmark = create_reporting_model( AdReportingCampaignBenchmarkByAccountCountryDbt, vendor_id=user_ad_account.vendor_id, subaccount_id=user_ad_account.subaccount_id, country_code=country_code, platform=platform, objective=objective, ) artist_benchmark = create_reporting_model( AdReportingCampaignBenchmarkByAccountArtistCountryDbt, vendor_id=user_ad_account.vendor_id, subaccount_id=user_ad_account.subaccount_id, global_participant_id=global_participant_id, country_code=country_code, platform=platform, objective=objective, ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, campaign_id=None, search=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, countries=[country_code], ) ) assert len(response.items) == 1 assert response.items[0].benchmark == benchmark.rate assert response.items[0].artist_benchmark == artist_benchmark.rate @pytest.mark.db def test_get_ad_sets_with_country_filter_not_showing_benchmarks( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, fake: FakerTyped, ) -> None: ad_account = create_model(MetaAdAccount) global_participant_id = fake.uuid4_string() country_code_1 = fake.uuid4_string() country_code_2 = fake.uuid4_string() platform = AdReportingPlatform.META objective = AdReportingObjective.OUTCOME_AWARENESS user_ad_account = create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=platform, objective=objective, global_participant_id=global_participant_id, ) create_reporting_model( AdReportingAdSetDbt, account_id=ad_account.external_id, platform=platform, campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, ) create_reporting_model( AdReportingAdSetCountryDbt, account_id=ad_account.external_id, platform=platform, country_code=country_code_1, campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, ) create_reporting_model( AdReportingCampaignBenchmarkByAccountCountryDbt, vendor_id=user_ad_account.vendor_id, subaccount_id=user_ad_account.subaccount_id, country_code=country_code_1, platform=platform, objective=objective, ) create_reporting_model( AdReportingCampaignBenchmarkByAccountArtistCountryDbt, vendor_id=user_ad_account.vendor_id, subaccount_id=user_ad_account.subaccount_id, global_participant_id=global_participant_id, country_code=country_code_1, platform=platform, objective=objective, ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, campaign_id=None, search=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, countries=[country_code_1, country_code_2], ) ) assert len(response.items) == 1 assert response.items[0].artist_benchmark is None assert response.items[0].benchmark is None @pytest.mark.db def test_get_ad_sets_meta_and_tiktok( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ) -> None: meta_ad_account = create_model(MetaAdAccount) create_model( MetaUserAdAccount, ad_account_id=meta_ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) tiktok_ad_account = create_model(TikTokAdAccount) create_model( TikTokUserAdAccount, ad_account_id=tiktok_ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) create_reporting_model( AdReportingAdSetDbt, account_id=meta_ad_account.external_id, platform=AdReportingPlatform.META, ) create_reporting_model( AdReportingAdSetDbt, account_id=tiktok_ad_account.external_id, platform=AdReportingPlatform.TIKTOK, ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.summary.total == 2 @pytest.mark.db def test_get_ad_sets_no_allowed_accounts( self, handler: GetAdReportingAdSetsHandler, create_reporting_model: CreateReportingModel, identity_id: str, ) -> None: create_reporting_model(AdReportingAdSetDbt, platform=AdReportingPlatform.META) create_reporting_model(AdReportingAdSetDbt, platform=AdReportingPlatform.TIKTOK) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.summary.total == 0 @pytest.mark.db def test_get_ad_sets_empty( self, handler: GetAdReportingAdSetsHandler, identity_id: str ) -> None: response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.summary.total == 0 @pytest.mark.db def test_get_ad_sets_country_filter_with_zeros( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, fake: FakerTyped, ) -> None: ad_account = create_model(MetaAdAccount) global_participant_id = fake.uuid4_string() platform = AdReportingPlatform.META objective = AdReportingObjective.OUTCOME_AWARENESS create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=platform, objective=objective, global_participant_id=global_participant_id, ) ad_set = create_reporting_model( AdReportingAdSetDbt, account_id=ad_account.external_id, platform=platform, campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, ) create_reporting_model( AdReportingAdSetCountryDbt, account_id=ad_account.external_id, external_id=ad_set.external_id, platform=platform, country_code="US", campaign_id=campaign.id, campaign_objective=campaign.objective, campaign_global_participant_id=campaign.global_participant_id, impressions=0, clicks=0, reach=0, actions=0, ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], countries=["US"], limit=10, offset=0, ) ) assert response @pytest.mark.db def test_get_ad_sets_budget_and_spend_different_currencies( self, handler: GetAdReportingAdSetsHandler, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ) -> None: ad_account = create_model(MetaAdAccount) create_model( MetaUserAdAccount, ad_account_id=ad_account.id, identity_id=identity_id, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) campaign = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account.external_id, platform=AdReportingPlatform.META, ) ad_set_1 = create_reporting_model( AdReportingAdSetDbt, campaign_id=campaign.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, currency="USD", name="aaa", ) ad_set_2 = create_reporting_model( AdReportingAdSetDbt, campaign_id=campaign.external_id, account_id=ad_account.external_id, platform=AdReportingPlatform.META, campaign_objective=AdReportingObjective.OUTCOME_AWARENESS, currency="GBP", name="bbb", ) response = handler.handle( GetAdReportingAdSetsRequest( identity_id=identity_id, search=None, campaign_id=None, global_participant_id=None, platform=None, objective=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.summary.total == 2 assert response.summary.budget is None assert ad_set_1.daily_budget_usd is not None assert ad_set_2.daily_budget_usd is not None assert ( response.summary.budget_usd == ad_set_1.daily_budget_usd + ad_set_2.daily_budget_usd ) assert response.summary.budget_type == AdReportingBudgetType.DAILY_BUDGET assert response.summary.currency is None assert response.summary.spend is None assert response.summary.spend_usd == ad_set_1.spend_usd + ad_set_2.spend_usd assert response.items[0].name == ad_set_1.name assert response.items[0].budget == ad_set_1.daily_budget assert response.items[0].budget_usd == ad_set_1.daily_budget_usd assert response.items[0].currency == ad_set_1.currency assert response.items[0].spend == ad_set_1.spend assert response.items[0].spend_usd == ad_set_1.spend_usd assert response.items[1].name == ad_set_2.name assert response.items[1].budget == ad_set_2.daily_budget assert response.items[1].budget_usd == ad_set_2.daily_budget_usd assert response.items[1].currency == ad_set_2.currency assert response.items[1].spend == ad_set_2.spend assert response.items[1].spend_usd == ad_set_2.spend_usd