from unittest import mock import pytest from dirty_equals import IsList from dmp.ad_accounts.enums import AdAccountPlatform from dmp.ad_accounts.models import AdAccountDbt from dmp.adapters.fivetran.enums import ( AdsAccountsSyncMode, ConnectionSetupState, ConnectionSyncState, ) from dmp.adapters.fivetran.models import AdsConnection from dmp.app_connections.enums import AppConnectionStatus from dmp.meta.models import MetaAdAccount, MetaAdReportingConnection, MetaUserConnection from dmp.meta.repositories import MetaAdReportingConnectionRepository from dmp.meta.services import MetaAdReportingConnectionService from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel, CreateReportingModel class TestMetaAdReportingConnectionService: @pytest.mark.db def test_refresh_with_empty_ad_accounts_add_new( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: ad_reporting_connection = create_model(MetaAdReportingConnection) new_ad_account_external_id_1 = fake.pystr() new_ad_account_external_id_2 = fake.pystr() fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.ALL_ACCOUNTS, }, source_sync_details={ "account_ids": [ new_ad_account_external_id_1, new_ad_account_external_id_2, ] }, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": True, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 2 @pytest.mark.db def test_refresh_with_new_add_account( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: existed_ad_account_external_id_1 = fake.pystr() existed_ad_account_external_id_2 = fake.pystr() new_ad_account_external_id = fake.pystr() ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ MetaAdAccount(external_id=existed_ad_account_external_id_1), MetaAdAccount(external_id=existed_ad_account_external_id_2), ], ) fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.ALL_ACCOUNTS, }, source_sync_details={ "account_ids": [ existed_ad_account_external_id_1, existed_ad_account_external_id_2, new_ad_account_external_id, ] }, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": True, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 3 @pytest.mark.db def test_refresh_with_new_add_account_that_already_exist( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: existed_ad_account_external_id_1 = fake.pystr() existed_ad_account_external_id_2 = fake.pystr() existing_ad_account = create_model(MetaAdAccount) ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ MetaAdAccount(external_id=existed_ad_account_external_id_1), MetaAdAccount(external_id=existed_ad_account_external_id_2), ], ) fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.ALL_ACCOUNTS, }, source_sync_details={ "account_ids": [ existed_ad_account_external_id_1, existed_ad_account_external_id_2, existing_ad_account.external_id, ] }, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": True, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 3 @pytest.mark.db def test_delete_ad_account( self, service: MetaAdReportingConnectionService, ad_reporting_connection_repository: MetaAdReportingConnectionRepository, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, ) -> None: ad_account = create_model(MetaAdAccount) ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ad_account, create_model(MetaAdAccount)], ) service.delete_ad_account(ad_reporting_connection, ad_account=ad_account) fivetran_client_mock.delete_connection.assert_not_called() connection = ad_reporting_connection_repository.first() assert connection assert connection.ad_accounts_count == 1 assert connection.ad_accounts[0].id != ad_account.id @pytest.mark.db def test_delete_last_ad_account( self, service: MetaAdReportingConnectionService, ad_reporting_connection_repository: MetaAdReportingConnectionRepository, create_model: CreateModel, ) -> None: ad_account = create_model(MetaAdAccount) ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ad_account] ) service.delete_ad_account(ad_reporting_connection, ad_account=ad_account) connections = ad_reporting_connection_repository.all() assert len(connections) == 0 @pytest.mark.db def test_delete_ad_account_not_linked_to_connection( self, service: MetaAdReportingConnectionService, ad_reporting_connection_repository: MetaAdReportingConnectionRepository, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, ) -> None: ad_account = create_model(MetaAdAccount) ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[create_model(MetaAdAccount)] ) service.delete_ad_account(ad_reporting_connection, ad_account=ad_account) fivetran_client_mock.delete_connection.assert_not_called() connection = ad_reporting_connection_repository.first() assert connection assert connection.ad_accounts_count == 1 @pytest.mark.db def test_delete_ad_account_should_delete_connection_if_ad_accounts_missing( self, service: MetaAdReportingConnectionService, ad_reporting_connection_repository: MetaAdReportingConnectionRepository, create_model: CreateModel, ) -> None: ad_account = create_model(MetaAdAccount) ad_reporting_connection_without_ad_accounts = create_model( MetaAdReportingConnection ) service.delete_ad_account( ad_reporting_connection_without_ad_accounts, ad_account=ad_account ) ad_reporting_connection = ad_reporting_connection_repository.first() assert not ad_reporting_connection @pytest.mark.db def test_refresh_connection_with_new_ad_account_for_existed_connection( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: ad_account_1 = create_model(MetaAdAccount) ad_account_2 = create_model(MetaAdAccount) new_ad_account_external_id = fake.pystr() ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ad_account_1, ad_account_2] ) fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.SPECIFIC_ACCOUNTS, "accounts": [ ad_account_1.external_id, ad_account_2.external_id, new_ad_account_external_id, ], }, source_sync_details={ "account_ids": [ ad_account_1.external_id, ad_account_2.external_id, ] }, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": False, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 3 @pytest.mark.db def test_refresh_connection_with_deleted_ad_account_for_existed_connection( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, ) -> None: ad_account_1 = create_model(MetaAdAccount) ad_account_2 = create_model(MetaAdAccount) ad_reporting_connection = create_model( MetaAdReportingConnection, ad_accounts=[ad_account_1, ad_account_2] ) fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.SPECIFIC_ACCOUNTS, "accounts": [ ad_account_1.external_id, ], }, source_sync_details={ "account_ids": [ ad_account_1.external_id, ad_account_2.external_id, ] }, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": False, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 1 @pytest.mark.db def test_refresh_connection_before_sync_details_for_specific_accounts( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: ad_reporting_connection = create_model(MetaAdReportingConnection) external_id = fake.pystr() external_id_2 = fake.pystr() fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.SPECIFIC_ACCOUNTS, "accounts": [external_id, external_id_2], }, source_sync_details=None, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": False, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 2 @pytest.mark.db def test_refresh_connection_for_incomplete_connection_not_create_ad_accounts( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, fake: FakerTyped, ) -> None: ad_reporting_connection = create_model(MetaAdReportingConnection) external_id = fake.pystr() external_id_2 = fake.pystr() fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.SPECIFIC_ACCOUNTS, "accounts": [external_id, external_id_2], }, source_sync_details=None, status={ "setup_state": ConnectionSetupState.INCOMPLETE, "sync_state": ConnectionSyncState.RESCHEDULED, "is_historical_sync": False, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 0 @pytest.mark.db def test_find_to_notify_on_initial_sync_completion( self, service: MetaAdReportingConnectionService, create_model: CreateModel, create_reporting_model: CreateReportingModel, ) -> None: processing_ad_reporting_connection = create_model( MetaAdReportingConnection, status=AppConnectionStatus.WAITING_FOR_PROCESSING, initial_email_sent=False, ) connected_ad_reporting_connection = create_model( MetaAdReportingConnection, status=AppConnectionStatus.CONNECTED, initial_email_sent=False, ) create_model( MetaAdReportingConnection, status=AppConnectionStatus.WAITING_FOR_PROCESSING, initial_email_sent=False, ) create_reporting_model( AdAccountDbt, platform=AdAccountPlatform.META, source_schema=processing_ad_reporting_connection.fivetran_schema, ) create_reporting_model( AdAccountDbt, platform=AdAccountPlatform.META, source_schema=processing_ad_reporting_connection.fivetran_schema, ) create_reporting_model( AdAccountDbt, platform=AdAccountPlatform.META, source_schema=connected_ad_reporting_connection.fivetran_schema, ) result = service.find_to_notify_on_initial_sync_completion() assert result assert result == IsList( processing_ad_reporting_connection, connected_ad_reporting_connection, check_order=False, ) @pytest.mark.db def test_refresh_connection_sync_all_accounts_initial_sync( self, service: MetaAdReportingConnectionService, fivetran_client_mock: mock.MagicMock, create_model: CreateModel, build_model: BuildModel, identity_id: str, ) -> None: ad_account = create_model(MetaAdAccount) create_model( MetaUserConnection, identity_id=identity_id, ad_accounts=[ad_account] ) ad_reporting_connection = create_model( MetaAdReportingConnection, identity_id=identity_id, status=AppConnectionStatus.WAITING_FOR_PROCESSING, ) fivetran_connection = build_model( AdsConnection, config={ "sync_mode": AdsAccountsSyncMode.ALL_ACCOUNTS, }, source_sync_details=None, status={ "setup_state": ConnectionSetupState.CONNECTED, "sync_state": ConnectionSyncState.SYNCING, "is_historical_sync": True, }, ) fivetran_client_mock.get_ads_connection.return_value = fivetran_connection refreshed_ad_reporting_connection = service.refresh_connection( ad_reporting_connection ) assert refreshed_ad_reporting_connection assert refreshed_ad_reporting_connection.ad_accounts_count == 1 assert refreshed_ad_reporting_connection.ad_accounts[0].id == ad_account.id