"""Test Processor.""" from decimal import Decimal from unittest.mock import call, patch import httpx from owsclient.test import OwsClientMock from payoneer_payments_payout import config from payoneer_payments_payout.error_handling import OwsServiceException from payoneer_payments_payout.processor import PayoneerPaymentsPayoutProcessor @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands') @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.' 'create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.' 'create_abacus_event') @patch('payoneer_payments_payout.processor.' 'get_payment_entity') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts') @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options') def test_processor_process( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event ): """Test Processor.process.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ { 'payment_group_payment_batch_id': 1 }, { 'payment_group_payment_batch_id': 2 } ] get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) mock_accounts_brands = {1: 'brand 1', 2: 'brand 2'} processor._get_accounts_brands = lambda account_ids: mock_accounts_brands processor.process() assert mock_set_options.called assert mock_processor.return_value.process.call_count == 2 post_payment_group_payment_batch_mock.assert_has_calls( [ call({ 'payment_group_payment_id': '1', 'batch_num': 1, 'payoneer_program_id': '100158940' }), call({ 'payment_group_payment_id': '1', 'batch_num': 2, 'payoneer_program_id': '100158970' }) ] ) post_payment_group_payment_batch_accounts_mock.assert_has_calls( [ call([{ 'payment_group_payment_account_id': '17', 'payment_group_payment_batch_id': 1 }]), call([{ 'payment_group_payment_account_id': '18', 'payment_group_payment_batch_id': 2 }]) ] ) create_abacus_event_mock.assert_has_calls( [ call({ 'event_name': 'commit_to_subledger', 'target_type': 'payment_group_payment_batch', 'target_id': 1 }), call({ 'event_name': 'commit_to_subledger', 'target_type': 'payment_group_payment_batch', 'target_id': 1 }) ] ) mock_update_state_by_id.assert_has_calls( [call(1, {'action_status': 'complete'}), call(1, {'action_status': 'complete'})] ) assert mock_payment_entry_queue.return_value.flush_entries.call_count == 2 assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 0 assert mock_get_accounts_brands.call_args_list == [] assert mock_processor.call_args_list == [ call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_accounts_brands ), call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_accounts_brands ), ] @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands') @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.' 'create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.' 'create_abacus_event') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts') @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options') def test_processor_process_skip_subledger( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event ): """Test Processor.process.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ { 'payment_group_payment_batch_id': 1 }, { 'payment_group_payment_batch_id': 2 } ] create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor._skip_subledger = True processor.process() assert mock_set_options.called assert mock_processor.return_value.process.call_count == 2 post_payment_group_payment_batch_mock.assert_has_calls( [ call({ 'payment_group_payment_id': '1', 'batch_num': 1, 'payoneer_program_id': '100158940' }), call({ 'payment_group_payment_id': '1', 'batch_num': 2, 'payoneer_program_id': '100158970' }) ] ) post_payment_group_payment_batch_accounts_mock.assert_has_calls( [ call([{ 'payment_group_payment_account_id': '17', 'payment_group_payment_batch_id': 1 }]), call([{ 'payment_group_payment_account_id': '18', 'payment_group_payment_batch_id': 2 }]) ] ) create_abacus_event_mock.assert_not_called() mock_update_state_by_id.assert_has_calls( [call(1, {'action_status': 'complete'}), call(1, {'action_status': 'complete'})] ) assert mock_payment_entry_queue.return_value.flush_entries.call_count == 2 assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 2 assert mock_get_accounts_brands.called assert mock_processor.call_args_list == [ call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_get_accounts_brands.return_value), call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_get_accounts_brands.return_value ), ] @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands') @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.' 'create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.' 'create_abacus_event') @patch('payoneer_payments_payout.processor.' 'get_payment_entity') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts') @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options') def test_processor_process_fails_sending_payment( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test Processor.process when payment_entry_queue fails to flush.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ { 'payment_group_payment_batch_id': 1 }, { 'payment_group_payment_batch_id': 2 } ] get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.side_effect =\ OwsServiceException('test') mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) mock_accounts_brands = {1: 'brand 1', 2: 'brand 2'} processor._get_accounts_brands = lambda account_ids: mock_accounts_brands processor.process() assert mock_set_options.called assert mock_processor.return_value.process.call_count == 2 post_payment_group_payment_batch_mock.assert_has_calls( [ call({ 'payment_group_payment_id': '1', 'batch_num': 1, 'payoneer_program_id': '100158940' }), call({ 'payment_group_payment_id': '1', 'batch_num': 2, 'payoneer_program_id': '100158970' }) ] ) post_payment_group_payment_batch_accounts_mock.assert_has_calls( [ call([{ 'payment_group_payment_account_id': '17', 'payment_group_payment_batch_id': 1 }]), call([{ 'payment_group_payment_account_id': '18', 'payment_group_payment_batch_id': 2 }]) ] ) mock_update_state_by_id.assert_has_calls([ call(1, { 'action_status': 'error', 'message': 'Exception on sending payment test'}), call(1, { 'action_status': 'error', 'message': 'Exception on sending payment test'}) ]) assert mock_payment_entry_queue.return_value.flush_entries.call_count == 2 assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 0 assert not mock_get_accounts_brands.called assert mock_processor.call_args_list == [ call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_accounts_brands ), call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_accounts_brands ), ] @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands') @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.' 'create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.' 'create_abacus_event') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.' 'post_payment_group_payment_batch') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts') @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch('payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options') def test_processor_process_fails_debiting_ledger( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test Processor.process when ledger_entry_queue fails to flush.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ { 'payment_group_payment_batch_id': 1 }, { 'payment_group_payment_batch_id': 2 } ] create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.side_effect =\ OwsServiceException('bad ledger') processor = PayoneerPaymentsPayoutProcessor(mock_event) processor._skip_subledger = True processor.process() assert mock_set_options.called assert mock_processor.return_value.process.call_count == 2 post_payment_group_payment_batch_mock.assert_has_calls( [ call({ 'payment_group_payment_id': '1', 'batch_num': 1, 'payoneer_program_id': '100158940' }), call({ 'payment_group_payment_id': '1', 'batch_num': 2, 'payoneer_program_id': '100158970' }) ] ) post_payment_group_payment_batch_accounts_mock.assert_has_calls( [ call([{ 'payment_group_payment_account_id': '17', 'payment_group_payment_batch_id': 1 }]), call([{ 'payment_group_payment_account_id': '18', 'payment_group_payment_batch_id': 2 }]) ] ) create_abacus_event_mock.call_count == 0 mock_update_state_by_id.assert_has_calls([ call(1, { 'action_status': 'error', 'message': 'Exception on debiting ledger bad ledger'}), call(1, { 'action_status': 'error', 'message': 'Exception on debiting ledger bad ledger'}) ]) assert mock_payment_entry_queue.return_value.flush_entries.call_count == 2 assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 2 assert mock_get_accounts_brands.called assert mock_processor.call_args_list == [ call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_get_accounts_brands.return_value ), call( mock_event, mock_payment_entry_queue.return_value, mock_ledger_entry_queue.return_value, mock_get_accounts_brands.return_value ), ] def test_create_payment_accounts_batches_by_program_id( mock_payment_group_payment_accounts, mock_event ): """Test _create_payment_accounts_batches_by_program_id method.""" mock_accounts = mock_payment_group_payment_accounts processor = PayoneerPaymentsPayoutProcessor(mock_event) result = processor._create_payment_accounts_batches_by_program_id(mock_accounts) assert result == { '100158940': [mock_accounts[0]], '100158970': [mock_accounts[1]] } @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') def test_create_payment_group_payment_batch( mock_create_abacus_state, mock_create_abacus_event, mock_event, mock_payment_group_payment_batch, mock_payment_group_payment_batch_account, ows_client_mock: OwsClientMock ): """Test _create_payment_group_payment_batch method.""" processor = PayoneerPaymentsPayoutProcessor(mock_event) request_url = '/payment-group-payment-batch' ows_client_mock.post( 'ows-payment', request_url ).mock( return_value=httpx.Response( 201, json=mock_payment_group_payment_batch ) ) request_url = 'payment-group-payment-batch-account/bulk' ows_client_mock.post('ows-payment', request_url).mock( return_value=httpx.Response( 201, json=mock_payment_group_payment_batch_account ) ) program_id = '100158940' processor._create_payment_group_payment_batch([1, 2, 3], program_id) mock_create_abacus_state.assert_called_once_with('payment_group_payment_batch', 1) @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') def test_create_payment_group_payment_batch_do_not_send_commit_to_subledger_event( mock_create_abacus_state, mock_event, mock_payment_group_payment_batch, mock_payment_group_payment_batch_account, ows_client_mock: OwsClientMock ): """Test _create_payment_group_payment_batch method.""" processor = PayoneerPaymentsPayoutProcessor(mock_event) request_url = '/payment-group-payment-batch' ows_client_mock.post('ows-payment', request_url).mock( return_value=httpx.Response( 201, json=mock_payment_group_payment_batch ) ) request_url = '/payment-group-payment-batch-account/bulk' ows_client_mock.post('ows-payment', request_url).mock( return_value=httpx.Response( 201, json=mock_payment_group_payment_batch_account ) ) program_id = '100158940' processor._create_payment_group_payment_batch([1, 2, 3], program_id) mock_create_abacus_state.assert_called_once_with('payment_group_payment_batch', 1) @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_entity') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_true_for_knr_account( mock_get_payment_group_payment_data, mock_get_payment_group, mock_get_payment_entity, mock_config, mock_event, mock_payment_entity, mock_knr_payment_group, mock_payment_group_payment ): """Test _set_options true for KNR accounts.""" mock_config.ENVIRONMENT = 'dev' mock_get_payment_entity.return_value = mock_payment_entity mock_get_payment_group.return_value = mock_knr_payment_group mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False assert processor._skip_subledger is False processor._set_options() assert processor._skip_payoneer is True assert processor._skip_subledger is True @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_entity') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_false_for_non_knr_account( mock_get_payment_group_payment_data, mock_get_payment_group, mock_get_payment_entity, mock_config, mock_event, mock_alt_payment_entity, mock_knr_payment_group, mock_payment_group_payment ): """Test _set_options false for KNR accounts.""" mock_config.ENVIRONMENT = 'dev' mock_get_payment_entity.return_value = mock_alt_payment_entity mock_get_payment_group.return_value = mock_knr_payment_group mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False assert processor._skip_subledger is False processor._set_options() assert processor._skip_payoneer is False assert processor._skip_subledger is False @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_entity') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_no_payment_entity( mock_get_payment_group_payment_data, mock_get_payment_group, mock_get_payment_entity, mock_config, mock_event, mock_alt_payment_group, mock_payment_group_payment ): """Test _set_options false when there are not payment entity.""" mock_config.ENVIRONMENT = 'dev' mock_get_payment_entity.return_value = dict() mock_get_payment_group.return_value = mock_alt_payment_group mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False assert processor._skip_subledger is False processor._set_options() assert processor._skip_payoneer is False assert processor._skip_subledger is False @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_true_for_payment_method( mock_get_payment_group_payment_data, mock_get_payment_group, mock_config, mock_event, mock_payment_entity, mock_payment_group_with_payment_type, mock_payment_group_payment ): """Test _set_options true for KNR accounts.""" mock_config.ENVIRONMENT = 'dev' mock_config.SKIP_PAYONEER_PAYMENT_TYPE_IDS = [ mock_payment_group_with_payment_type[ 'group_criteria' ]['reference_payment_type_id'] ] mock_get_payment_group.return_value = mock_payment_group_with_payment_type mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False assert processor._skip_subledger is False processor._set_options() assert processor._skip_payoneer is True assert processor._skip_subledger is False @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_false_for_payment_method( mock_get_payment_group_payment_data, mock_get_payment_group, mock_config, mock_event, mock_alt_payment_entity, mock_payment_group_with_payment_type, mock_payment_group_payment ): """Test _set_options false for KNR accounts.""" mock_config.ENVIRONMENT = 'dev' mock_config.SKIP_PAYONEER_PAYMENT_TYPE_IDS = [ mock_payment_group_with_payment_type[ 'group_criteria' ]['reference_payment_type_id'] + 1 ] mock_get_payment_group.return_value = mock_payment_group_with_payment_type mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False assert processor._skip_subledger is False processor._set_options() assert processor._skip_payoneer is False assert processor._skip_subledger is False @patch('payoneer_payments_payout.processor.' 'config') @patch('payoneer_payments_payout.processor.' 'get_payment_group') @patch('payoneer_payments_payout.processor.' 'get_payment_group_payment_data') def test_set_options_false_no_payment_method( mock_get_payment_group_payment_data, mock_get_payment_group, mock_config, mock_event, mock_alt_payment_group, mock_payment_group_payment ): """Test _set_options false when there are not payment entity.""" mock_config.ENVIRONMENT = 'dev' mock_get_payment_group.return_value = mock_alt_payment_group mock_get_payment_group_payment_data.return_value = mock_payment_group_payment processor = PayoneerPaymentsPayoutProcessor(mock_event) assert processor._skip_payoneer is False processor._set_options() assert processor._skip_payoneer is False @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_processor_flush_entries_and_create_abacus_event_when_calc_pmts_state_exists( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flush_entries call and create_abacus_event trigger when calc_payments_state exists.""" # noqa: E501 mock_set_options.return_value = False mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST, } post_payment_group_payment_batch_mock.return_value = { 'payment_group_payment_batch_id': 123 } create_abacus_state_by_parent_table_mock.return_value = ( mock_payment_group_payment_batch_state ) mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() # Verify flush_entries was called with program_id mock_payment_entry_queue.return_value.flush_entries.assert_called_with('100158970') # Verify create_abacus_event was called with correct parameters create_abacus_event_mock.assert_called_with( { 'event_name': config.COMMIT_TO_SUBLEDGER_EVENT_NAME, 'target_type': config.BATCH_TABLE_NAME, 'target_id': 1, } ) # Verify ledger flush_entries was NOT called when calculate_payments_state exists assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 0 # Verify state was updated to complete mock_update_state_by_id.assert_called_with(1, {'action_status': 'complete'}) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_fail_processor_flush_entries_when_calc_pmts_state_exists( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test fail flush_entries call and create_abacus_event trigger when calc_payments_state exists.""" # noqa: E501 mock_set_options.return_value = False mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST, } post_payment_group_payment_batch_mock.return_value = { 'payment_group_payment_batch_id': 123 } # Mock calculate_payments_state to exist create_abacus_state_by_parent_table_mock.return_value = ( mock_payment_group_payment_batch_state ) # Simulate flush_entries raising an exception mock_payment_entry_queue.return_value.flush_entries.side_effect = ( OwsServiceException('test') ) mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() # Verify flush_entries was called with program_id mock_payment_entry_queue.return_value.flush_entries.assert_called_with('100158970') # Verify create_abacus_event was NOT called due to exception create_abacus_event_mock.assert_not_called() # Verify ledger flush_entries was NOT called when calculate_payments_state exists assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 0 # Verify state was updated to error due to exception mock_update_state_by_id.assert_called_with( 1, {'action_status': 'error', 'message': 'Exception on sending payment test'} ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_fail_ledger_flush_entries_skip_subledger( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test fail ledger flush_entries call when calc_payments_state does not exist.""" # noqa: E501 mock_set_options.return_value = False mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST, } post_payment_group_payment_batch_mock.return_value = { 'payment_group_payment_batch_id': 123 } create_abacus_state_by_parent_table_mock.return_value = ( mock_payment_group_payment_batch_state ) mock_payment_entry_queue.return_value.flush_entries.return_value = True # Simulate ledger flush_entries raising an exception mock_ledger_entry_queue.return_value.flush_entries.side_effect = ( OwsServiceException('bad ledger') ) processor = PayoneerPaymentsPayoutProcessor(mock_event) processor._skip_subledger = True processor.process() # Verify flush_entries was called with program_id mock_payment_entry_queue.return_value.flush_entries.assert_called_with('100158970') # Verify create_abacus_event was NOT called when calculate_pmnt_state does not exist create_abacus_event_mock.assert_not_called() # Verify ledger flush_entries was called and raised exception assert mock_ledger_entry_queue.return_value.flush_entries.call_count == 2 # Verify state was updated to error due to exception mock_update_state_by_id.assert_called_with( 1, { 'action_status': 'error', 'message': 'Exception on debiting ledger bad ledger' } ) @patch('payoneer_payments_payout.processor.get_brands') @patch('payoneer_payments_payout.processor.lookup_vendor_ids') @patch('payoneer_payments_payout.processor.vendor_company_brands_dataloader') def test_get_accounts_brands_normal( mock_vendor_company_brands_dataloader, mock_lookup_vendor_ids, mock_get_brands ): """Test _get_accounts_brands normal case.""" processor = PayoneerPaymentsPayoutProcessor(event={}) account_ids = [1, 2] mock_get_brands.return_value = [ {'uuid': 'brand-1', 'name': 'Brand One'}, {'uuid': 'brand-2', 'name': 'Brand Two'} ] mock_lookup_vendor_ids.return_value = { 'vendors': [ {'uuid': 'vendor-uuid-1'}, {'uuid': 'vendor-uuid-2'} ] } mock_vendor_company_brands_dataloader.return_value = { 'vendors': [ {'vendor_id': 101, 'company_brand_uuid': 'brand-1'}, {'vendor_id': 102, 'company_brand_uuid': 'brand-2'} ] } result = processor._get_accounts_brands(account_ids) assert result == {101: 'Brand One', 102: 'Brand Two'} @patch('payoneer_payments_payout.processor.get_brands') @patch('payoneer_payments_payout.processor.lookup_vendor_ids') @patch('payoneer_payments_payout.processor.vendor_company_brands_dataloader') def test_get_accounts_brands_empty_vendors( mock_vendor_company_brands_dataloader, mock_lookup_vendor_ids, mock_get_brands ): """Test _get_accounts_brands when no vendors are returned.""" processor = PayoneerPaymentsPayoutProcessor(event={}) account_ids = [1, 2] mock_lookup_vendor_ids.return_value = {'vendors': []} result = processor._get_accounts_brands(account_ids) assert result == {} @patch('payoneer_payments_payout.processor.get_brands') @patch('payoneer_payments_payout.processor.lookup_vendor_ids') @patch('payoneer_payments_payout.processor.vendor_company_brands_dataloader') def test_get_accounts_brands_empty_brands( mock_vendor_company_brands_dataloader, mock_lookup_vendor_ids, mock_get_brands ): """Test _get_accounts_brands when no brands are returned.""" processor = PayoneerPaymentsPayoutProcessor(event={}) account_ids = [1, 2] mock_get_brands.return_value = [] mock_lookup_vendor_ids.return_value = { 'vendors': [ {'uuid': 'vendor-uuid-1'} ] } mock_vendor_company_brands_dataloader.return_value = { 'vendors': [ {'vendor_id': 101, 'company_brand_uuid': 'brand-1'} ] } result = processor._get_accounts_brands(account_ids) assert result == {} @patch('payoneer_payments_payout.processor.get_brands') @patch('payoneer_payments_payout.processor.lookup_vendor_ids') @patch('payoneer_payments_payout.processor.vendor_company_brands_dataloader') def test_get_accounts_brands_missing_vendor_data( mock_vendor_company_brands_dataloader, mock_lookup_vendor_ids, mock_get_brands ): """Test _get_accounts_brands when vendor data is missing.""" processor = PayoneerPaymentsPayoutProcessor(event={}) account_ids = [1, 2] mock_get_brands.return_value = [ {'uuid': 'brand-1', 'name': 'Brand One'} ] mock_lookup_vendor_ids.return_value = { 'vendors': [None] } mock_vendor_company_brands_dataloader.return_value = { 'vendors': [None] } result = processor._get_accounts_brands(account_ids) assert result == {} @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_allocations_debited_on_success( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough allocations are updated to DEBITED on successful payment.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ {'payment_group_payment_batch_id': 1}, {'payment_group_payment_batch_id': 2}, ] get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [] processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() assert mock_update_flowthrough.call_count == 2 mock_update_flowthrough.assert_any_call( ['17'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=[], ) mock_update_flowthrough.assert_any_call( ['18'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=[], ) @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_allocations_returned_on_error( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough allocations are updated to RETURNED on payment error.""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ {'payment_group_payment_batch_id': 1}, {'payment_group_payment_batch_id': 2}, ] get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.side_effect = \ OwsServiceException('test') mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() assert mock_update_flowthrough.call_count == 2 mock_update_flowthrough.assert_any_call( ['17'], payment_status_transition=('attached_to_payment', 'returned'), ) mock_update_flowthrough.assert_any_call( ['18'], payment_status_transition=('attached_to_payment', 'returned'), ) @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_allocations_skipped_when_skip_payoneer( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough allocations are NOT updated when skip_payoneer is True (KNR).""" mock_payment_group_payment_accounts_req.return_value = { 'items': mock_payment_group_payment_accounts, 'total_count': config.BATCH_SIZE_POST } post_payment_group_payment_batch_mock.side_effect = [ {'payment_group_payment_batch_id': 1}, {'payment_group_payment_batch_id': 2}, ] create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor._skip_payoneer = True processor._skip_subledger = True processor.process() mock_update_flowthrough.assert_not_called() @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_posted_on_success( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough sub-ledger is posted on successful payment.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [ { 'account_id': 123, 'contract_id': 829, 'currency': 'CAD', 'amount_payable': '10000.00', 'target_id': 998, }, { 'account_id': 123, 'contract_id': 829, 'currency': 'CAD', 'amount_payable': '2000.00', 'target_id': 999, }, { 'account_id': 123, 'contract_id': 900, 'currency': 'USD', 'amount_payable': '500.00', 'target_id': 1000, }, ] mock_post_flowthrough_ledger.return_value = {'result': 'ok'} processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() mock_get_flowthrough_details.assert_called_once_with(['17']) mock_post_flowthrough_ledger.assert_called_once() entries = mock_post_flowthrough_ledger.call_args[0][0] assert len(entries) == 2 entries_by_contract = {e['contract_id']: e for e in entries} assert entries_by_contract[829]['model_type'] == 'contract' assert entries_by_contract[829]['account_id'] == 123 assert entries_by_contract[829]['currency_code'] == 'CAD' assert entries_by_contract[829]['currency_amount'] == Decimal('-12000.00') assert entries_by_contract[829]['abacus_event_id'] == 1 assert entries_by_contract[900]['model_type'] == 'contract' assert entries_by_contract[900]['account_id'] == 123 assert entries_by_contract[900]['currency_code'] == 'USD' assert entries_by_contract[900]['currency_amount'] == Decimal('-500.00') assert entries_by_contract[900]['abacus_event_id'] == 1 mock_update_state_by_id.assert_called_once_with( 1, {'action_status': config.ACTION_STATUSES.COMPLETE} ) mock_update_flowthrough.assert_called_once_with( ['17'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=mock_get_flowthrough_details.return_value, ) @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_failure_reverts_to_error( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough sub-ledger failure sets ERROR and reverts allocations.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [ { 'account_id': 123, 'contract_id': 829, 'currency': 'CAD', 'amount_payable': '667686.00', 'target_id': 999, } ] mock_post_flowthrough_ledger.side_effect = OwsServiceException('test error') processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() mock_update_state_by_id.assert_called_once_with( 1, { 'action_status': config.ACTION_STATUSES.ERROR, 'message': config.EXCEPTION_POSTING_FLOWTHROUGH_LEDGER_MSG.format( 'test error' ) } ) mock_get_flowthrough_details.assert_called_once_with(['17']) mock_update_flowthrough.assert_called_once_with( ['17'], payment_status_transition=('attached_to_payment', 'returned'), payable_details=mock_get_flowthrough_details.return_value, ) @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_not_posted_when_skip_payoneer( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough sub-ledger is NOT posted when skip_payoneer is True.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True processor = PayoneerPaymentsPayoutProcessor(mock_event) processor._skip_payoneer = True processor._skip_subledger = True processor.process() mock_get_flowthrough_details.assert_not_called() mock_post_flowthrough_ledger.assert_not_called() @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_not_posted_when_no_payable_details( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough sub-ledger is NOT posted when no payable details exist.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [] processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() mock_get_flowthrough_details.assert_called_once_with(['17']) mock_post_flowthrough_ledger.assert_not_called() mock_update_state_by_id.assert_called_once_with( 1, {'action_status': config.ACTION_STATUSES.COMPLETE} ) mock_update_flowthrough.assert_called_once_with( ['17'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=[], ) @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_skips_zero_amount_payable_entries( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test zero amount_payable entries are filtered out before posting.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} get_payment_entity_mock.return_value = {'payment_entity_name': 'NONKNR test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [ { 'account_id': 123, 'contract_id': 829, 'currency': 'CAD', 'amount_payable': '10000.00', 'target_id': 998, }, { 'account_id': 123, 'contract_id': 900, 'currency': 'USD', 'amount_payable': '500.00', 'target_id': 1000, }, { 'account_id': 123, 'contract_id': 950, 'currency': 'EUR', 'amount_payable': '0.00', 'target_id': 1001, }, ] mock_post_flowthrough_ledger.return_value = {'result': 'ok'} processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() mock_get_flowthrough_details.assert_called_once_with(['17']) mock_post_flowthrough_ledger.assert_called_once() entries = mock_post_flowthrough_ledger.call_args[0][0] assert len(entries) == 2 entries_by_contract = {e['contract_id']: e for e in entries} assert 829 in entries_by_contract assert 900 in entries_by_contract assert 950 not in entries_by_contract mock_update_state_by_id.assert_called_once_with( 1, {'action_status': config.ACTION_STATUSES.COMPLETE} ) mock_update_flowthrough.assert_called_once_with( ['17'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=mock_get_flowthrough_details.return_value, ) @patch('payoneer_payments_payout.processor.post_flowthrough_sub_ledger') @patch('payoneer_payments_payout.processor.get_all_flowthrough_payable_details') @patch('payoneer_payments_payout.processor.update_flowthrough_allocations') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_get_accounts_brands' ) @patch('payoneer_payments_payout.processor.LedgerEntryQueue') @patch('payoneer_payments_payout.processor.PaymentEntryQueue') @patch('payoneer_payments_payout.processor.create_abacus_state_by_parent_table') @patch('payoneer_payments_payout.processor.create_abacus_event') @patch('payoneer_payments_payout.processor.get_payment_entity') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch_accounts') @patch('payoneer_payments_payout.processor.post_payment_group_payment_batch') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_payment_group_payment_accounts' ) @patch('payoneer_payments_payout.processor.PaymentProcessor') @patch('payoneer_payments_payout.processor.update_abacus_state_by_id') @patch( 'payoneer_payments_payout.processor.PayoneerPaymentsPayoutProcessor.' '_set_options' ) def test_flowthrough_sub_ledger_not_posted_when_all_entries_zero_amount( mock_set_options, mock_update_state_by_id, mock_processor, mock_payment_group_payment_accounts_req, post_payment_group_payment_batch_mock, post_payment_group_payment_batch_accounts_mock, get_payment_entity_mock, create_abacus_event_mock, create_abacus_state_by_parent_table_mock, mock_payment_entry_queue, mock_ledger_entry_queue, mock_get_accounts_brands, mock_update_flowthrough, mock_get_flowthrough_details, mock_post_flowthrough_ledger, mock_payment_group_payment_accounts, mock_payment_group_payment_batch_state, mock_event, ): """Test flowthrough sub-ledger is NOT posted when all entries have zero amount.""" mock_payment_group_payment_accounts_req.return_value = { 'items': [mock_payment_group_payment_accounts[0]], 'total_count': 1 } post_payment_group_payment_batch_mock.return_value = \ {'payment_group_payment_batch_id': 1} get_payment_entity_mock.return_value = {'payment_entity_name': 'test'} create_abacus_state_by_parent_table_mock.return_value = \ mock_payment_group_payment_batch_state mock_payment_entry_queue.return_value.flush_entries.return_value = True mock_ledger_entry_queue.return_value.flush_entries.return_value = True mock_get_flowthrough_details.return_value = [ { 'account_id': 123, 'contract_id': 829, 'currency': 'CAD', 'amount_payable': '0.00', 'target_id': 998, }, { 'account_id': 123, 'contract_id': 900, 'currency': 'USD', 'amount_payable': '0.00', 'target_id': 999, }, ] processor = PayoneerPaymentsPayoutProcessor(mock_event) processor.process() mock_get_flowthrough_details.assert_called_once_with(['17']) mock_post_flowthrough_ledger.assert_not_called() mock_update_state_by_id.assert_called_once_with( 1, {'action_status': config.ACTION_STATUSES.COMPLETE} ) mock_update_flowthrough.assert_called_once_with( ['17'], ledger_status_transition=('attached_to_payment', 'debited'), payable_details=mock_get_flowthrough_details.return_value, )