from unittest import mock import pytest from anydi import Container from dirty_equals import IsPartialDataclass from dmp.adapters.google.enums import GoogleAdAccountUserAccessRole from dmp.adapters.google.models import RefreshToken, User, UserAdAccount from dmp.config import Settings from dmp.google.exceptions import GoogleUserConnectionScopesError from dmp.google.handlers import ConnectGoogleUserHandler, ConnectGoogleUserRequest from dmp.google.models import ( GoogleAdAccount, GoogleUserConnection, GoogleUserConnectionAdAccount, ) from dmp.google.repositories import ( GoogleAdAccountRepository, GoogleUserConnectionRepository, ) from dmp.google.services import GoogleAdAccountService from tests.unit.faker import FakerTyped from tests.unit.types import BuildModel, CreateModel class TestConnectGoogleUserHandler: @pytest.mark.db def test_connect_user_with_insufficient_scopes_token( self, handler: ConnectGoogleUserHandler, google_client_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, ) -> None: auth_code = fake.plain_token() google_client_mock.get_refresh_token.return_value = build_model(RefreshToken) with pytest.raises(GoogleUserConnectionScopesError): handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) @pytest.mark.db def test_connect_user_with_sufficient_scopes_token( self, handler: ConnectGoogleUserHandler, user_connection_repository: GoogleUserConnectionRepository, google_client_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, settings: Settings, ) -> None: auth_code = fake.plain_token() google_client_mock.get_refresh_token.return_value = build_model(RefreshToken) user = build_model(User) google_client_mock.get_user.return_value = user google_client_mock.get_refresh_token.return_value = build_model( RefreshToken, scope=" ".join(settings.google_user_required_scopes), ) user_connection_response = handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) assert user_connection_response assert user_connection_response.status == "CONNECTED" assert user_connection_response.ad_accounts == [] assert user_connection_response.ad_accounts_count == 0 user_connection = user_connection_repository.first() assert user_connection assert user_connection.user_id == user.id assert user_connection.identity_id == identity_id @pytest.mark.db def test_connect_user_with_existing_connection_and_new_user_id( self, handler: ConnectGoogleUserHandler, user_connection_repository: GoogleUserConnectionRepository, google_client_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, create_model: CreateModel, settings: Settings, ) -> None: create_model( GoogleUserConnection, identity_id=identity_id, ) auth_code = fake.plain_token() google_client_mock.get_refresh_token.return_value = build_model( RefreshToken, scope=" ".join(settings.google_user_required_scopes), ) user = build_model(User) google_client_mock.get_user.return_value = user user_connection_response = handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) assert user_connection_response assert user_connection_response.status == "CONNECTED" assert user_connection_response.ad_accounts == [] assert user_connection_response.ad_accounts_count == 0 user_connections = user_connection_repository.find_by_identity_id( identity_id=identity_id ) assert user_connections assert len(user_connections) == 2 user_connection = user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user.id ) assert user_connection assert user_connection.user_id == user.id assert user_connection.identity_id == identity_id @pytest.mark.db def test_connect_user_with_ad_accounts( self, container: Container, handler: ConnectGoogleUserHandler, user_connection_repository: GoogleUserConnectionRepository, google_client_mock: mock.MagicMock, ad_account_service_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, settings: Settings, ) -> None: auth_code = fake.plain_token() user = build_model(User) ad_account = build_model(GoogleAdAccount) google_client_mock.get_refresh_token.return_value = build_model(RefreshToken) google_client_mock.get_user.return_value = user google_client_mock.get_refresh_token.return_value = build_model( RefreshToken, scope=" ".join(settings.google_user_required_scopes), ) ad_account_service_mock.get_ad_accounts_for_user_connection.return_value = [ ad_account ] with container.override(GoogleAdAccountService, ad_account_service_mock): user_connection_response = handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) assert user_connection_response assert user_connection_response.status == "CONNECTED" assert user_connection_response user_connection = user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user.id, ) assert user_connection assert user_connection.user_id == user.id assert user_connection.identity_id == identity_id assert user_connection.connection_ad_accounts == [ IsPartialDataclass(ad_account_id=ad_account.id) ] @pytest.mark.db def test_connect_user_with_ad_accounts_updating_existing( self, container: Container, handler: ConnectGoogleUserHandler, user_connection_repository: GoogleUserConnectionRepository, ad_account_repository: GoogleAdAccountRepository, google_client_mock: mock.MagicMock, ad_account_service_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, create_model: CreateModel, settings: Settings, ) -> None: auth_code = fake.plain_token() user = build_model(User) ad_account_1 = create_model(GoogleAdAccount) ad_account_2 = build_model(GoogleAdAccount) create_model( GoogleUserConnection, connection_ad_accounts=[ GoogleUserConnectionAdAccount(ad_account=ad_account_1), ], ) google_client_mock.get_refresh_token.return_value = build_model(RefreshToken) google_client_mock.get_user.return_value = user google_client_mock.get_refresh_token.return_value = build_model( RefreshToken, scope=" ".join(settings.google_user_required_scopes), ) ad_account_service_mock.get_ad_accounts_for_user_connection.return_value = [ ad_account_2 ] with container.override(GoogleAdAccountService, ad_account_service_mock): user_connection_response = handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) assert user_connection_response assert user_connection_response.status == "CONNECTED" assert user_connection_response user_connection = user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user.id, ) assert user_connection assert user_connection.user_id == user.id assert user_connection.identity_id == identity_id assert user_connection.connection_ad_accounts == [ IsPartialDataclass(ad_account_id=ad_account_2.id) ] ad_accounts = ad_account_repository.all() assert ad_accounts assert len(ad_accounts) == 2 assert ad_accounts[0].id == ad_account_1.id assert ad_accounts[1].id == ad_account_2.id @pytest.mark.db def test_saving_parent_ad_account_external_id( self, container: Container, handler: ConnectGoogleUserHandler, user_connection_repository: GoogleUserConnectionRepository, google_client_mock: mock.MagicMock, ad_account_service_mock: mock.MagicMock, identity_id: str, fake: FakerTyped, build_model: BuildModel, settings: Settings, ) -> None: auth_code = fake.plain_token() user = build_model(User) ad_account = build_model(GoogleAdAccount) user_ad_account = build_model( UserAdAccount, id=ad_account.external_id, user_role=GoogleAdAccountUserAccessRole.ADMIN, login_customer_id=fake.pystr(), ) google_client_mock.get_refresh_token.return_value = build_model(RefreshToken) google_client_mock.get_user.return_value = user google_client_mock.get_refresh_token.return_value = build_model( RefreshToken, scope=" ".join(settings.google_user_required_scopes), ) ad_account_service_mock.get_ad_accounts_for_user_connection.return_value = [ ad_account ] ad_account_service_mock.get_api_ad_accounts_for_connection.return_value = [ user_ad_account ] with container.override(GoogleAdAccountService, ad_account_service_mock): handler.handle( ConnectGoogleUserRequest( identity_id=identity_id, auth_code=auth_code, redirect_uri=fake.pystr(), code_verifier=fake.pystr(), ) ) user_connection = user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user.id, ) assert user_connection assert len(user_connection.connection_ad_accounts) == 1 assert user_connection.connection_ad_accounts[0].ad_account == ad_account assert ( user_connection.connection_ad_accounts[0].parent_ad_account_id == user_ad_account.login_customer_id )