from unittest import mock import pytest from fansifter_common.auth.account import Account, AccountAccess from dmp.adapters.features import AUDIENCE_ALLOW_AUDIENCE_SHARING_TO_ALL_AD_ACCOUNTS from dmp.adapters.tiktok.exceptions import TikTokClientError from dmp.adapters.tiktok.models import User from dmp.audiences.enums import ( AudienceExportJustification, AudienceExportStatus, AudienceSharePlatform, ) from dmp.audiences.exceptions import ( AudienceShareActiveConnectionNotFoundError, AudienceTooSmallError, EmptyAudienceError, ) from dmp.audiences.models import Audience, AudienceSnapshot from dmp.tiktok.exceptions import InvalidTikTokAdAccountId from dmp.tiktok.handlers import ShareTikTokAudienceHandler, ShareTikTokAudienceRequest from dmp.tiktok.models import TikTokAdAccount, TikTokUserAdAccount, TikTokUserConnection from dmp.tiktok.repositories import ( TikTokUserConnectionRepository, ) from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel, EnableFeatures class TestShareTikTokAudienceHandler: @pytest.fixture(autouse=True) def mock_tiktok_client( self, tiktok_client_mock: mock.MagicMock, build_model: BuildModel, ) -> None: tiktok_client_mock.get_user.return_value = build_model(User) @pytest.mark.db def test_share_audience( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, ) -> None: justification = AudienceExportJustification.OTHER reason_notes = "test" ad_account = create_model(TikTokAdAccount) audience = create_model(Audience) snapshot = create_model(AudienceSnapshot, audience=audience, fan_count=1001) user_connection = create_model( TikTokUserConnection, is_valid=True, identity_id=identity_id, ad_accounts=[ad_account], ) create_model( TikTokUserAdAccount, ad_account_id=ad_account.id, identity_id=user_connection.identity_id, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) tiktok_audience = handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account.id, justification=justification, reason_notes=reason_notes, ) ) assert tiktok_audience.audience_id == snapshot.audience_id assert tiktok_audience.external_id is None assert tiktok_audience.snapshot_id == snapshot.id assert tiktok_audience.name == f"[FS] {audience.name}" assert tiktok_audience.ad_account_id == ad_account.id assert tiktok_audience.share.status == AudienceExportStatus.PENDING assert tiktok_audience.share.platform == AudienceSharePlatform.TIKTOK assert tiktok_audience.share.justification == justification assert tiktok_audience.share.reason_notes == reason_notes assert tiktok_audience.share.created_by == identity_id @pytest.mark.db def test_share_audience_no_active_user_connection( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, ) -> None: ad_account = create_model(TikTokAdAccount) audience = create_model(Audience) snapshot = create_model(AudienceSnapshot, audience=audience, fan_count=1001) user_connection = create_model( TikTokUserConnection, is_valid=False, identity_id=identity_id, ad_accounts=[ad_account], ) create_model( TikTokUserAdAccount, ad_account_id=ad_account.id, identity_id=user_connection.identity_id, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) with pytest.raises(AudienceShareActiveConnectionNotFoundError) as exc_info: handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account.id, justification=AudienceExportJustification.OTHER, reason_notes="test", ) ) assert exc_info.value.code == "audience_share_active_connection_not_found" assert ( exc_info.value.message == "Active connection for Audience share not found" ) assert exc_info.value.status_code == 404 @pytest.mark.db def test_share_audience_no_user_ad_account( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, fake: FakerTyped, ) -> None: snapshot = create_model(AudienceSnapshot, fan_count=1001) with pytest.raises(InvalidTikTokAdAccountId): handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=fake.uuid4_string(), justification=AudienceExportJustification.OTHER, reason_notes="test", ) ) @pytest.mark.db def test_share_audience_without_snapshot( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, fake: FakerTyped, ) -> None: ad_account_id = fake.pystr() audience = create_model(Audience) with pytest.raises(EmptyAudienceError): handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=audience.id, ad_account_id=ad_account_id, justification=AudienceExportJustification.OTHER, reason_notes="test", ) ) @pytest.mark.db def test_share_audience_with_zero_fans( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, fake: FakerTyped, ) -> None: ad_account_id = fake.pystr() snapshot = create_model(AudienceSnapshot, fan_count=0) with pytest.raises(EmptyAudienceError): handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account_id, justification=AudienceExportJustification.OTHER, reason_notes="test", ) ) @pytest.mark.db def test_share_audience_get_user_error( self, handler: ShareTikTokAudienceHandler, tiktok_client_mock: mock.MagicMock, create_model: CreateModel, tiktok_user_connection_repository: TikTokUserConnectionRepository, identity_id: str, ) -> None: justification = AudienceExportJustification.OTHER reason_notes = "test" ad_account = create_model(TikTokAdAccount) audience = create_model(Audience) snapshot = create_model(AudienceSnapshot, audience=audience, fan_count=1001) user_connection = create_model( TikTokUserConnection, is_valid=True, identity_id=identity_id, ad_accounts=[ad_account], ) create_model( TikTokUserAdAccount, ad_account_id=ad_account.id, identity_id=user_connection.identity_id, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) tiktok_client_mock.get_user.side_effect = TikTokClientError with pytest.raises(AudienceShareActiveConnectionNotFoundError) as exc_info: handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account.id, justification=justification, reason_notes=reason_notes, ) ) assert exc_info.value.status_code == 404 user_connections = tiktok_user_connection_repository.all() user_connection = user_connections[0] assert not user_connection.is_valid @pytest.mark.db def test_share_audience_with_small_audience( self, handler: ShareTikTokAudienceHandler, tiktok_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, identity_id: str, ) -> None: justification = AudienceExportJustification.OTHER reason_notes = "test" ad_account = create_model(TikTokAdAccount) audience = create_model(Audience) snapshot = create_model(AudienceSnapshot, audience=audience, fan_count=999) user_connection = create_model( TikTokUserConnection, is_valid=True, identity_id=identity_id, ad_accounts=[ad_account], ) create_model( TikTokUserAdAccount, ad_account_id=ad_account.id, identity_id=user_connection.identity_id, vendor_id=audience.vendor_id, subaccount_id=audience.subaccount_id, ) tiktok_client_mock.get_user.return_value = build_model(User) with pytest.raises(AudienceTooSmallError): handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account.id, justification=justification, reason_notes=reason_notes, ) ) @pytest.mark.db def test_share_audience_to_an_ad_account_of_different_vendor( self, handler: ShareTikTokAudienceHandler, create_model: CreateModel, identity_id: str, auth_service_mock: mock.MagicMock, enable_features: EnableFeatures, ) -> None: justification = AudienceExportJustification.OTHER reason_notes = "test" audience_vendor_id = 1000 ad_account_vendor_id = 2000 ad_account = create_model(TikTokAdAccount) audience = create_model(Audience, vendor_id=audience_vendor_id, subaccount_id=0) snapshot = create_model(AudienceSnapshot, audience=audience, fan_count=1001) user_connection = create_model( TikTokUserConnection, is_valid=True, identity_id=identity_id, ad_accounts=[ad_account], ) create_model( TikTokUserAdAccount, ad_account_id=ad_account.id, identity_id=user_connection.identity_id, vendor_id=ad_account_vendor_id, subaccount_id=0, ) auth_service_mock.authorize_for_permission.return_value = AccountAccess( accounts=[ Account(vendor_id=audience_vendor_id, subaccount_id=0), Account(vendor_id=ad_account_vendor_id, subaccount_id=0), ], ) with enable_features([AUDIENCE_ALLOW_AUDIENCE_SHARING_TO_ALL_AD_ACCOUNTS]): tiktok_audience = handler.handle( ShareTikTokAudienceRequest( identity_id=identity_id, audience_id=snapshot.audience_id, ad_account_id=ad_account.id, justification=justification, reason_notes=reason_notes, ) ) assert tiktok_audience.audience_id == snapshot.audience_id assert tiktok_audience.external_id is None assert tiktok_audience.snapshot_id == snapshot.id assert tiktok_audience.name == f"[FS] {audience.name}" assert tiktok_audience.ad_account_id == ad_account.id assert tiktok_audience.share.status == AudienceExportStatus.PENDING assert tiktok_audience.share.platform == AudienceSharePlatform.TIKTOK assert tiktok_audience.share.justification == justification assert tiktok_audience.share.reason_notes == reason_notes assert tiktok_audience.share.created_by == identity_id