"""Test bank details logic.""" from typing import Any from unittest.mock import call, MagicMock, Mock, patch from owsresponse.response import Response import pytest from payee.connectors.secure_data.document import SecureDocument from payee.constants.constants import ( ACCOUNT_PAYEE_ACTION_NAMES, ACTION_STATUSES, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, PAYEE_BANKING_DETAILS_PARTS, ) from payee.constants.error import ERROR_OWS_HEADERS from payee.logic.bank_details import ( get_data_change_parts, manage_payee, purge_bank_details_info, register_whitelabel_profile, reset_banking_states, save_bank_details_info, save_payee_bank_details, set_states, sync_bank_details, ) from payee.logic.exceptions import LogicError from payee.models.account_payee import AccountPayee from payee.models.bank_details import BankDetailsDocument from payee.utils.exception import ( AbacusStateException, RegisterPayeeException, RegisterWhitelabelProfileException, ) from tests.utils.factories import ( AbacusStateFactory, AccountPayeeFactory, BankDetailsIndividualPayeeTypeResponseFactory, BankDetailsIndividualTypeFlattenFactory, BankDetailsIndividualTypeNestedFactory, BankDetailsIndividualTypeResponseFactory, BankDetailsIndividualTypeResponseObscuredFactory, PayeeFactory, ) account_payee_id = 123 payoneer_client_reference_id = '00000000-0000-0000-0000-000000000001' payee_type_account_payee = 'account_payee' payee_type_payee = 'payee' @patch('payee.logic.bank_details.get_data_change_parts') @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.secure_document.flask_request.get_ows_headers') @patch('payee.logic.secure_document.current_timestamp') @patch( 'payee.logic.bank_details.get_payee_details', return_value=None, ) @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.AccountPayee') def test_sync_bank_details_create( mock_account_payee, mock_register_payee, mock_save_bank_details, mock_get_payee_details, mock_current_timestamp, mock_ows_headers, mock_audit_fields, mock_get_account_payee_states, mock_set_states, mock_get_data_change_parts, mock_config, mock_datetime_object, mock_headers, ): """Test successfully creating bank details.""" payee = AccountPayee(account_payee_id=account_payee_id, payoneer_program_id=1) account_payee = AccountPayeeFactory.build(account_payee_id=payee.account_payee_id) mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_account_payee_states.return_value = { 'payment_eligibility': {'action_status': 'init'} } audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_save_bank_details.return_value = mock_secure_doc mock_current_timestamp.return_value = mock_datetime_object mock_ows_headers.return_value = mock_headers mock_audit_fields.return_value = audit_fields params = {**BankDetailsIndividualTypeFlattenFactory.build()} res = sync_bank_details(payee, **params) exp_res = { **BankDetailsIndividualTypeResponseObscuredFactory.build(), **audit_fields, 'payoneer_client_reference_id': str(payee.account_payee_id), 'account_payee_id': payee.account_payee_id, } assert res == exp_res mock_get_account_payee_states.assert_called_once_with( account_payee.account_payee_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_register_payee.assert_called_once_with(payee, params) mock_save_bank_details.assert_called_once_with( payee.payee_entity_id, mock_config.SDM_CONFIG, BankDetailsDocument, **audit_fields, **params, ) mock_set_states.assert_called_once_with( payee.payee_entity_id, mock_get_account_payee_states.return_value, mock_get_data_change_parts.return_value, payee_type_account_payee, ) @patch('payee.logic.bank_details.get_data_change_parts') @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.secure_document.flask_request.get_ows_headers') @patch('payee.logic.secure_document.current_timestamp') @patch( 'payee.logic.bank_details.get_payee_details', return_value=None, ) @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.AccountPayee') def test_sync_payee_bank_details_create( mock_account_payee, mock_register_payee, mock_save_bank_details, mock_get_payee_details, mock_current_timestamp, mock_ows_headers, mock_audit_fields, mock_get_account_payee_states, mock_set_states, mock_get_data_change_parts, mock_config, mock_datetime_object, mock_headers, ): """Test successfully creating bank details.""" payee = PayeeFactory.build( payoneer_client_reference_id=payoneer_client_reference_id ) mock_account_payee.get_by_id_or_error.return_value = payee mock_get_account_payee_states.return_value = { 'payment_eligibility': {'action_status': 'init'} } audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_save_bank_details.return_value = mock_secure_doc mock_current_timestamp.return_value = mock_datetime_object mock_ows_headers.return_value = mock_headers mock_audit_fields.return_value = audit_fields params = {**BankDetailsIndividualTypeFlattenFactory.build()} res = sync_bank_details(payee, **params) exp_res = { **BankDetailsIndividualPayeeTypeResponseFactory.build(), **audit_fields, } assert res == exp_res mock_get_account_payee_states.assert_called_once_with( payee.payee_entity_id, payee_type_payee ) mock_get_payee_details.assert_called_once_with( payee.payoneer_program_id, payee.payoneer_client_reference_id ) mock_register_payee.assert_called_once_with(payee, params) mock_save_bank_details.assert_called_once_with( payee.payoneer_client_reference_id, mock_config.SDM_CONFIG, BankDetailsDocument, **audit_fields, **params, ) mock_set_states.assert_called_once_with( payee.payee_entity_id, mock_get_account_payee_states.return_value, mock_get_data_change_parts.return_value, payee_type_payee, ) @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch( 'payee.logic.bank_details.get_payee_details', return_value=None, ) @patch('payee.logic.secure_document.flask_request.get_ows_headers', return_value={}) @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.AccountPayee') def test_sync_bank_details_create_error( mock_account_payee, mock_register_payee, mock_get_headers, mock_get_payee_details, mock_get_account_payee_states, mock_set_states, mock_config, ): """Test getting error on saving data.""" params = BankDetailsIndividualTypeResponseFactory.build() account_payee = AccountPayeeFactory.build(account_payee_id=account_payee_id) mock_account_payee.get_by_id_or_error.return_value = account_payee with pytest.raises(LogicError) as excinfo: sync_bank_details(account_payee, **params) assert str(excinfo.value) == ERROR_OWS_HEADERS mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_set_states.assert_not_called() @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.manage_payee') @patch('payee.logic.bank_details.AccountPayee') def test_sync_bank_details_status_error( mock_account_payee, mock_manage_payee, mock_get_account_payee_states, mock_set_states, ): params = BankDetailsIndividualTypeResponseFactory.build() account_payee = AccountPayeeFactory.build( account_payee_id=params['account_payee_id'] ) mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_account_payee_states.side_effect = AbacusStateException('test error') with pytest.raises(AbacusStateException): sync_bank_details( account_payee, account_payee_id=account_payee.account_payee_id, ) mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_manage_payee.assert_not_called() mock_set_states.assert_not_called() @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_data_change_parts') @patch('payee.logic.bank_details.manage_payee') @patch('payee.logic.bank_details.save_bank_details_info') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') def test_sync_bank_details_success( mock_build_response, mock_save_bank_details_info, mock_manage_payee, mock_get_data_change_parts, mock_get_account_payee_states, mock_set_states, ): payee = AccountPayee(account_payee_id=account_payee_id) params = { **BankDetailsIndividualTypeFlattenFactory.build(), } mock_affected_states = ['state1', 'state2'] mock_get_data_change_parts.return_value = mock_affected_states mock_secure_document = MagicMock() mock_save_bank_details_info.return_value = mock_secure_document mock_message = '{imagine: bank details json}' mock_build_response.return_value = mock_message result = sync_bank_details(payee, **params) mock_get_data_change_parts.assert_called_once_with(payee.account_payee_id, params) mock_get_account_payee_states.assert_called_once_with( payee.payee_entity_id, payee_type_account_payee ) mock_manage_payee.assert_called_once_with( payee, mock_affected_states, params, mock_get_account_payee_states.return_value, ) mock_save_bank_details_info.assert_called_once_with(payee.account_payee_id, params) mock_build_response.assert_called_once_with( payee.account_payee_id, mock_secure_document ) mock_set_states.assert_called_once_with( payee.payee_entity_id, mock_get_account_payee_states.return_value, mock_get_data_change_parts.return_value, payee_type_account_payee, ) assert result == mock_message @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.secure_document.flask_request.get_ows_headers', return_value={}) @patch('payee.logic.bank_details.update_payout_methods') @patch('payee.logic.bank_details.AccountPayee') def test_sync_bank_details_update_error( mock_account_payee, mock_update_payout_methods, mock_get_headers, mock_get_payee_details, mock_has_bank_details, mock_get_account_payee_states, mock_set_states, mock_config, ): """Test getting error on updating data.""" params = BankDetailsIndividualTypeFlattenFactory.build() account_payee = AccountPayeeFactory.build() mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_payee_details.return_value = BankDetailsIndividualTypeNestedFactory.build() audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_has_bank_details.return_value = mock_secure_doc with pytest.raises(LogicError) as excinfo: sync_bank_details(account_payee, **params) assert str(excinfo.value) == ERROR_OWS_HEADERS mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_set_states.assert_not_called() @pytest.mark.parametrize('is_name_changed', (True, False)) @patch('payee.logic.bank_details.set_states') @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.secure_document.flask_request.get_ows_headers') @patch('payee.logic.secure_document.current_timestamp') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_payout_methods') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.AccountPayee') @patch('payee.logic.bank_details._is_account_payee', return_value=True) def test_sync_bank_details_update_changed_name_or_country( mock__is_account_payee, mock_account_payee, mock_register_payee, mock_update_payout_methods, mock_release_payee, mock_save_bank_details, mock_has_bank_details, mock_get_payee_details, mock_get_account_payee_states, mock_current_timestamp, mock_ows_headers, mock_audit_fields, mock_set_states, mock_config, mock_datetime_object, mock_headers, is_name_changed: bool, ): """Test successfully updating bank details.""" payee = AccountPayee(account_payee_id=account_payee_id, payoneer_program_id=1) account_payee = AccountPayeeFactory.build(account_payee_id=payee.account_payee_id) mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'approved'} } audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_existing_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build( **( dict(first_name='old first_name', last_name='old last_name') if is_name_changed else dict(country_code='GB') ) ), **audit_fields, } ) mock_get_payee_details.return_value = BankDetailsIndividualTypeNestedFactory.build( **( dict( contact__first_name='old first_name', contact__last_name='old last_name' ) if is_name_changed else dict(address__country_code='GB') ) ) mock_has_bank_details.return_value = mock_existing_secure_doc mock_save_bank_details.return_value = mock_secure_doc mock_current_timestamp.return_value = mock_datetime_object mock_ows_headers.return_value = mock_headers mock_audit_fields.return_value = audit_fields params = { **BankDetailsIndividualTypeFlattenFactory.build(), } res = sync_bank_details(payee, **params) exp_res = { **BankDetailsIndividualTypeResponseObscuredFactory.build(), **audit_fields, 'payoneer_client_reference_id': str(payee.account_payee_id), 'account_payee_id': payee.account_payee_id, } assert res == exp_res mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_update_payout_methods.assert_not_called() mock_register_payee.assert_called_once_with(payee, params) mock_release_payee.assert_called_once_with( account_payee.payoneer_program_id, payee.account_payee_id ) mock_save_bank_details.assert_called_once_with( payee.account_payee_id, mock_config.SDM_CONFIG, BankDetailsDocument, **audit_fields, **params, ) mock_set_states.assert_called_once_with( payee.payee_entity_id, mock_get_account_payee_states.return_value, {PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION} if is_name_changed else {PAYEE_BANKING_DETAILS_PARTS.COUNTRY_CODE}, payee_type_account_payee, ) @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.secure_document.flask_request.get_ows_headers') @patch('payee.logic.secure_document.current_timestamp') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.release_payee', return_value=Response(status=500)) @patch('payee.logic.bank_details.update_payout_methods') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.AccountPayee') def test_sync_bank_details_update_changed_name_release_error( mock_account_payee, mock_register_payee, mock_update_payout_methods, mock_release_payee, mock_save_bank_details, mock_has_bank_details, mock_get_payee_details, mock_get_account_payee_states, mock_current_timestamp, mock_ows_headers, mock_audit_fields, mock_config, mock_datetime_object, mock_headers, ): """Test successfully updating bank details.""" payee = AccountPayee(account_payee_id=account_payee_id) account_payee = AccountPayeeFactory.build(account_payee_id=payee.account_payee_id) mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'approved'} } audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_existing_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build( first_name='old first_name', last_name='old last_name', ), **audit_fields, } ) mock_get_payee_details.return_value = BankDetailsIndividualTypeNestedFactory.build( contact__first_name='old first_name', contact__last_name='old last_name', ) mock_has_bank_details.return_value = mock_existing_secure_doc mock_save_bank_details.return_value = mock_secure_doc mock_current_timestamp.return_value = mock_datetime_object mock_ows_headers.return_value = mock_headers mock_audit_fields.return_value = audit_fields params = { **BankDetailsIndividualTypeFlattenFactory.build(), } with pytest.raises(LogicError, match='Error releasing payee on update'): sync_bank_details(payee, **params) mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_update_payout_methods.assert_not_called() mock_register_payee.assert_not_called() mock_release_payee.assert_called_once_with(payee.account_payee_id) mock_save_bank_details.assert_not_called() @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.secure_document.flask_request.get_ows_headers') @patch('payee.logic.secure_document.current_timestamp') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_payout_methods') @patch( 'payee.logic.bank_details.register_payee', side_effect=RegisterPayeeException('test'), ) @patch('payee.logic.bank_details.AccountPayee') @patch('payee.logic.bank_details._is_account_payee', return_value=True) def test_sync_bank_details_update_changed_name_release_error( mock__is_account_payee, mock_account_payee, mock_register_payee, mock_update_payout_methods, mock_release_payee, mock_save_bank_details, mock_has_bank_details, mock_get_payee_details, mock_get_account_payee_states, mock_current_timestamp, mock_ows_headers, mock_audit_fields, mock_config, mock_datetime_object, mock_headers, ): """Test successfully updating bank details.""" payee = AccountPayee(account_payee_id=account_payee_id, payoneer_program_id=1) account_payee = AccountPayeeFactory.build(account_payee_id=payee.account_payee_id) mock_account_payee.get_by_id_or_error.return_value = account_payee mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'rejected'} } audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **audit_fields, } ) mock_existing_secure_doc = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build( first_name='old first_name', last_name='old last_name', ), **audit_fields, } ) mock_get_payee_details.return_value = BankDetailsIndividualTypeNestedFactory.build( contact__first_name='old first_name', contact__last_name='old last_name', ) mock_has_bank_details.return_value = mock_existing_secure_doc mock_save_bank_details.return_value = mock_secure_doc mock_current_timestamp.return_value = mock_datetime_object mock_ows_headers.return_value = mock_headers mock_audit_fields.return_value = audit_fields params = { **BankDetailsIndividualTypeFlattenFactory.build(), } with pytest.raises(RegisterPayeeException, match='test'): sync_bank_details(payee, **params) mock_get_account_payee_states.assert_called_once_with( account_payee.payee_entity_id, payee_type_account_payee ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_update_payout_methods.assert_not_called() mock_register_payee.assert_called_once_with(payee, params) mock_release_payee.assert_called_once_with( account_payee.payoneer_program_id, payee.account_payee_id ) mock_save_bank_details.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_register_new_payee( mock_update_payout_methods, mock_update_profile, mock_payoneer_release, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_states = set() params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_get_payee_details.return_value = False manage_payee( account_payee, affected_states, params, mock_get_account_payee_states.return_value, ) mock_register_payee.assert_called_once_with(account_payee, params) mock_update_payout_methods.assert_not_called() mock_update_profile.assert_not_called() mock_payoneer_release.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_personal_information( mock_update_payout_methods, mock_update_profile, mock_payoneer_release, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_states = {PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION} params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_get_payee_details.return_value = True mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'approved'} } mock_payoneer_release.return_value = True manage_payee( account_payee, affected_states, params, mock_get_account_payee_states.return_value, ) mock_payoneer_release.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_register_payee.assert_called_once_with(account_payee, params) mock_update_profile.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_rejected_payee( mock_update_payout_methods: Mock, mock_update_profile: Mock, mock_payoneer_release: Mock, mock_register_payee: Mock, mock_get_payee_details: Mock, mock_get_account_payee_states: Mock, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_states = {PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION} params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_get_payee_details.return_value = True mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'rejected'} } mock_payoneer_release.return_value = True manage_payee( account_payee, affected_states, params, mock_get_account_payee_states.return_value, ) mock_payoneer_release.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_register_payee.assert_called_once_with(account_payee, params) mock_update_profile.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_personal_information_error( mock_update_payout_methods, mock_update_profile, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_states = [PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION] params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_get_payee_details.return_value = True mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'running'} } with pytest.raises(LogicError): manage_payee( account_payee, affected_states, params, mock_get_account_payee_states.return_value, ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_register_payee.assert_not_called() mock_update_profile.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_address_information( mock_update_payout_methods, mock_update_profile, mock_payoneer_release, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_states = {PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION} params = {'address': 'New Address'} mock_get_payee_details.return_value = True mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'approved'} } manage_payee( account_payee, affected_states, params, mock_get_account_payee_states.return_value, ) mock_update_profile.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id, params ) mock_register_payee.assert_not_called() mock_payoneer_release.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_address_information_pending_error( mock_update_payout_methods, mock_update_profile, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_parts = [PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION] params = {'address': 'New Address'} mock_get_payee_details.return_value = Response(status=200) mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'running'} } with pytest.raises(LogicError): manage_payee( account_payee, affected_parts, params, mock_get_account_payee_states.return_value, ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_update_profile.assert_not_called() mock_register_payee.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_address_information_response_error( mock_update_payout_methods, mock_update_profile, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_parts = [PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION] params = {'address': 'New Address'} mock_get_payee_details.return_value = Response(status=200) mock_get_account_payee_states.return_value = {} with pytest.raises(LogicError): manage_payee( account_payee, affected_parts, params, mock_get_account_payee_states.return_value, ) mock_get_payee_details.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id ) mock_update_profile.assert_not_called() mock_register_payee.assert_not_called() mock_update_payout_methods.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_update_banking_information( mock_update_payout_methods, mock_update_profile, mock_payoneer_release, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_parts = {PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION} params = {'bank_name': 'Updated Bank', 'account_number': '987654321'} mock_get_payee_details.return_value = True manage_payee( account_payee, affected_parts, params, mock_get_account_payee_states.return_value, ) mock_update_payout_methods.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id, params ) mock_register_payee.assert_not_called() mock_payoneer_release.assert_not_called() mock_update_profile.assert_not_called() @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.get_payee_details') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.release_payee') @patch('payee.logic.bank_details.update_profile') @patch('payee.logic.bank_details.update_payout_methods') def test_manage_payee_banking_and_address_information( mock_update_payout_methods, mock_update_profile, mock_release_payee, mock_register_payee, mock_get_payee_details, mock_get_account_payee_states, ): account_payee = AccountPayeeFactory.build(account_payee_id=123) affected_parts = { PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION, } params = { 'bank_name': 'Updated Bank', 'account_number': '987654321', 'address': 'New Address', } mock_get_payee_details.return_value = True mock_get_account_payee_states.return_value = { 'banking_details_review': {'action_status': 'approved'} } manage_payee( account_payee, affected_parts, params, mock_get_account_payee_states.return_value, ) mock_update_profile.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id, params ) mock_update_payout_methods.assert_called_once_with( account_payee.payoneer_program_id, account_payee.account_payee_id, params ) mock_register_payee.assert_not_called() mock_release_payee.assert_not_called() @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payee_address') @patch('payee.logic.bank_details.BankDetailsDataHelper.get_email') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_no_secure_details( mock_build_payout_method, mock_get_email, mock_build_payee_address, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'new_email@example.com', 'address': 'New Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = False result = get_data_change_parts(account_payee_id, new_data) expected_states = { PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME, PAYEE_BANKING_DETAILS_PARTS.COUNTRY_CODE, } assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_personal_information_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'new first name', 'last_name': 'new last name', 'date_of_birth': '2000-01-01', 'email': 'email@example.com', 'country_code': 'US', 'address_1': 'Address', 'city': 'City', 'province': 'Province', 'zip': '0011', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'old first name', 'last_name': 'old last name', 'date_of_birth': '1999-12-31', 'email': 'email@example.com', }, 'address': { 'country_code': 'US', 'address_1': 'Address', 'city': 'City', 'province': 'Province', 'zip': '0011', }, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION} assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_country_code_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'email@example.com', 'country_code': 'US', 'address_1': 'Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'email@example.com', }, 'address': { 'country_code': 'GB', 'address_1': 'Address', }, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.COUNTRY_CODE} assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_address_information_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'address_1': 'New Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', }, 'address': {'address_1': 'Old Address'}, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION} assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_address_information_email_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'new_email@example.com', 'address_1': 'New Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', }, 'address': {'address_1': 'Old Address'}, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION} assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_address_information_phone_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'phone': '4567890', 'phone_country': 'US', 'address_1': 'Old Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'phone': '87654321', 'phone_country': 'US', }, 'address': {'address_1': 'Old Address'}, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION} assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_address_account_name_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'address_1': 'Old Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', }, 'address': {'address_1': 'Old Address'}, 'payout_method': { 'bank_field_details': [{'name': 'AccountName', 'value': 'test name'}] }, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = { PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME, } assert result == expected_states @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_no_changes( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'address_1': 'Old Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', }, 'address': {'address_1': 'Old Address'}, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = set() assert result == expected_states @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.current_app.config.get') @patch('payee.logic.bank_details.get_audit_fields') def test_save_bank_details_info_success( mock_get_audit_fields, mock_config_get, mock_save_secure_document_details ): payee = MagicMock(spec=AccountPayee) params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_secure_document = MagicMock(spec=SecureDocument) mock_save_secure_document_details.return_value = mock_secure_document mock_config_get.return_value = 'mock_sdm_config' audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_get_audit_fields.return_value = audit_fields result = save_bank_details_info(payee, params) mock_save_secure_document_details.assert_called_once_with( payee, 'mock_sdm_config', BankDetailsDocument, **audit_fields, bank_name='Test Bank', account_number='123456789', ) assert result == mock_secure_document @patch('payee.logic.bank_details.save_secure_document_details') @patch('payee.logic.bank_details.current_app.config.get') @patch('payee.logic.bank_details.get_audit_fields') @patch('payee.logic.bank_details.logger') def test_save_bank_details_info_failure( mock_logger, mock_get_audit_fields, mock_config_get, mock_save_secure_document_details, ): payee = MagicMock(spec=AccountPayee) params = {'bank_name': 'Test Bank', 'account_number': '123456789'} mock_config_get.return_value = 'mock_sdm_config' audit_fields = { 'modified_at': '2022-05-04T12:16:57.474487', 'modified_by_identity': 'aksjKJASD66340ooOHSDA)D!d1s', 'modified_by_profile_id': '90000020', 'modified_by_profile_type': 'AbacusProfile', } mock_get_audit_fields.return_value = audit_fields mock_save_secure_document_details.side_effect = Exception('Some error occurred') with pytest.raises(LogicError): save_bank_details_info(payee, params) mock_logger.error.assert_called_once_with( 'Error when saving payee: Some error occurred' ) @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.save_bank_details_info') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') def test_save_bank_details_success( mock_build_response, mock_save_bank_details_info, mock_has_bank_details, ): payee = AccountPayee(account_payee_id=account_payee_id) params = { **BankDetailsIndividualTypeFlattenFactory.build(), 'account_payee_id': payee.account_payee_id, } mock_has_bank_details.return_value = None mock_secure_document = MagicMock() mock_save_bank_details_info.return_value = mock_secure_document mock_message = '{imagine: bank details json}' mock_build_response.return_value = mock_message result = save_payee_bank_details(**params) mock_save_bank_details_info.assert_called_once_with(payee.account_payee_id, params) mock_build_response.assert_called_once_with( payee.account_payee_id, mock_secure_document ) assert result == mock_message @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.save_bank_details_info') @patch('payee.logic.bank_details.AccountPayee') def test_save_bank_details_error( mock_account_payee, mock_save_bank_details_info, mock_has_bank_details, mock_config, ): """Test getting error on updating data.""" params = BankDetailsIndividualTypeFlattenFactory.build() account_payee = AccountPayeeFactory.build() mock_account_payee.get_by_id_or_error.return_value = account_payee mock_secure_document = MagicMock() mock_has_bank_details.return_value = mock_secure_document with pytest.raises(LogicError) as excinfo: save_payee_bank_details(**params) assert str(excinfo.value) == 'Payee bank details already exists' mock_save_bank_details_info.assert_not_called() @patch('payee.logic.bank_details.purge_secure_document_details') def test_purge_bank_details_info(mock_purge_secure_document_details): account_payee_id = 123 purge_bank_details_info(account_payee_id) mock_purge_secure_document_details.assert_called_once_with( account_payee_id, BankDetailsDocument ) @pytest.mark.parametrize( 'affected_parts', ( { PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME, }, {PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION}, {PAYEE_BANKING_DETAILS_PARTS.COUNTRY_CODE}, {}, ), ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_create_review( mock_create_payee_states, mock_update_state, affected_parts ): accout_payee_id = 123 eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY ) states = {ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state} set_states(accout_payee_id, states, affected_parts, payee_type_account_payee) assert mock_create_payee_states.call_args_list == [ call( accout_payee_id, payee_type_account_payee, [ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW], ) ] if affected_parts: assert mock_update_state.call_args_list == [ call( eligibility_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ) ] else: mock_update_state.assert_not_called() @pytest.mark.parametrize( 'affected_parts,is_rejected', ( ({PAYEE_BANKING_DETAILS_PARTS.PERSONAL_INFORMATION}, False), ({PAYEE_BANKING_DETAILS_PARTS.COUNTRY_CODE}, False), ({PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION}, True), ), ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_update_review_and_eligibility( mock_create_payee_states: Mock, mock_update_state: Mock, affected_parts: str, is_rejected: bool, ): accout_payee_id = 123 review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.REJECTED if is_rejected else ACTION_STATUSES.APPROVED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states(accout_payee_id, states, affected_parts, payee_type_account_payee) mock_create_payee_states.assert_not_called() assert mock_update_state.call_args_list == [ call( review_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), call( eligibility_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] @pytest.mark.parametrize( 'review_status', (ACTION_STATUSES.APPROVED, ACTION_STATUSES.INIT) ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_update_only_eligibility( mock_create_payee_states: Mock, mock_update_state: Mock, review_status: str ): accout_payee_id = 123 review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=review_status, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( accout_payee_id, states, {PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() if review_status == ACTION_STATUSES.APPROVED: assert mock_update_state.call_args_list == [ call( eligibility_state['abacus_state_id'], ACTION_STATUSES.RUNNING, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] else: mock_update_state.assert_not_called() @pytest.mark.parametrize( 'changed_part', ( PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION, PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION, ), ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_update_only_eligibility_if_previously_rejected( mock_create_payee_states: Mock, mock_update_state: Mock, changed_part: str ): accout_payee_id = 123 review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.APPROVED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY, action_status=ACTION_STATUSES.REJECTED, ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( accout_payee_id, states, {changed_part}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() assert mock_update_state.call_args_list == [ call( eligibility_state['abacus_state_id'], ACTION_STATUSES.RUNNING, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] @patch('payee.logic.bank_details.is_awal_account') @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') @patch('payee.logic.bank_details.is_tap_awal_payment_eligibility_auto_approve_enabled') def test_set_states_update_only_eligibility_awal_account_re_approved( mock_is_feature_enabled: Mock, mock_create_payee_states: Mock, mock_update_state: Mock, mock_is_awal_account: Mock, ): """AWAL payees previously rejected are re-approved instead of restarted.""" account_payee = AccountPayeeFactory.create() mock_is_feature_enabled.return_value = True mock_is_awal_account.return_value = True review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.APPROVED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY, action_status=ACTION_STATUSES.REJECTED, ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( account_payee.account_payee_id, states, {PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() mock_is_awal_account.assert_called_once_with(account_payee.account_id) assert mock_update_state.call_args_list == [ call( eligibility_state['abacus_state_id'], ACTION_STATUSES.APPROVED, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] @patch('payee.logic.bank_details.is_awal_account') @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') @patch('payee.logic.bank_details.is_tap_awal_payment_eligibility_auto_approve_enabled') def test_set_states_update_only_eligibility_awal_account_already_approved( mock_is_feature_enabled: Mock, mock_create_payee_states: Mock, mock_update_state: Mock, mock_is_awal_account: Mock, ): """AWAL payees already approved are not redundantly re-approved.""" account_payee = AccountPayeeFactory.create() mock_is_feature_enabled.return_value = True mock_is_awal_account.return_value = True review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.APPROVED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY, action_status=ACTION_STATUSES.APPROVED, ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( account_payee.account_payee_id, states, {PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() mock_is_awal_account.assert_called_once_with(account_payee.account_id) mock_update_state.assert_not_called() @patch('payee.logic.bank_details.is_awal_account') @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') @patch('payee.logic.bank_details.is_tap_awal_payment_eligibility_auto_approve_enabled') def test_set_states_update_only_eligibility_awal_account_feature_disabled( mock_is_feature_enabled: Mock, mock_create_payee_states: Mock, mock_update_state: Mock, mock_is_awal_account: Mock, ): """AWAL auto-approve is skipped when the feature flag is disabled.""" account_payee = AccountPayeeFactory.create() mock_is_feature_enabled.return_value = False mock_is_awal_account.return_value = True review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.APPROVED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY, action_status=ACTION_STATUSES.REJECTED, ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( account_payee.account_payee_id, states, {PAYEE_BANKING_DETAILS_PARTS.BANKING_INFORMATION}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() mock_is_awal_account.assert_not_called() assert mock_update_state.call_args_list == [ call( eligibility_state['abacus_state_id'], ACTION_STATUSES.RUNNING, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_update_rejected_payee( mock_create_payee_states: Mock, mock_update_state: Mock ): accout_payee_id = 123 review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW, action_status=ACTION_STATUSES.REJECTED, ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY, action_status=ACTION_STATUSES.RUNNING, ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } set_states( accout_payee_id, states, {PAYEE_BANKING_DETAILS_PARTS.ACCOUNT_HOLDER_NAME}, payee_type_account_payee, ) mock_create_payee_states.assert_not_called() assert mock_update_state.call_args_list == [ call( review_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), call( eligibility_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ), ] @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.create_payee_states') def test_set_states_error_eligibility(mock_create_payee_states, mock_update_state): accout_payee_id = 123 review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW ) states = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, } with pytest.raises(LogicError, match='Inconsistent payee state'): set_states(accout_payee_id, states, [], payee_type_account_payee) assert not mock_create_payee_states.called @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.get_payee_states') def test_reset_banking_states_both_states_exist( mock_get_payee_states: Mock, mock_update_state: Mock, ): """Test reset_banking_states when both states exist.""" review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW ) eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY ) mock_get_payee_states.return_value = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } reset_banking_states(account_payee_id) mock_get_payee_states.assert_called_once_with(account_payee_id) assert mock_update_state.call_count == 2 mock_update_state.assert_any_call( review_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ) mock_update_state.assert_any_call( eligibility_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.get_payee_states') def test_reset_banking_states_no_states( mock_get_payee_states: Mock, mock_update_state: Mock, ): """Test reset_banking_states when states don't exist (no-op).""" mock_get_payee_states.return_value = {} reset_banking_states(account_payee_id) mock_get_payee_states.assert_called_once_with(account_payee_id) mock_update_state.assert_not_called() @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.get_payee_states') def test_reset_banking_states_only_review_state( mock_get_payee_states: Mock, mock_update_state: Mock, ): """Test reset_banking_states when only review state exists.""" review_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW ) mock_get_payee_states.return_value = { ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: review_state, } reset_banking_states(account_payee_id) mock_get_payee_states.assert_called_once_with(account_payee_id) mock_update_state.assert_called_once_with( review_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ) @patch('payee.logic.bank_details.update_state') @patch('payee.logic.bank_details.get_payee_states') def test_reset_banking_states_only_eligibility_state( mock_get_payee_states: Mock, mock_update_state: Mock, ): """Test reset_banking_states when only eligibility state exists.""" eligibility_state = AbacusStateFactory.build( action_name=ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY ) mock_get_payee_states.return_value = { ACCOUNT_PAYEE_ACTION_NAMES.PAYMENT_ELIGIBILITY: eligibility_state, } reset_banking_states(account_payee_id) mock_get_payee_states.assert_called_once_with(account_payee_id) mock_update_state.assert_called_once_with( eligibility_state['abacus_state_id'], ACTION_STATUSES.INIT, BANK_DETAILS_REGISTRATION_STATE_MESSAGE, ) @pytest.mark.parametrize( 'has_account_payee,bank_details_state,has_bank_details', ( (True, None, True), (True, ACTION_STATUSES.INIT, True), (True, ACTION_STATUSES.RUNNING, True), (False, ACTION_STATUSES.RUNNING, True), (False, None, False), ), ) @patch('payee.logic.bank_details.create_payee_states') @patch('payee.logic.bank_details.register_payee') @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.get_payee_states') @patch('payee.logic.bank_details.AccountPayee') def test_register_whitelabel_profile( mock_account_payee: Mock, mock_get_account_payee_states: Mock, mock_has_secure_document_details: Mock, mock_register_payee: Mock, mock_create_payee_states: Mock, mock_identity_fields: dict[str, Any], has_account_payee: bool, bank_details_state: str | None, has_bank_details: bool, ): """Test register_whitelabel_profile function.""" account_payee_id = 1005 account_payee = AccountPayeeFactory.build(account_payee_id=account_payee_id) bank_details = BankDetailsDocument( { **BankDetailsIndividualTypeFlattenFactory.build(), **mock_identity_fields, } ) if has_account_payee: mock_account_payee.get_by_id_or_error.return_value = account_payee else: mock_account_payee.get_by_id_or_error.side_effect = ValueError('test err') mock_get_account_payee_states.return_value = ( {ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW: bank_details_state} if bank_details_state else {} ) if has_bank_details: mock_has_secure_document_details.return_value = bank_details else: mock_has_secure_document_details.return_value = None if not has_account_payee: with pytest.raises(ValueError, match='test err'): register_whitelabel_profile(account_payee_id) elif bank_details_state: with pytest.raises( RegisterWhitelabelProfileException, match='banking_details_review status exists', ): register_whitelabel_profile(account_payee_id) elif not has_bank_details: with pytest.raises( RegisterWhitelabelProfileException, match='Bank details not found' ): register_whitelabel_profile(account_payee_id) else: register_whitelabel_profile(account_payee_id) mock_account_payee.get_by_id_or_error.assert_called_once_with(account_payee_id) if has_account_payee: mock_get_account_payee_states.assert_called_once_with(account_payee_id) else: mock_get_account_payee_states.assert_not_called() if has_account_payee and not bank_details_state: mock_has_secure_document_details.assert_called_once_with( account_payee_id, BankDetailsDocument ) else: mock_has_secure_document_details.assert_not_called() if has_account_payee and not bank_details_state and has_bank_details: mock_register_payee.assert_called_once_with( account_payee, bank_details.original_values | {'account_payee_id': account_payee_id}, ) mock_create_payee_states.assert_called_once_with( account_payee_id, 'account_payee', [ACCOUNT_PAYEE_ACTION_NAMES.BANKING_DETAILS_REVIEW], ) else: mock_register_payee.assert_not_called() mock_create_payee_states.assert_not_called() @patch('payee.logic.bank_details.has_secure_document_details') @patch('payee.logic.bank_details.BankDetailsDocument.build_response') @patch('payee.logic.bank_details.BankDetailsDataHelper.build_payout_method') def test_get_data_change_parts_address_information_phone_and_phone_country_changed( mock_build_payout_method, mock_build_response, mock_has_secure_document_details, ): account_payee_id = 123 new_data = { 'type': 'INDIVIDUAL', 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'phone': '4567890', 'phone_country': 'US', 'address_1': 'Old Address', 'bank_name': 'Test Bank', } mock_has_secure_document_details.return_value = True existing_data = { 'type': 'INDIVIDUAL', 'contact': { 'first_name': 'first name', 'last_name': 'last name', 'date_of_birth': '2000-01-01', 'email': 'test@example.com', 'phone': '87654321', 'phone_country': 'GB', }, 'address': {'address_1': 'Old Address'}, 'payout_method': {}, } mock_build_response.return_value = existing_data mock_build_payout_method.return_value = {} result = get_data_change_parts(account_payee_id, new_data) expected_states = {PAYEE_BANKING_DETAILS_PARTS.ADDRESS_INFORMATION} assert result == expected_states