from collections.abc import Iterator from unittest import mock import pytest from anydi import Container from dirty_equals import IsApprox from faker import Faker from fansifter_common.auth.account import Account, AccountAccess from dmp.adapters.ows_socials import ArtistSocialsStats from dmp.fandata.models import ( AccountArtistFullFanDataAccessDbt, FansByArtistAccountCountryDbt, FansByArtistAccountDbt, FansByCustomListAccountCountryDbt, FansByCustomListAccountDbt, GlobalFansByArtistCountryDbt, GlobalFansByArtistDbt, ) from dmp.rosters.handlers import GetRostersHandler, GetRostersRequest from dmp.rosters.models import ArtistRosterLocalRep, ArtistRosterMainRep from dmp.rosters.services import GlobalFanDataAccessService from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateReportingModel class TestGetRostersHandler: @pytest.mark.db def test_get_rosters_empty( self, handler: GetRostersHandler, identity_id: str ) -> None: response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_get_rosters( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, build_model: BuildModel, ) -> None: socials_stats = build_model(ArtistSocialsStats) fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=socials_stats.chartmetric_artist_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [socials_stats] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 item = response.items[0] assert item.vendor_id == fan_account.vendor_id assert item.subaccount_id == fan_account.subaccount_id assert item.chartmetric_artist_id == fan_account.chartmetric_id assert item.global_participant_id == fan_account.global_participant_id assert not item.custom_list_id assert item.name == fan_account.global_participant_name assert item.fans_count == fan_account.fans_count assert item.fans_count_7_days_change == fan_account.fans_count_7_days_change assert item.fans_count_28_days_change == fan_account.fans_count_28_days_change assert item.fans_count_6_month_change == fan_account.fans_count_6_month_change assert item.fans_count_1_year_change == fan_account.fans_count_1_year_change assert ( item.fans_count_7_days_percent_change == fan_account.fans_count_7_days_percent_change ) assert ( item.fans_count_28_days_percent_change == fan_account.fans_count_28_days_percent_change ) assert ( item.fans_count_6_month_percent_change == fan_account.fans_count_6_month_percent_change ) assert ( item.fans_count_1_year_percent_change == fan_account.fans_count_1_year_percent_change ) assert item.spotify_monthly_listeners == socials_stats.spotify_monthly_listeners assert item.instagram_followers == socials_stats.instagram_followers assert item.tiktok_followers == socials_stats.tiktok_followers assert item.spotify_followers == socials_stats.spotify_followers assert item.facebook_followers == socials_stats.facebook_followers assert item.youtube_followers == socials_stats.youtube_followers assert item.twitter_followers == socials_stats.twitter_followers assert item.soundcloud_followers == socials_stats.soundcloud_followers assert item.deezer_followers == socials_stats.deezer_followers assert item.engagement_benchmark == fan_account.engagement_benchmark assert item.engagement_ratio == fan_account.engagement_ratio assert item.ad_consent_fans_count == fan_account.ad_consent_fans_count assert item.email_consent_fans_count == fan_account.email_consent_fans_count @pytest.mark.db def test_get_rosters_limit( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, ows_socials_client_mock: mock.MagicMock, identity_id: str, account: Account, ) -> None: create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=1, offset=0, ) ) assert response.total == 2 assert len(response.items) == 1 @pytest.mark.db def test_get_rosters_with_zero_fans( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, ) -> None: create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, fans_count=0, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert response.items[0].fans_count == 0 @pytest.mark.db def test_get_rosters_for_specific_artist( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, ) -> None: fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, search=fan_account.global_participant_name, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert ( response.items[0].global_participant_id == fan_account.global_participant_id ) @pytest.mark.db def test_get_rosters_for_partial_name_search( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, ows_socials_client_mock: mock.MagicMock, identity_id: str, account: Account, ) -> None: fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, search=fan_account.global_participant_name[:5], order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert ( response.items[0].global_participant_id == fan_account.global_participant_id ) @pytest.mark.db def test_get_rosters_with_artist_team_access( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id + 1, subaccount_id=fan_data_owner_subaccount_id + 1, ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=fan_account.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=None, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, ) ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert ( response.items[0].global_participant_id == fan_account.global_participant_id ) @pytest.mark.db def test_get_rosters_with_artist_team_access_and_provider_vendor_id( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id + 1, subaccount_id=fan_data_owner_subaccount_id + 1, ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=fan_account.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=fan_data_owner_vendor_id, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, ) ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert ( response.items[0].global_participant_id == fan_account.global_participant_id ) @pytest.mark.db def test_get_rosters_with_artist_team_access_returns_empty_when_no_access( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 requesting_user_vendor_id = fan_data_owner_vendor_id + 2000 requesting_user_subaccount_id = fan_data_owner_subaccount_id + 2000 fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id + 1, subaccount_id=fan_data_owner_subaccount_id + 1, ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=fan_account.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=None, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=requesting_user_vendor_id, subaccount_id=requesting_user_subaccount_id, ) ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 0 assert not response.items @pytest.mark.db def test_get_rosters_no_access_returns_empty_for_artist_team_and_vendor( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 requesting_user_vendor_id = fan_data_owner_vendor_id + 2000 requesting_user_subaccount_id = fan_data_owner_subaccount_id + 2000 fan_account = create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id + 1, subaccount_id=fan_data_owner_subaccount_id + 1, ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=fan_account.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=fan_data_owner_vendor_id, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=requesting_user_vendor_id, subaccount_id=requesting_user_subaccount_id, ) ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 0 assert not response.items @pytest.mark.db def test_get_rosters_with_direct_access_and_artist_team_access( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 direct_access_vendor_id = fan_data_owner_vendor_id + 2000 direct_access_subaccount_id = fan_data_owner_subaccount_id + 2000 artist_of_artist_team = create_reporting_model( FansByArtistAccountDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, fans_count=1000, ) artist_with_direct_access = create_reporting_model( FansByArtistAccountDbt, vendor_id=direct_access_vendor_id, subaccount_id=direct_access_subaccount_id, fans_count=2000, ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=artist_of_artist_team.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=None, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, ), Account( vendor_id=direct_access_vendor_id, subaccount_id=direct_access_subaccount_id, ), ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], search=None, limit=10, offset=0, ) ) assert response.total == 2 # ordered by fans_count desc assert ( response.items[0].global_participant_id == artist_with_direct_access.global_participant_id ) assert ( response.items[1].global_participant_id == artist_of_artist_team.global_participant_id ) @pytest.mark.db def test_get_rosters_zero_fans_7_days_ago( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, fake: FakerTyped, ) -> None: vendor_id = fake.integer() subaccount_id = fake.integer() create_reporting_model( FansByArtistAccountDbt, vendor_id=vendor_id, subaccount_id=subaccount_id, fans_count_7_days_ago=0, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[Account(vendor_id=vendor_id, subaccount_id=subaccount_id)], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert response.items[0].fans_count_7_days_percent_change == 1 @pytest.mark.db def test_get_rosters_zero_fans_28_days_ago( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, ) -> None: create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, fans_count_28_days_ago=0, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert response.items[0].fans_count_28_days_percent_change == 1 @pytest.mark.db def test_get_rosters_zero_fans_6_month_ago( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, ) -> None: create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, fans_count_6_month_ago=0, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 1 assert response.items[0].fans_count_6_month_percent_change == 1 @pytest.mark.db def test_get_rosters_by_countries( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, ) -> None: fan_account = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], countries=[fan_account.country_iso2], limit=10, offset=0, ) ) item_engagement_ratio = round( (fan_account.fans_count / fan_account.engagement_benchmark), 3 ) assert response.total == 1 item = response.items[0] assert item.vendor_id == fan_account.vendor_id assert item.subaccount_id == fan_account.subaccount_id assert item.global_participant_id == fan_account.global_participant_id assert not item.custom_list_id assert item.name == fan_account.global_participant_name assert item.fans_count == fan_account.fans_count assert ( item.fans_count_7_days_change == fan_account.fans_count - fan_account.fans_count_7_days_ago ) assert ( item.fans_count_28_days_change == fan_account.fans_count - fan_account.fans_count_28_days_ago ) assert ( item.fans_count_6_month_change == fan_account.fans_count - fan_account.fans_count_6_month_ago ) assert ( item.fans_count_1_year_change == fan_account.fans_count - fan_account.fans_count_1_year_ago ) assert item.fans_count_7_days_percent_change == IsApprox( round( (fan_account.fans_count - fan_account.fans_count_7_days_ago) / fan_account.fans_count_7_days_ago, 3, ) ) assert item.fans_count_28_days_percent_change == IsApprox( round( (fan_account.fans_count - fan_account.fans_count_28_days_ago) / fan_account.fans_count_28_days_ago, 3, ) ) assert item.fans_count_6_month_percent_change == IsApprox( round( (fan_account.fans_count - fan_account.fans_count_6_month_ago) / fan_account.fans_count_6_month_ago, 3, ) ) assert item.fans_count_1_year_percent_change == IsApprox( round( (fan_account.fans_count - fan_account.fans_count_1_year_ago) / fan_account.fans_count_1_year_ago, 3, ) ) assert item.engagement_benchmark == fan_account.engagement_benchmark assert item.engagement_ratio == IsApprox(item_engagement_ratio) assert item.ad_consent_fans_count == fan_account.ad_consent_fans_count assert item.email_consent_fans_count == fan_account.email_consent_fans_count @pytest.mark.db def test_get_rosters_by_multiple_countries( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, ows_socials_client_mock: mock.MagicMock, identity_id: str, account: Account, ) -> None: item_1 = create_reporting_model( FansByArtistAccountCountryDbt, country_iso2="US", vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, ) item_2 = create_reporting_model( FansByArtistAccountCountryDbt, country_iso2="UK", vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, global_participant_id=item_1.global_participant_id, chartmetric_id=item_1.chartmetric_id, global_participant_name=item_1.global_participant_name, ) create_reporting_model( FansByArtistAccountCountryDbt, country_iso2="AU", vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, global_participant_id=item_1.global_participant_id, chartmetric_id=item_1.chartmetric_id, global_participant_name=item_1.global_participant_name, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], countries=["US", "UK"], limit=10, offset=0, ) ) total_fans_count = item_1.fans_count + item_2.fans_count total_fans_benchmark = item_1.engagement_benchmark + item_2.engagement_benchmark total_fans_count_7_days_ago = ( item_1.fans_count_7_days_ago + item_2.fans_count_7_days_ago ) total_fans_count_28_days_ago = ( item_1.fans_count_28_days_ago + item_2.fans_count_28_days_ago ) total_fans_count_6_month_ago = ( item_1.fans_count_6_month_ago + item_2.fans_count_6_month_ago ) total_fans_count_1_year_ago = ( item_1.fans_count_1_year_ago + item_2.fans_count_1_year_ago ) fans_count_7_days_change = total_fans_count - total_fans_count_7_days_ago fans_count_28_days_change = total_fans_count - total_fans_count_28_days_ago fans_count_6_month_change = total_fans_count - total_fans_count_6_month_ago fans_count_1_year_change = total_fans_count - total_fans_count_1_year_ago fans_count_7_days_percent_change = round( (total_fans_count - total_fans_count_7_days_ago) / total_fans_count_7_days_ago, 3, ) fans_count_28_days_percent_change = round( (total_fans_count - total_fans_count_28_days_ago) / total_fans_count_28_days_ago, 3, ) fans_count_6_month_percent_change = round( (total_fans_count - total_fans_count_6_month_ago) / total_fans_count_6_month_ago, 3, ) fans_count_1_year_percent_change = round( (total_fans_count - total_fans_count_1_year_ago) / total_fans_count_1_year_ago, 3, ) select_country_ratio = round(total_fans_count / total_fans_benchmark, 3) assert response.total == 1 assert response.items[0].fans_count == total_fans_count assert response.items[0].fans_count_7_days_change == fans_count_7_days_change assert response.items[0].fans_count_28_days_change == fans_count_28_days_change assert response.items[0].fans_count_6_month_change == fans_count_6_month_change assert response.items[0].fans_count_1_year_change == fans_count_1_year_change assert response.items[0].fans_count_7_days_percent_change == IsApprox( fans_count_7_days_percent_change ) assert response.items[0].fans_count_28_days_percent_change == IsApprox( fans_count_28_days_percent_change ) assert response.items[0].fans_count_6_month_percent_change == IsApprox( fans_count_6_month_percent_change ) assert response.items[0].fans_count_1_year_percent_change == IsApprox( fans_count_1_year_percent_change ) assert response.items[0].engagement_benchmark == total_fans_benchmark assert response.items[0].engagement_ratio == IsApprox(select_country_ratio) assert ( response.items[0].ad_consent_fans_count == item_1.ad_consent_fans_count + item_2.ad_consent_fans_count ) assert ( response.items[0].email_consent_fans_count == item_1.email_consent_fans_count + item_2.email_consent_fans_count ) @pytest.mark.db def test_get_rosters_with_zero_fans_by_countries( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, build_model: BuildModel, ows_socials_client_mock: mock.MagicMock, ) -> None: socials_stats = build_model(ArtistSocialsStats) fan_account = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=socials_stats.chartmetric_artist_id, fans_count=0, fans_count_7_days_ago=0, fans_count_28_days_ago=0, fans_count_6_month_ago=0, fans_count_1_year_ago=0, ) create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [socials_stats] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], countries=[fan_account.country_iso2], limit=10, offset=0, ) ) item_engagement_ratio = round( (fan_account.fans_count / fan_account.engagement_benchmark), 3 ) assert response.total == 1 item = response.items[0] assert item.vendor_id == fan_account.vendor_id assert item.subaccount_id == fan_account.subaccount_id assert item.global_participant_id == fan_account.global_participant_id assert not item.custom_list_id assert item.name == fan_account.global_participant_name assert item.fans_count == fan_account.fans_count assert ( item.fans_count_7_days_change == fan_account.fans_count - fan_account.fans_count_7_days_ago ) assert ( item.fans_count_28_days_change == fan_account.fans_count - fan_account.fans_count_28_days_ago ) assert ( item.fans_count_6_month_change == fan_account.fans_count - fan_account.fans_count_6_month_ago ) assert ( item.fans_count_1_year_change == fan_account.fans_count - fan_account.fans_count_1_year_ago ) assert item.fans_count_7_days_percent_change == 0 assert item.fans_count_28_days_percent_change == 0 assert item.fans_count_6_month_percent_change == 0 assert item.fans_count_1_year_percent_change == 0 assert item.engagement_benchmark == fan_account.engagement_benchmark assert item.engagement_ratio == IsApprox(item_engagement_ratio) assert item.ad_consent_fans_count == fan_account.ad_consent_fans_count assert item.email_consent_fans_count == fan_account.email_consent_fans_count @pytest.mark.db def test_get_rosters_by_countries_with_artist_team_access( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, ows_socials_client_mock: mock.MagicMock, build_model: BuildModel, fake: FakerTyped, ) -> None: fan_data_owner_vendor_id = fake.integer() fan_data_owner_subaccount_id = fake.integer() artist_team_vendor_id = fan_data_owner_vendor_id + 1000 artist_team_subaccount_id = fan_data_owner_subaccount_id + 1000 country_iso2 = "US" socials_stats = build_model(ArtistSocialsStats) fan_account_country = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, chartmetric_id=socials_stats.chartmetric_artist_id, country_iso2=country_iso2, ) create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=fan_data_owner_vendor_id, subaccount_id=fan_data_owner_subaccount_id, chartmetric_id=socials_stats.chartmetric_artist_id, country_iso2="FR", ) create_reporting_model( AccountArtistFullFanDataAccessDbt, global_participant_id=fan_account_country.global_participant_id, vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, provider_vendor_id=None, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account( vendor_id=artist_team_vendor_id, subaccount_id=artist_team_subaccount_id, ) ], ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [socials_stats] response = handler.handle( GetRostersRequest( identity_id=identity_id, countries=[fan_account_country.country_iso2], order_by=["fansCount.desc"], limit=10, offset=0, ) ) item_engagement_ratio = round( (fan_account_country.fans_count / fan_account_country.engagement_benchmark), 3, ) assert response.total == 1 item = response.items[0] assert item.vendor_id == fan_account_country.vendor_id assert item.subaccount_id == fan_account_country.subaccount_id assert item.chartmetric_artist_id == fan_account_country.chartmetric_id assert item.global_participant_id == fan_account_country.global_participant_id assert item.fans_count == fan_account_country.fans_count assert item.engagement_benchmark == fan_account_country.engagement_benchmark assert item.engagement_ratio == IsApprox(item_engagement_ratio) @pytest.mark.db def test_get_rosters_order_by_socials_stats( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, build_model: BuildModel, ) -> None: artist_1_socials_stats = build_model( ArtistSocialsStats, spotifyMonthlyListeners=None, ) artist_2_socials_stats = build_model( ArtistSocialsStats, spotifyMonthlyListeners=42, ) fan_account_1 = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=artist_1_socials_stats.chartmetric_artist_id, ) fan_account_2 = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=artist_2_socials_stats.chartmetric_artist_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [ artist_1_socials_stats, artist_2_socials_stats, ] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["spotifyMonthlyListeners.desc"], limit=10, offset=0, ) ) assert response.total == 2 # ordered by spotifyMonthlyListeners.desc: 42 > None assert ( response.items[0].global_participant_id == fan_account_2.global_participant_id ) assert ( response.items[0].spotify_monthly_listeners == artist_2_socials_stats.spotify_monthly_listeners ) assert ( response.items[1].global_participant_id == fan_account_1.global_participant_id ) assert ( response.items[1].spotify_monthly_listeners == artist_1_socials_stats.spotify_monthly_listeners ) @pytest.mark.db def test_get_rosters_order_by_socials_stats_with_empty_value( self, handler: GetRostersHandler, create_reporting_model: CreateReportingModel, identity_id: str, account: Account, ows_socials_client_mock: mock.MagicMock, build_model: BuildModel, ) -> None: socials_stats_1 = build_model( ArtistSocialsStats, spotifyMonthlyListeners=10, tiktokFollowers=1000, ) socials_stats_2 = build_model( ArtistSocialsStats, spotifyMonthlyListeners=42, tiktokFollowers=4242, ) socials_stats_3 = build_model( ArtistSocialsStats, spotifyMonthlyListeners=None, tiktokFollowers=None, ) fan_account_1 = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=socials_stats_1.chartmetric_artist_id, ) fan_account_2 = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=socials_stats_2.chartmetric_artist_id, ) fan_account_3 = create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, chartmetric_id=socials_stats_3.chartmetric_artist_id, ) create_reporting_model( FansByArtistAccountDbt, vendor_id=account.vendor_id + 1, subaccount_id=account.subaccount_id + 1, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [ socials_stats_1, socials_stats_2, socials_stats_3, ] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["tiktokFollowers.asc"], limit=10, offset=0, ) ) assert response.total == 3 # ordered by tiktokFollowers.asc: 1000, 4242, None (nulls last) assert ( response.items[0].global_participant_id == fan_account_1.global_participant_id ) assert response.items[0].tiktok_followers == socials_stats_1.tiktok_followers assert ( response.items[1].global_participant_id == fan_account_2.global_participant_id ) assert response.items[1].tiktok_followers == socials_stats_2.tiktok_followers assert ( response.items[2].global_participant_id == fan_account_3.global_participant_id ) assert response.items[2].tiktok_followers == socials_stats_3.tiktok_followers @pytest.mark.db def test_get_rosters_engagement_benchmark_by_multiple_countries( self, handler: GetRostersHandler, build_model: BuildModel, create_reporting_model: CreateReportingModel, ows_socials_client_mock: mock.MagicMock, identity_id: str, account: Account, ) -> None: countries = ["US", "GB", "DE"] socials_stats = build_model( ArtistSocialsStats, spotifyMonthlyListeners=10, tiktokFollowers=1000, ) item_1 = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=account.vendor_id, subaccount_id=account.subaccount_id, country_iso2=countries[0], chartmetric_id=socials_stats.chartmetric_artist_id, ) item_2 = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=item_1.vendor_id, subaccount_id=item_1.subaccount_id, country_iso2=countries[1], global_participant_name=item_1.global_participant_name, global_participant_id=item_1.global_participant_id, chartmetric_id=socials_stats.chartmetric_artist_id, ) item_3 = create_reporting_model( FansByArtistAccountCountryDbt, vendor_id=item_1.vendor_id, subaccount_id=item_1.subaccount_id, country_iso2=countries[2], global_participant_name=item_1.global_participant_name, global_participant_id=item_1.global_participant_id, chartmetric_id=socials_stats.chartmetric_artist_id, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [socials_stats] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], countries=countries, limit=10, offset=0, ) ) total_fan_count = item_1.fans_count + item_2.fans_count + item_3.fans_count total_fan_benchmark = ( item_1.engagement_benchmark + item_2.engagement_benchmark + item_3.engagement_benchmark ) final_engagement_ratio = round((total_fan_count / total_fan_benchmark), 3) assert response.total == 1 item = response.items[0] assert item.vendor_id == item_1.vendor_id assert item.subaccount_id == item_1.subaccount_id assert item.global_participant_id == item_1.global_participant_id assert not item.custom_list_id assert item.name == item_1.global_participant_name assert item.fans_count == total_fan_count assert item.engagement_benchmark == total_fan_benchmark assert item.engagement_ratio == IsApprox(final_engagement_ratio) class TestGetRostersWithGlobalAccessHandler: @pytest.fixture(scope="class") def global_account(self) -> Account: return Account(vendor_id=1, subaccount_id=0) @pytest.fixture(scope="class") def non_global_account(self) -> Account: return Account(vendor_id=2, subaccount_id=0) @pytest.fixture(scope="class", autouse=True) def global_fandata_access_service_mock( self, container: Container, global_account: Account ) -> Iterator[mock.MagicMock]: global_fandata_access_service_mock = mock.MagicMock( spec=GlobalFanDataAccessService, get_enabled_for_any_vendor=mock.Mock( return_value=[global_account.vendor_id] ), ) with container.override( GlobalFanDataAccessService, global_fandata_access_service_mock ): yield global_fandata_access_service_mock @pytest.mark.db def test_get_rosters_empty( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, global_account: Account, identity_id: str, ) -> None: auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_get_rosters( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, global_account: Account, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id = faker.uuid4() chartmetric_id = faker.pyint() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) global_artist = create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, chartmetric_id=chartmetric_id, fans_count=3, ) custom_list_account = create_reporting_model( FansByCustomListAccountDbt, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, fans_count=1, ) # Should not be included in the response create_reporting_model( FansByArtistAccountDbt, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, chartmetric_id=chartmetric_id, fans_count=2, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 2 # Global list item: vendor_id/subaccount_id are None for global access global_item = response.items[0] assert global_item.vendor_id is None assert global_item.subaccount_id is None assert global_item.global_participant_id == global_artist.global_participant_id assert global_item.fans_count == global_artist.fans_count # Custom list item custom_item = response.items[1] assert custom_item.vendor_id == custom_list_account.vendor_id assert custom_item.subaccount_id == custom_list_account.subaccount_id assert custom_item.custom_list_id == custom_list_account.custom_list_id assert custom_item.fans_count == custom_list_account.fans_count @pytest.mark.db def test_get_rosters_main_rep_only( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, global_account: Account, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id_1 = faker.uuid4() global_participant_id_2 = faker.uuid4() chartmetric_id_1 = faker.pyint() chartmetric_id_2 = faker.pyint() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id_1, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) create_reporting_model( ArtistRosterLocalRep, global_participant_id=global_participant_id_2, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) global_artist_1 = create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id_1, chartmetric_id=chartmetric_id_1, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id_2, chartmetric_id=chartmetric_id_2, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], exclude_local_reps=True, limit=10, offset=0, ) ) assert response.total == 1 # Global list item = response.items[0] assert item.vendor_id is None assert item.subaccount_id is None assert item.chartmetric_artist_id == global_artist_1.chartmetric_id assert item.global_participant_id == global_artist_1.global_participant_id @pytest.mark.db def test_get_rosters_multiple_accounts( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, global_account: Account, non_global_account: Account, identity_id: str, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id = faker.uuid4() chartmetric_id = faker.pyint() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account, non_global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) global_artist = create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, chartmetric_id=chartmetric_id, fans_count=3, ) custom_list_account = create_reporting_model( FansByCustomListAccountDbt, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, fans_count=1, ) # Should not be included in the response create_reporting_model( FansByArtistAccountDbt, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, chartmetric_id=chartmetric_id, fans_count=2, ) fan_artist_account = create_reporting_model( FansByArtistAccountDbt, global_participant_id=global_participant_id, vendor_id=non_global_account.vendor_id, subaccount_id=non_global_account.subaccount_id, chartmetric_id=chartmetric_id, fans_count=2, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, ) ) assert response.total == 3 # ordered by fans_count desc: global(3), non_global(2), custom_list(1) assert response.items[0].vendor_id is None assert ( response.items[0].global_participant_id == global_artist.global_participant_id ) assert response.items[1].vendor_id == fan_artist_account.vendor_id assert ( response.items[1].global_participant_id == fan_artist_account.global_participant_id ) assert response.items[2].custom_list_id == custom_list_account.custom_list_id @pytest.mark.db def test_get_rosters_search( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, global_account: Account, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id = faker.uuid4() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) create_reporting_model( GlobalFansByArtistDbt, global_participant_id=global_participant_id, global_participant_name="Global Artist", ) create_reporting_model( FansByCustomListAccountDbt, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, custom_list_name="artist list", ) # Should not be included in the response create_reporting_model( FansByArtistAccountDbt, global_participant_id=global_participant_id, global_participant_name="Artist account", vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, search="artist", ) ) assert response.total == 2 @pytest.mark.db def test_get_rosters_by_country_empty( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, identity_id: str, global_account: Account, ) -> None: auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, countries=["US"], ) ) assert response.total == 0 assert response.items == [] @pytest.mark.db def test_get_rosters_by_country( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, global_account: Account, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id = faker.uuid4() chartmetric_id = faker.pyint() country_code = faker.country_code() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) global_artist = create_reporting_model( GlobalFansByArtistCountryDbt, global_participant_id=global_participant_id, chartmetric_id=chartmetric_id, country_iso2=country_code, fans_count=3, ) custom_list_account = create_reporting_model( FansByCustomListAccountCountryDbt, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, country_iso2=country_code, fans_count=1, ) # Should not be included in the response create_reporting_model( FansByArtistAccountCountryDbt, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, chartmetric_id=chartmetric_id, country_iso2=country_code, fans_count=2, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, countries=[country_code], ) ) assert response.total == 2 # Global list item has vendor_id/subaccount_id as None global_item = response.items[0] assert global_item.vendor_id is None assert global_item.subaccount_id is None assert global_item.global_participant_id == global_artist.global_participant_id assert global_item.fans_count == global_artist.fans_count # Custom list item custom_item = response.items[1] assert custom_item.custom_list_id == custom_list_account.custom_list_id assert custom_item.fans_count == custom_list_account.fans_count @pytest.mark.db def test_get_rosters_by_country_search( self, handler: GetRostersHandler, auth_service_mock: mock.MagicMock, create_reporting_model: CreateReportingModel, identity_id: str, global_account: Account, ows_socials_client_mock: mock.MagicMock, faker: Faker, ) -> None: global_participant_id = faker.uuid4() country_code = faker.country_code() auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[global_account], ) create_reporting_model( ArtistRosterMainRep, global_participant_id=global_participant_id, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, ) create_reporting_model( GlobalFansByArtistCountryDbt, global_participant_id=global_participant_id, global_participant_name="global artist", country_iso2=country_code, ) create_reporting_model( FansByArtistAccountCountryDbt, global_participant_id=global_participant_id, global_participant_name="other", vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, country_iso2=country_code, ) create_reporting_model( FansByCustomListAccountCountryDbt, vendor_id=global_account.vendor_id, subaccount_id=global_account.subaccount_id, custom_list_name="artist list", country_iso2=country_code, ) ows_socials_client_mock.get_stats_by_artists_ids.return_value = [] response = handler.handle( GetRostersRequest( identity_id=identity_id, order_by=["fansCount.desc"], limit=10, offset=0, countries=[country_code], search="artist", ) ) assert response.total == 1