from unittest import mock import pytest from anydi import Container from starlette.testclient import TestClient from dmp.app_connections.enums import AppConnectionStatus from dmp.config import Settings from dmp.core.types import EncryptedToken from dmp.google.dtos import ( GoogleAdAccountOverview, GoogleUser, GoogleUserAdAccountLabel, GoogleUserConnectionAdAccount, GoogleUserConnectionResponse, ) from dmp.google.enums import GoogleUserConnectionStatus from dmp.google.handlers import ( AssignLabelsToGoogleUserAdAccountsHandler, ConnectGoogleUserHandler, DeleteGoogleUserAdAccountHandler, GetGoogleAudienceShareHandler, GetGoogleAudienceShareResponse, GetGoogleUserAdAccountsHandler, GetGoogleUserConnectionHandler, RefreshGoogleAdReportingConnectionHandler, ) from dmp.google.handlers.delete_ad_accounts import DeleteGoogleAdAccountsHandler from dmp.google.handlers.get_ad_accounts import GetGoogleAdAccountsHandler from dmp.google.handlers.get_ad_accounts_v2 import ( GetGoogleAdAccountsV2Handler, GetGoogleAdAccountsV2Request, GetGoogleAdAccountsV2Response, ) from dmp.google.models import GoogleAdReportingConnection from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel def test_connect_google_user( client: TestClient, build_model: BuildModel, fake: FakerTyped, container: Container, settings: Settings, ) -> None: user = build_model(GoogleUser, granted_scopes=settings.google_user_required_scopes) ad_account = build_model(GoogleUserConnectionAdAccount) handler_mock = mock.MagicMock(spec=ConnectGoogleUserHandler) handler_mock.handle.return_value = GoogleUserConnectionResponse( user=user, status=GoogleUserConnectionStatus.CONNECTED, required_scopes=settings.google_user_required_scopes, ad_accounts=[ad_account], ) with container.override(ConnectGoogleUserHandler, handler_mock): response = client.post( "/google/user/connection", json={ "authCode": fake.pystr(), "redirectUri": fake.pystr(), "codeVerifier": fake.pystr(), }, ) assert response.status_code == 200 assert response.json() == { "user": { "id": user.id, "name": user.name, "grantedScopes": user.granted_scopes, }, "status": "CONNECTED", "requiredScopes": settings.google_user_required_scopes, "adAccounts": [ { "id": ad_account.id, "externalId": ad_account.external_id, "name": ad_account.name, } ], "adAccountsCount": 1, } def test_get_google_user_connection( client: TestClient, build_model: BuildModel, container: Container, settings: Settings, ) -> None: user = build_model(GoogleUser) ad_account = build_model(GoogleUserConnectionAdAccount) handler_mock = mock.MagicMock(spec=GetGoogleUserConnectionHandler) handler_mock.handle.return_value = GoogleUserConnectionResponse( user=user, status=GoogleUserConnectionStatus.CONNECTED, required_scopes=settings.google_user_required_scopes, ad_accounts=[ad_account], ) with container.override(GetGoogleUserConnectionHandler, handler_mock): response = client.get( "/google/user/connection", params={ "userId": user.id, }, ) assert response.status_code == 200 assert response.json() == { "user": { "id": user.id, "name": user.name, "grantedScopes": user.granted_scopes, }, "status": "CONNECTED", "requiredScopes": settings.google_user_required_scopes, "adAccounts": [ { "id": ad_account.id, "externalId": ad_account.external_id, "name": ad_account.name, } ], "adAccountsCount": 1, } @pytest.mark.db def test_get_google_user_connection_missing( client: TestClient, build_model: BuildModel, settings: Settings, ) -> None: user = build_model(GoogleUser) response = client.get( "/google/user/connection", params={ "userId": user.id, }, ) assert response.status_code == 200 assert response.json() == { "user": None, "status": "NOT_CONNECTED", "requiredScopes": settings.google_user_required_scopes, "adAccounts": [], "adAccountsCount": 0, } def test_get_user_ad_accounts( client: TestClient, build_model: BuildModel, container: Container ) -> None: user = build_model(GoogleUser) ad_account = build_model(GoogleUserAdAccountLabel) handler_mock = mock.MagicMock(spec=GetGoogleUserAdAccountsHandler) handler_mock.handle.return_value = [ad_account] with container.override(GetGoogleUserAdAccountsHandler, handler_mock): response = client.get( "/google/user/ad-accounts", params={ "userId": user.id, }, ) assert response.status_code == 200 assert response.json() == [ { "id": ad_account.id, "name": ad_account.name, "vendorId": ad_account.vendor_id, "subaccountId": ad_account.subaccount_id, } ] @pytest.mark.db def test_assign_google_user_ad_account_labels( client: TestClient, fake: FakerTyped, container: Container, ) -> None: handler_mock = mock.MagicMock(spec=AssignLabelsToGoogleUserAdAccountsHandler) handler_mock.handle.return_value = None with container.override(AssignLabelsToGoogleUserAdAccountsHandler, handler_mock): response = client.post( "/google/user/ad-accounts", json=[ { "adAccountId": fake.pystr(), "vendorId": 7123, "subaccountId": 0, }, { "adAccountId": fake.pystr(), "vendorId": 7123, "subaccountId": 0, }, ], ) assert response.status_code == 204 @pytest.mark.db def test_assign_google_user_ad_account_labels_validate_unique_ad_account_ids( client: TestClient, fake: FakerTyped, container: Container, ) -> None: handler_mock = mock.MagicMock(spec=AssignLabelsToGoogleUserAdAccountsHandler) handler_mock.handle.return_value = None ad_account_id = fake.pystr() with container.override(AssignLabelsToGoogleUserAdAccountsHandler, handler_mock): response = client.post( "/google/user/ad-accounts", json=[ { "adAccountId": ad_account_id, "vendorId": 7123, "subaccountId": 0, }, { "adAccountId": ad_account_id, "vendorId": 7123, "subaccountId": 0, }, ], ) assert response.status_code == 422 @pytest.mark.db def test_refresh_google_ad_reporting_connection( client: TestClient, container: Container, create_model: CreateModel ) -> None: connection = create_model( GoogleAdReportingConnection, status=AppConnectionStatus.CONNECTED, ) handler_mock = mock.MagicMock(spec=RefreshGoogleAdReportingConnectionHandler) handler_mock.handle.return_value = connection with container.override(RefreshGoogleAdReportingConnectionHandler, handler_mock): response = client.put( "/google/ad-reporting/connection/refresh", json={"fivetranConnectorId": connection.fivetran_connector_id}, ) assert response.status_code == 204 def test_get_google_ad_accounts( client: TestClient, build_model: BuildModel, container: Container, ) -> None: ad_account_1 = build_model(GoogleAdAccountOverview) ad_account_2 = build_model(GoogleAdAccountOverview) handler_mock = mock.MagicMock(spec=GetGoogleAdAccountsHandler) handler_mock.handle.return_value = [ad_account_1, ad_account_2] with container.override(GetGoogleAdAccountsHandler, handler_mock): response = client.get("/google/ad-accounts") assert response.status_code == 200 assert response.json() == [ { "id": ad_account_1.id, "userAdAccountId": ad_account_1.user_ad_account_id, "userId": ad_account_1.user_id, "externalId": ad_account_1.external_id, "name": ad_account_1.name, "vendorId": ad_account_1.vendor_id, "subaccountId": ad_account_1.subaccount_id, "isSelfConnected": ad_account_1.is_self_connected, "userConnectionStatus": ad_account_1.user_connection_status, "userSelfConnected": ad_account_1.user_self_connected, "adReportingConnectionStatus": ad_account_1.ad_reporting_connection_status, "adReportingSelfConnected": ad_account_1.ad_reporting_self_connected, "sharingUserId": ad_account_1.sharing_user_id, "reportingUserId": ad_account_1.reporting_user_id, "campaignsCount": ad_account_1.campaigns_count, }, { "id": ad_account_2.id, "userAdAccountId": ad_account_2.user_ad_account_id, "userId": ad_account_2.user_id, "externalId": ad_account_2.external_id, "name": ad_account_2.name, "vendorId": ad_account_2.vendor_id, "subaccountId": ad_account_2.subaccount_id, "isSelfConnected": ad_account_2.is_self_connected, "userConnectionStatus": ad_account_2.user_connection_status, "userSelfConnected": ad_account_2.user_self_connected, "adReportingConnectionStatus": ad_account_2.ad_reporting_connection_status, "adReportingSelfConnected": ad_account_2.ad_reporting_self_connected, "sharingUserId": ad_account_2.sharing_user_id, "reportingUserId": ad_account_2.reporting_user_id, "campaignsCount": ad_account_2.campaigns_count, }, ] def test_get_google_ad_accounts_v2( client: TestClient, build_model: BuildModel, container: Container, ) -> None: ad_account_1 = build_model(GoogleAdAccountOverview) ad_account_2 = build_model(GoogleAdAccountOverview) handler_mock = mock.MagicMock(spec=GetGoogleAdAccountsV2Handler) handler_mock.handle.return_value = GetGoogleAdAccountsV2Response( total=2, items=[ad_account_1, ad_account_2], ) with container.override(GetGoogleAdAccountsV2Handler, handler_mock): response = client.get("/google/ad-accounts-v2") assert response.status_code == 200 assert response.json() == { "total": 2, "limit": 10, "offset": 0, "items": [ { "id": ad_account_1.id, "userAdAccountId": ad_account_1.user_ad_account_id, "userId": ad_account_1.user_id, "externalId": ad_account_1.external_id, "name": ad_account_1.name, "vendorId": ad_account_1.vendor_id, "subaccountId": ad_account_1.subaccount_id, "isSelfConnected": ad_account_1.is_self_connected, "userConnectionStatus": ad_account_1.user_connection_status, "userSelfConnected": ad_account_1.user_self_connected, "adReportingConnectionStatus": ad_account_1.ad_reporting_connection_status, "adReportingSelfConnected": ad_account_1.ad_reporting_self_connected, "sharingUserId": ad_account_1.sharing_user_id, "reportingUserId": ad_account_1.reporting_user_id, "campaignsCount": ad_account_1.campaigns_count, }, { "id": ad_account_2.id, "userAdAccountId": ad_account_2.user_ad_account_id, "userId": ad_account_2.user_id, "externalId": ad_account_2.external_id, "name": ad_account_2.name, "vendorId": ad_account_2.vendor_id, "subaccountId": ad_account_2.subaccount_id, "isSelfConnected": ad_account_2.is_self_connected, "userConnectionStatus": ad_account_2.user_connection_status, "userSelfConnected": ad_account_2.user_self_connected, "adReportingConnectionStatus": ad_account_2.ad_reporting_connection_status, "adReportingSelfConnected": ad_account_2.ad_reporting_self_connected, "sharingUserId": ad_account_2.sharing_user_id, "reportingUserId": ad_account_2.reporting_user_id, "campaignsCount": ad_account_2.campaigns_count, }, ], } def test_get_google_ad_accounts_v2_with_filters( client: TestClient, build_model: BuildModel, fake: FakerTyped, container: Container, ) -> None: ad_account = build_model(GoogleAdAccountOverview) vendor_id = fake.integer() subaccount_id = fake.integer() handler_mock = mock.MagicMock(spec=GetGoogleAdAccountsV2Handler) handler_mock.handle.return_value = GetGoogleAdAccountsV2Response( total=1, items=[ad_account], ) with container.override(GetGoogleAdAccountsV2Handler, handler_mock): response = client.get( "/google/ad-accounts-v2", params={ "vendorId": vendor_id, "subaccountId": subaccount_id, "limit": 5, "offset": 10, }, ) assert response.status_code == 200 body = response.json() assert body["total"] == 1 assert body["limit"] == 5 assert body["offset"] == 10 assert len(body["items"]) == 1 call_request = handler_mock.handle.call_args[0][0] assert call_request.vendor_id == vendor_id assert call_request.subaccount_id == subaccount_id assert call_request.limit == 5 assert call_request.offset == 10 def test_get_google_ad_accounts_v2_with_order_by( client: TestClient, build_model: BuildModel, container: Container, ) -> None: ad_account = build_model(GoogleAdAccountOverview) handler_mock = mock.MagicMock(spec=GetGoogleAdAccountsV2Handler) handler_mock.handle.return_value = GetGoogleAdAccountsV2Response( total=1, items=[ad_account], ) with container.override(GetGoogleAdAccountsV2Handler, handler_mock): response = client.get( "/google/ad-accounts-v2", params={"orderBy": ["campaignsCount.desc", "name.asc"]}, ) assert response.status_code == 200 call_request = handler_mock.handle.call_args[0][0] assert call_request.order_by == ["campaignsCount.desc", "name.asc"] def test_get_google_ad_accounts_v2_defaults_order_by( client: TestClient, container: Container, ) -> None: handler_mock = mock.MagicMock(spec=GetGoogleAdAccountsV2Handler) handler_mock.handle.return_value = GetGoogleAdAccountsV2Response(total=0, items=[]) with container.override(GetGoogleAdAccountsV2Handler, handler_mock): response = client.get("/google/ad-accounts-v2") assert response.status_code == 200 call_request = handler_mock.handle.call_args[0][0] assert call_request.order_by == GetGoogleAdAccountsV2Request.DEFAULT_ORDER_BY def test_delete_google_user_ad_account( client: TestClient, fake: FakerTyped, container: Container, ) -> None: handler_mock = mock.MagicMock(spec=DeleteGoogleUserAdAccountHandler) handler_mock.handle.return_value = None with container.override(DeleteGoogleUserAdAccountHandler, handler_mock): response = client.delete( f"/google/user/ad-accounts/{fake.pystr()}", params={"userId": fake.pystr()} ) assert response.status_code == 204 def test_delete_tiktok_ad_accounts( client: TestClient, fake: FakerTyped, container: Container, ) -> None: handler_mock = mock.MagicMock(spec=DeleteGoogleAdAccountsHandler) handler_mock.handle.return_value = None with container.override(DeleteGoogleAdAccountsHandler, handler_mock): response = client.delete( f"/google/ad-accounts/{fake.pystr()}", params={ "identityId": fake.uuid4_string(), "vendorId": 7123, "subaccountId": 0, }, ) assert response.status_code == 204 @pytest.mark.db def test_audience_share( client: TestClient, container: Container, fake: FakerTyped ) -> None: share_response = GetGoogleAudienceShareResponse( audience_id=fake.pystr(), audience_external_id=fake.pystr(), user_list_resource=fake.pystr(), audience_name=fake.pystr(), fans_count=fake.integer(), ad_account_external_id=fake.pystr(), parent_ad_account_external_id=fake.pystr(), user_access_token=EncryptedToken(fake.pystr()), ) handler_mock = mock.MagicMock(spec=GetGoogleAudienceShareHandler) handler_mock.handle.return_value = share_response with container.override(GetGoogleAudienceShareHandler, handler_mock): response = client.get(f"/google/audience-shares/{share_response.audience_id}") assert response.status_code == 200 assert response.json() == { "audienceId": share_response.audience_id, "audienceExternalId": share_response.audience_external_id, "userListResource": share_response.user_list_resource, "audienceName": share_response.audience_name, "fansCount": share_response.fans_count, "adAccountExternalId": share_response.ad_account_external_id, "parentAdAccountExternalId": share_response.parent_ad_account_external_id, "userAccessToken": share_response.user_access_token, }