import dataclasses import datetime from collections.abc import Iterator import pytest from fansifter_common.utils import timezone from dmp.ad_reporting.enums import AdReportingObjective, AdReportingPlatform from dmp.ad_reporting.handlers import ( GetAdReportingReportsHandler, GetAdReportingReportsRequest, ) from dmp.ad_reporting.models import ( AdReportingCampaignDbt, AdReportingReport, ) from dmp.meta.models import MetaAdAccount, MetaUserAdAccount from tests.unit.faker import FakerTyped from tests.unit.types import CreateModel, CreateReportingModel @dataclasses.dataclass(kw_only=True, frozen=True) class ReportsData: ad_account_1: MetaAdAccount ad_account_2: MetaAdAccount ad_account_3: MetaAdAccount ad_account_4: MetaAdAccount ad_account_5: MetaAdAccount campaign_1: AdReportingCampaignDbt campaign_2: AdReportingCampaignDbt campaign_3: AdReportingCampaignDbt campaign_4: AdReportingCampaignDbt campaign_5: AdReportingCampaignDbt report_1: AdReportingReport report_2: AdReportingReport report_3: AdReportingReport report_4: AdReportingReport report_5: AdReportingReport class TestGetAdReportingReportsHandler: @pytest.fixture(autouse=True) def reports_data( self, create_model: CreateModel, create_reporting_model: CreateReportingModel, identity_id: str, ) -> Iterator[ReportsData]: # Create ad accounts ad_account_1 = create_model(MetaAdAccount) ad_account_2 = create_model(MetaAdAccount) ad_account_3 = create_model(MetaAdAccount) ad_account_4 = create_model(MetaAdAccount) ad_account_5 = create_model(MetaAdAccount) # Create user ad accounts create_model( MetaUserAdAccount, ad_account_id=ad_account_1.id, identity_id=identity_id, ) create_model( MetaUserAdAccount, ad_account_id=ad_account_2.id, identity_id=identity_id, ) create_model( MetaUserAdAccount, ad_account_id=ad_account_3.id, identity_id=identity_id, ) create_model( MetaUserAdAccount, ad_account_id=ad_account_4.id, identity_id=identity_id, ) create_model( MetaUserAdAccount, ad_account_id=ad_account_5.id, identity_id=identity_id, ) # Create campaigns campaign_1 = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account_1.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_AWARENESS, clicks=1000, views_p25=5000, views=5000, _global_participant_ids=["global_participant_id_1"], ) campaign_2 = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account_2.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_AWARENESS, clicks=2000, views_p25=4000, views=4000, _global_participant_ids=["global_participant_id_2"], ) campaign_3 = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account_3.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_TRAFFIC, clicks=3000, views_p25=3500, views=3500, _global_participant_ids=["global_participant_id_3"], ) campaign_4 = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account_4.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_TRAFFIC, clicks=4000, views_p25=2000, views=2000, _global_participant_ids=["global_participant_id_4"], ) campaign_5 = create_reporting_model( AdReportingCampaignDbt, account_id=ad_account_5.external_id, platform=AdReportingPlatform.META, objective=AdReportingObjective.OUTCOME_SALES, clicks=5000, views_p25=1000, views=1000, _global_participant_ids=["global_participant_id_5"], ) # Create reports report_1 = create_reporting_model( AdReportingReport, name="Report 1", identity_id=identity_id, campaign_ids={campaign_1.id, campaign_2.id}, created_at=timezone.now(), # clicks=3000, # views_p25=9000, ) report_2 = create_reporting_model( AdReportingReport, name="Report 2", identity_id=identity_id, campaign_ids={campaign_1.id, campaign_3.id}, created_at=timezone.now() + datetime.timedelta(minutes=1), # clicks=4000, # views_p25=8000, ) report_3 = create_reporting_model( AdReportingReport, name="Report 3", identity_id=identity_id, campaign_ids={campaign_1.id, campaign_4.id}, created_at=timezone.now() + datetime.timedelta(minutes=2), # clicks=5000, # views_p25=7000, ) report_4 = create_reporting_model( AdReportingReport, name="Report 4", identity_id=identity_id, campaign_ids={campaign_1.id, campaign_5.id}, created_at=timezone.now() + datetime.timedelta(minutes=3), # clicks=6000, # views_p25=6000, ) report_5 = create_reporting_model( AdReportingReport, name="Report 5", identity_id=identity_id, campaign_ids={campaign_2.id, campaign_3.id}, created_at=timezone.now() + datetime.timedelta(minutes=4), # clicks=5000, # views_p25=5000, ) yield ReportsData( ad_account_1=ad_account_1, ad_account_2=ad_account_2, ad_account_3=ad_account_3, ad_account_4=ad_account_4, ad_account_5=ad_account_5, campaign_1=campaign_1, campaign_2=campaign_2, campaign_3=campaign_3, campaign_4=campaign_4, campaign_5=campaign_5, report_1=report_1, report_2=report_2, report_3=report_3, report_4=report_4, report_5=report_5, ) @pytest.mark.db def test_get_reports( self, handler: GetAdReportingReportsHandler, identity_id: str, reports_data: ReportsData, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search=None, global_participant_id=None, platform=None, order_by=["createdAt.desc"], limit=10, offset=0, ) ) assert response.total == 5 assert len(response.items) == 5 assert response.items[0].name == reports_data.report_5.name assert response.items[1].name == reports_data.report_4.name assert response.items[2].name == reports_data.report_3.name assert response.items[3].name == reports_data.report_2.name assert response.items[4].name == reports_data.report_1.name @pytest.mark.db def test_get_reports_report_fields( self, handler: GetAdReportingReportsHandler, identity_id: str, reports_data: ReportsData, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search=None, global_participant_id=None, platform=None, order_by=["createdAt.asc"], limit=1, offset=0, ) ) report_1 = response.items[0] assert report_1.name == "Report 1" assert report_1.global_participant_ids == { reports_data.campaign_1.first_global_participant_id, reports_data.campaign_2.first_global_participant_id, } assert report_1.start_at == min( reports_data.campaign_1.start_at, reports_data.campaign_2.start_at, ) assert report_1.end_at == max( reports_data.campaign_1.end_at, reports_data.campaign_2.end_at, ) assert report_1.clicks == ( reports_data.campaign_1.clicks + reports_data.campaign_2.clicks ) assert report_1.impressions == ( reports_data.campaign_1.impressions + reports_data.campaign_2.impressions ) assert report_1.views_p25 == ( reports_data.campaign_1.views_p25 + reports_data.campaign_2.views_p25 ) assert report_1.views == ( reports_data.campaign_1.views + reports_data.campaign_2.views ) assert report_1.spend_usd == ( reports_data.campaign_1.spend_usd + reports_data.campaign_2.spend_usd ) @pytest.mark.db def test_get_reports_order_by_click_and_views( self, handler: GetAdReportingReportsHandler, identity_id: str, reports_data: ReportsData, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search=None, global_participant_id=None, platform=None, order_by=["clicks.desc", "views.desc"], limit=10, offset=0, ) ) assert response.total == 5 assert len(response.items) == 5 assert response.items[0].id == reports_data.report_4.id assert response.items[0].clicks == 6000 assert response.items[0].views_p25 == 6000 assert response.items[0].views == 6000 assert response.items[1].id == reports_data.report_5.id assert response.items[1].clicks == 5000 assert response.items[1].views_p25 == 7500 assert response.items[1].views == 7500 assert response.items[2].id == reports_data.report_3.id assert response.items[2].clicks == 5000 assert response.items[2].views_p25 == 7000 assert response.items[2].views == 7000 assert response.items[3].id == reports_data.report_2.id assert response.items[3].clicks == 4000 assert response.items[3].views_p25 == 8500 assert response.items[3].views == 8500 assert response.items[4].id == reports_data.report_1.id assert response.items[4].clicks == 3000 assert response.items[4].views_p25 == 9000 assert response.items[4].views == 9000 @pytest.mark.db def test_get_reports_paginated( self, handler: GetAdReportingReportsHandler, identity_id: str, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search=None, global_participant_id=None, platform=None, order_by=["createdAt.desc"], limit=2, offset=1, ) ) assert response.total == 5 assert len(response.items) == 2 assert response.items[0].name == "Report 4" assert response.items[1].name == "Report 3" @pytest.mark.db def test_get_reports_search( self, handler: GetAdReportingReportsHandler, identity_id: str, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search="report 4", global_participant_id=None, platform=None, order_by=["createdAt.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert len(response.items) == 1 assert response.items[0].name == "Report 4" @pytest.mark.db def test_get_reports_filter_by_global_participant_id( self, handler: GetAdReportingReportsHandler, identity_id: str, reports_data: ReportsData, ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=identity_id, search=None, global_participant_id=reports_data.campaign_3.first_global_participant_id, platform=None, order_by=["createdAt.asc"], limit=10, offset=0, ) ) assert response.total == 2 assert len(response.items) == 2 assert response.items[0].id == reports_data.report_2.id assert response.items[0].global_participant_ids == { "global_participant_id_1", "global_participant_id_3", } assert response.items[1].id == reports_data.report_5.id assert response.items[1].global_participant_ids == { "global_participant_id_2", "global_participant_id_3", } @pytest.mark.db def test_get_reports_empty( self, handler: GetAdReportingReportsHandler, fake: FakerTyped ) -> None: response = handler.handle( GetAdReportingReportsRequest( identity_id=fake.uuid4_string(), search=None, global_participant_id=None, platform=None, order_by=["name.asc"], limit=10, offset=0, ) ) assert response.total == 0 assert response.items == []