"""Test ows-payee requests.""" from datetime import date from http import HTTPStatus from typing import Union from unittest import mock from unittest.mock import MagicMock import httpx from owsclient.test import OwsClientMock import pytest from src.connectors import ows_payee from src.connectors.exceptions import OwsPayeeException from src.constants import PAYONEER_DEFAULT_TIMEOUT from src.models import ( Address, BankFieldDetail, Company, Contact, PayeeDetails, PayoutMethod, TaxFormInfoBulk, TaxFormInfoDetailsBulk, ) from tests.unit.error_handlers.test_common_error_handlers import get_http_status_error from tests.unit.factories import ( NewTaxDetailsFactory, PayeeDetailsFactory, TaxDetailsDataloaderItemFactory, TaxDetailsFactory, TaxFormInfoDetailsItemFactory, TaxFormInfoItemFactory, USTaxFormW8BENEFactory, USTaxFormW8BENFactory, USTaxFormW8ECIFactory, USTaxFormW8IMYFactory, USTaxFormW9Factory, ) @mock.patch('src.connectors.ows_payee.post') def test_save_bank_details_success(mock_post_req: mock.MagicMock) -> None: """Test save_bank_details method success.""" payee = PayeeDetailsFactory.build() path_pattern = f'/account-payee/{payee.account_payee_id}/bank-details' mock_response = mock.MagicMock() mock_response.status_code = 201 mock_response.raise_for_status = mock.MagicMock() mock_post_req.return_value = mock_response ows_payee.save_bank_details(payee) mock_post_req.assert_called_once_with( 'ows-payee', path_pattern, payee.model_dump(mode='json', exclude={'account_payee_id'}), ) @mock.patch('src.connectors.ows_payee.post') def test_save_bank_details_failure(mock_post_req: mock.MagicMock) -> None: """Test save_bank_details method failure.""" payee = PayeeDetailsFactory.build() path_pattern = f'/account-payee/{payee.account_payee_id}/bank-details' mock_response = mock.MagicMock() mock_response.status_code = 400 mock_response.raise_for_status = mock.MagicMock(side_effect=Exception('Error')) mock_post_req.return_value = mock_response with pytest.raises( OwsPayeeException, match=f'ows-payee failure: Failed to save bank details for payee {payee.account_payee_id}: Error', ): ows_payee.save_bank_details(payee) mock_post_req.assert_called_once_with( 'ows-payee', path_pattern, payee.model_dump(mode='json', exclude={'account_payee_id'}), ) @pytest.mark.parametrize('is_error', (True, False)) def test_delete_banking_details(ows_client_mock: OwsClientMock, is_error: bool) -> None: """Test delete banking details.""" account_payee_id = 11 ows_client_mock.delete( 'ows-payee', f'/account-payee/{account_payee_id}/bank-details' ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.delete_banking_details(account_payee_id) else: ows_payee.delete_banking_details(account_payee_id) @pytest.mark.parametrize( 'is_error,expiration_date_start,expiration_date_end,is_active', ( (True, None, None, None), (False, None, None, None), (False, date(2020, 10, 5), None, False), (False, None, date(2024, 5, 11), True), ), ) def test_get_tax_form_info_details_bulk( ows_client_mock: OwsClientMock, is_error: bool, expiration_date_start: date | None, expiration_date_end: date | None, is_active: bool | None, ) -> None: """Test get_tax_form_info_details_bulk.""" account_payee_ids = [10, 21, 500] offset = 15 limit = 50 data_items = TaxFormInfoDetailsItemFactory.batch(3) body = ( {'account_payee_ids': account_payee_ids} | ( {'expiration_date_start': expiration_date_start.isoformat()} if expiration_date_start is not None else {} ) | ( {'expiration_date_end': expiration_date_end.isoformat()} if expiration_date_end is not None else {} ) | ({'is_active': is_active} if is_active is not None else {}) ) ows_client_mock.post( 'ows-payee', f'/account-payee/tax-form-info/details/bulk?offset={offset}&limit={limit}', json=body, ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK, json={ 'items': [i.model_dump(mode='json') for i in data_items], 'total_count': 3, }, ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.get_tax_form_info_details_bulk( account_payee_ids, expiration_date_start, expiration_date_end, is_active, offset, limit, ) else: result = ows_payee.get_tax_form_info_details_bulk( account_payee_ids, expiration_date_start, expiration_date_end, is_active, offset, limit, ) assert result == TaxFormInfoDetailsBulk(items=data_items, total_count=3) @pytest.mark.parametrize('is_error', (True, False)) def test_get_banking_details(ows_client_mock: OwsClientMock, is_error: bool) -> None: """Test get banking details.""" account_payee_id = 11 payee = PayeeDetails( account_payee_id=account_payee_id, type='INDIVIDUAL', contact=Contact( first_name='John', last_name='Doe', date_of_birth='1990-01-01', email='john.doe@example.com', ), company=Company(name='company name'), address=Address( country_code='US', address_1='123 Main St', address_2='Apt 4B', city='Metropolis', province='MetroState', zip='12345', ), payout_method=PayoutMethod( bank_account_type='checking', country='US', currency='USD', bank_field_details=[ BankFieldDetail(name='BankName', value='HSBC BANK ARGENTINA SA'), BankFieldDetail(name='RoutingNumber', value='Checking'), ], ), ) ows_client_mock.get( 'ows-payee', f'/account-payee/{account_payee_id}/bank-details' ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK, json=payee.model_dump(mode='json'), ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.get_bank_details_by_account_payee(account_payee_id) else: res = ows_payee.get_bank_details_by_account_payee(account_payee_id) assert res == payee @pytest.mark.parametrize( 'is_error,expiration_date_start,expiration_date_end,is_active', ( (True, None, None, None), (False, None, None, None), (False, date(2020, 10, 5), None, False), (False, None, date(2024, 5, 11), True), ), ) def test_get_tax_form_info_bulk( ows_client_mock: OwsClientMock, is_error: bool, expiration_date_start: date | None, expiration_date_end: date | None, is_active: bool | None, ) -> None: """Test get_tax_form_info_bulk.""" account_payee_ids = [10, 21, 500] offset = 15 limit = 50 data_items = TaxFormInfoItemFactory.batch(3) body = ( {'account_payee_ids': account_payee_ids} | ( {'expiration_date_start': expiration_date_start.isoformat()} if expiration_date_start is not None else {} ) | ( {'expiration_date_end': expiration_date_end.isoformat()} if expiration_date_end is not None else {} ) | ({'is_active': is_active} if is_active is not None else {}) ) ows_client_mock.post( 'ows-payee', f'/account-payee/tax-form-info/bulk?offset={offset}&limit={limit}', json=body, ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK, json={ 'items': [i.model_dump(mode='json') for i in data_items], 'total_count': 3, }, ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.get_tax_form_info_bulk( account_payee_ids, expiration_date_start, expiration_date_end, is_active, offset, limit, ) else: result = ows_payee.get_tax_form_info_bulk( account_payee_ids, expiration_date_start, expiration_date_end, is_active, offset, limit, ) assert result == TaxFormInfoBulk(items=data_items, total_count=3) @pytest.mark.parametrize( 'is_error,tax_form_factory', ( (True, USTaxFormW8BENFactory), (False, USTaxFormW8BENFactory), (False, USTaxFormW8BENEFactory), (False, USTaxFormW8ECIFactory), (False, USTaxFormW8IMYFactory), (False, USTaxFormW9Factory), ), ) def test_save_tax_form_info( ows_client_mock: OwsClientMock, is_error: bool, tax_form_factory: Union[ USTaxFormW8BENFactory, USTaxFormW8BENEFactory, USTaxFormW8ECIFactory, USTaxFormW8IMYFactory, USTaxFormW9Factory, ], ) -> None: """Test save_tax_form_info.""" account_payee_id = 1002 data = tax_form_factory.build(lob='lob1') ows_client_mock.post( 'ows-payee', f'/account-payee/{account_payee_id}/tax-form-info', json=data.model_dump(mode='json'), ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.save_tax_form_info(account_payee_id, data) else: ows_payee.save_tax_form_info(account_payee_id, data) @pytest.mark.parametrize('is_error', (True, False)) def test_delete_tax_form_info(ows_client_mock: OwsClientMock, is_error: bool) -> None: """Test delete_tax_form_info.""" account_tax_form_id = 301 ows_client_mock.delete( 'ows-payee', f'/account-payee/tax-form-info/{account_tax_form_id}' ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST if is_error else HTTPStatus.OK ) ) if is_error: with pytest.raises(OwsPayeeException): ows_payee.delete_tax_form_info(account_tax_form_id) else: ows_payee.delete_tax_form_info(account_tax_form_id) def test_get_tax_details_success(ows_client_mock: OwsClientMock) -> None: """Test get tax details.""" account_payee_id = 11 tax_details = TaxDetailsFactory.build() ows_client_mock.get( 'ows-payee', f'/account-payee/{account_payee_id}/tax-details' ).mock( return_value=httpx.Response( HTTPStatus.OK, json=tax_details.model_dump(mode='json'), ) ) res = ows_payee.get_tax_details(account_payee_id) assert res == tax_details def test_get_tax_details_failure(ows_client_mock: OwsClientMock) -> None: """Test get tax details.""" account_payee_id = 11 tax_details = TaxDetailsFactory.build() ows_client_mock.get( 'ows-payee', f'/account-payee/{account_payee_id}/tax-details' ).mock( return_value=httpx.Response( HTTPStatus.BAD_REQUEST, json=tax_details.model_dump(mode='json'), ) ) with pytest.raises(OwsPayeeException): ows_payee.get_tax_details(account_payee_id) def test_get_tax_details_not_found(ows_client_mock: OwsClientMock) -> None: """Test get tax details.""" account_payee_id = 11 tax_details = TaxDetailsFactory.build() ows_client_mock.get( 'ows-payee', f'/account-payee/{account_payee_id}/tax-details' ).mock( return_value=httpx.Response( HTTPStatus.NOT_FOUND, json=tax_details.model_dump(mode='json'), ) ) res = ows_payee.get_tax_details(account_payee_id) assert res is None @mock.patch('src.connectors.ows_payee.post') def test_save_tax_details_success(mock_post_req: mock.MagicMock) -> None: """Test save_tax_details method success.""" account_payee_id = 11 details = NewTaxDetailsFactory.build() path_pattern = f'/account-payee/{account_payee_id}/tax-details' ows_payee.save_tax_details(account_payee_id, details) mock_post_req.assert_called_once_with( 'ows-payee', path_pattern, details.model_dump(mode='json', exclude_none=True), raise_for_status=True, ) @mock.patch('src.connectors.ows_payee.post') def test_save_tax_details_failure_basic(mock_post_req: mock.MagicMock) -> None: """Test save_bank_details method failure.""" account_payee_id = 11 details = NewTaxDetailsFactory.build() path_pattern = f'/account-payee/{account_payee_id}/tax-details' mock_post_req.side_effect = Exception('Error') with pytest.raises( OwsPayeeException, match='ows-payee failure: Failed to save tax details', ) as exc: ows_payee.save_tax_details(account_payee_id, details) assert exc.value.error_code == 'Error' assert exc.value.additional_data == dict(account_payee_id=account_payee_id) mock_post_req.assert_called_once_with( 'ows-payee', path_pattern, details.model_dump(mode='json', exclude_none=True), raise_for_status=True, ) @mock.patch('src.connectors.ows_payee.post') def test_save_tax_details_failure_bad_request(mock_post_req: mock.MagicMock) -> None: """Test save_bank_details method failure.""" account_payee_id = 11 details = NewTaxDetailsFactory.build() path_pattern = f'/account-payee/{account_payee_id}/tax-details' mock_post_req.side_effect = get_http_status_error( 400, {'message': {'business_name': 'Wrong', 'address': {'city': 'Too'}}} ) with pytest.raises( OwsPayeeException, match='ows-payee failure: Failed to save tax details', ) as exc: ows_payee.save_tax_details(account_payee_id, details) assert exc.value.error_code == 'business_name: Wrong, city: Too' assert exc.value.additional_data == dict( account_payee_id=account_payee_id, business_name=details.business_name, city=details.address.city if details.address else None, ) mock_post_req.assert_called_once_with( 'ows-payee', path_pattern, details.model_dump(mode='json', exclude_none=True), raise_for_status=True, ) @mock.patch('src.connectors.ows_payee.post') def test_register_banking_details_success(mock_post_req: mock.MagicMock) -> None: """Test register_banking_details method success.""" account_payee_id = 11 ows_payee.register_banking_details(account_payee_id) mock_post_req.assert_called_once_with( 'ows-payee', f'/account-payee/{account_payee_id}/register-whitelabel-profile', raise_for_status=True, timeout=PAYONEER_DEFAULT_TIMEOUT, ) @mock.patch('src.connectors.ows_payee.post') def test_register_banking_details(mock_post_req: mock.MagicMock) -> None: """Test register_banking_details method failure.""" account_payee_id = 22 mock_post_req.side_effect = Exception('Test Error') with pytest.raises( OwsPayeeException, match=f'ows-payee failure: Failed to register banking details for payee {account_payee_id}: Test Error', ): ows_payee.register_banking_details(account_payee_id) mock_post_req.assert_called_once_with( 'ows-payee', f'/account-payee/{account_payee_id}/register-whitelabel-profile', raise_for_status=True, timeout=PAYONEER_DEFAULT_TIMEOUT, ) def test_get_tax_details_bulk_success(ows_client_mock: OwsClientMock) -> None: """Test get_tax_details_bulk method success.""" account_payee_ids = [23, 52, 180, 78] tax_details = [ TaxDetailsDataloaderItemFactory.build(account_payee_id=account_payee_id) for account_payee_id in account_payee_ids ] ows_client_mock.post( 'ows-payee', '/account-payee/tax-details-dataloader', json=[ {'accountPayeeId': account_payee_id, 'revision': None} for account_payee_id in account_payee_ids ], ).mock( return_value=httpx.Response( HTTPStatus.OK, json=[{'data': item.model_dump(mode='json')} for item in tax_details], ) ) result = ows_payee.get_tax_details_bulk(account_payee_ids) assert result == tax_details def test_get_tax_details_bulk_failure(ows_client_mock: OwsClientMock) -> None: """Test get_tax_details_bulk method failure.""" account_payee_ids = [11, 201, 51, 5] ows_client_mock.post( 'ows-payee', '/account-payee/tax-details-dataloader', json=[ {'accountPayeeId': account_payee_id, 'revision': None} for account_payee_id in account_payee_ids ], ).mock(return_value=httpx.Response(HTTPStatus.BAD_REQUEST)) with pytest.raises(OwsPayeeException): ows_payee.get_tax_details_bulk(account_payee_ids)