"""Payoneer Payments Test Suite.""" from unittest.mock import Mock, patch import pytest from src.processors.models import EligibleAccountLevelData from src.processors.payoneer_payments import payoneer_processor from tests.unit.factories import ( AccountFactory, EventFactory, PaymentEntityFactory, PaymentGroupFactory, ) mock_event = EventFactory.build() mock_group = PaymentGroupFactory.build(group_criteria={}) class TestPayoneerProcessor: """Test PayoneerProcessor.""" @patch.object(payoneer_processor, 'get_eligible_accounts') def test_get_payment_group_accounts(self, mock_get_eligible_accounts: Mock) -> None: """Test _get_payment_group_accounts returns list of EligibleAccountLevelData.""" mock_account1 = AccountFactory.build( account_id=1, country_of_tax_residence='USA', currency_code='USD', ) mock_account2 = AccountFactory.build( account_id=2, country_of_tax_residence='GBR', currency_code='GBP', ) mock_get_eligible_accounts.return_value = [mock_account1, mock_account2] test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) response = test_processor._get_payment_group_accounts() mock_get_eligible_accounts.assert_called_once_with(mock_group.payment_group_id) assert len(response) == 2 assert response[0].account_id == mock_account1.account_id assert ( response[0].country_of_tax_residence == mock_account1.country_of_tax_residence ) assert response[0].currency_code == mock_account1.currency_code assert response[0].country_of_tax_policy == None assert response[1].account_id == mock_account2.account_id assert response[1].country_of_tax_policy == None @patch.object(payoneer_processor, 'get_eligible_accounts') def test_get_payment_group_accounts_with_none_country( self, mock_get_eligible_accounts: Mock ) -> None: """Test _get_payment_group_accounts skips accounts when country_of_tax_residence is None.""" mock_account = AccountFactory.build( account_id=1, country_of_tax_residence=None, currency_code='USD', ) mock_get_eligible_accounts.return_value = [mock_account] test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._payment_entity_policy_country_mapping = {1: 'USA'} # Should skip accounts with None country_of_tax_residence response = test_processor._get_payment_group_accounts() mock_get_eligible_accounts.assert_called_once_with(mock_group.payment_group_id) assert len(response) == 0 # Account should be skipped @patch.object(payoneer_processor, 'get_eligible_accounts') def test_get_payment_group_accounts_raises_exception( self, mock_get_eligible_accounts: Mock ) -> None: """Test _get_payment_group_accounts propagates exceptions.""" mock_get_eligible_accounts.side_effect = Exception('API Error') test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) with pytest.raises(Exception, match='API Error'): test_processor._get_payment_group_accounts() @patch.object( payoneer_processor, 'create_payment_entity_to_country_of_tax_reporting_mapping' ) @patch.object(payoneer_processor, 'get_payment_entities') def test_get_payment_entity_mapping( self, mock_get_payment_entities: Mock, mock_create_mapping: Mock, ) -> None: """Test _get_payment_entity_mapping returns payment entity to country mapping.""" mock_entity1 = PaymentEntityFactory.build(reference_payment_entity_id=1) mock_entity2 = PaymentEntityFactory.build(reference_payment_entity_id=2) mock_get_payment_entities.return_value = [mock_entity1, mock_entity2] mock_mapping = {1: 'USA', 2: 'GBR'} mock_create_mapping.return_value = mock_mapping test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) response = test_processor._get_payment_entity_mapping() mock_get_payment_entities.assert_called_once() mock_create_mapping.assert_called_once_with([mock_entity1, mock_entity2]) assert response == mock_mapping @patch.object( payoneer_processor, 'create_payment_entity_to_country_of_tax_reporting_mapping' ) @patch.object(payoneer_processor, 'get_payment_entities') def test_get_payment_entity_mapping_empty( self, mock_get_payment_entities: Mock, mock_create_mapping: Mock, ) -> None: """Test _get_payment_entity_mapping with no payment entities.""" mock_get_payment_entities.return_value = [] mock_create_mapping.return_value = {} test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) response = test_processor._get_payment_entity_mapping() mock_get_payment_entities.assert_called_once() mock_create_mapping.assert_called_once_with([]) assert response == {} @patch.object( payoneer_processor, 'create_payment_entity_to_country_of_tax_reporting_mapping' ) @patch.object(payoneer_processor, 'get_payment_entities') def test_get_payment_entity_mapping_raises_exception( self, mock_get_payment_entities: Mock, mock_create_mapping: Mock, ) -> None: """Test _get_payment_entity_mapping propagates exceptions.""" mock_get_payment_entities.side_effect = Exception('Payment Entity API Error') test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) with pytest.raises(Exception, match='Payment Entity API Error'): test_processor._get_payment_entity_mapping() @patch.object(payoneer_processor.PayoneerProcessor, '_get_payment_group_accounts') def test_set_all_eligible_accounts( self, mock_get_payment_group_accounts: Mock ) -> None: """Test _set_all_eligible_accounts sets the accounts list.""" mock_accounts = [ Mock(account_id=1), Mock(account_id=2), ] mock_get_payment_group_accounts.return_value = mock_accounts test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._set_all_eligible_accounts() assert test_processor._all_eligible_accounts == mock_accounts mock_get_payment_group_accounts.assert_called_once() def test_set_total_count(self) -> None: """Test _set_total_count sets the total count from accounts list.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._all_eligible_accounts = [Mock(), Mock(), Mock()] test_processor._set_total_count() assert test_processor._total_count == 3 @patch.object(payoneer_processor.PayoneerProcessor, '_get_payment_entity_mapping') def test_set_payment_entity_policy_country_mapping( self, mock_get_payment_entity_mapping: Mock ) -> None: """Test _set_payment_entity_policy_country_mapping sets the mapping.""" mock_mapping = {1: 'USA', 2: 'GBR'} mock_get_payment_entity_mapping.return_value = mock_mapping test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._set_payment_entity_policy_country_mapping() assert test_processor._payment_entity_policy_country_mapping == mock_mapping mock_get_payment_entity_mapping.assert_called_once() def test_get_eligible_account_batch_data(self) -> None: """Test _get_eligible_account_batch_data returns correct slice of accounts.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) mock_accounts = [ EligibleAccountLevelData( account_id=i, country_of_tax_residence='USA', currency_code='USD', country_of_tax_policy='USA', ) for i in range(1, 11) ] test_processor._all_eligible_accounts = mock_accounts # Test first batch batch1 = test_processor._get_eligible_account_batch_data(offset=0) assert len(batch1) == 10 assert batch1[0].account_id == 1 # Test with offset test_processor._limit = 5 batch2 = test_processor._get_eligible_account_batch_data(offset=5) assert len(batch2) == 5 assert batch2[0].account_id == 6 @patch.object(payoneer_processor, 'PaymentBatchProcessor') def test_process_batch(self, mock_payment_batch_processor: Mock) -> None: """Test _process_batch creates and processes PaymentBatchProcessor.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) mock_accounts = [ EligibleAccountLevelData( account_id=1, country_of_tax_residence='USA', currency_code='USD', country_of_tax_policy='USA', ), EligibleAccountLevelData( account_id=2, country_of_tax_residence='GBR', currency_code='GBP', country_of_tax_policy='GBR', ), ] test_processor._all_eligible_accounts = mock_accounts test_processor._payment_entity_policy_country_mapping = {1: 'USA'} mock_processor_instance = Mock() mock_payment_batch_processor.return_value = mock_processor_instance test_processor._process_batch(offset=0) mock_payment_batch_processor.assert_called_once_with( test_processor._abacus_event, mock_accounts[0:100], payment_entity_policy_country_mapping=test_processor._payment_entity_policy_country_mapping, ) mock_processor_instance.process.assert_called_once() @patch.object(payoneer_processor.PayoneerProcessor, '_process_batch') @patch.object(payoneer_processor.PayoneerProcessor, '_set_total_count') @patch.object(payoneer_processor.PayoneerProcessor, '_set_all_eligible_accounts') @patch.object( payoneer_processor.PayoneerProcessor, '_set_payment_entity_policy_country_mapping', ) def test_process_success( self, mock_set_mapping: Mock, mock_set_accounts: Mock, mock_set_count: Mock, mock_process_batch: Mock, ) -> None: """Test process method processes all batches successfully.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._payment_entity_policy_country_mapping = {1: 'USA'} test_processor._all_eligible_accounts = [ EligibleAccountLevelData( account_id=i, country_of_tax_residence='USA', currency_code='USD', country_of_tax_policy='USA', ) for i in range(1, 6) ] test_processor._total_count = 5 test_processor._limit = 2 test_processor.process() mock_set_mapping.assert_called_once() mock_set_accounts.assert_called_once() mock_set_count.assert_called_once() # Should process 3 batches: offset 0, 2, 4 assert mock_process_batch.call_count == 3 mock_process_batch.assert_any_call(0) mock_process_batch.assert_any_call(2) mock_process_batch.assert_any_call(4) @patch.object(payoneer_processor.PayoneerProcessor, '_process_batch') @patch.object(payoneer_processor.PayoneerProcessor, '_set_total_count') @patch.object(payoneer_processor.PayoneerProcessor, '_set_all_eligible_accounts') @patch.object( payoneer_processor.PayoneerProcessor, '_set_payment_entity_policy_country_mapping', ) def test_process_exact_batch_size( self, mock_set_mapping: Mock, mock_set_accounts: Mock, mock_set_count: Mock, mock_process_batch: Mock, ) -> None: """Test process with exactly one batch of accounts.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._payment_entity_policy_country_mapping = {1: 'USA'} test_processor._limit = 100 test_processor._all_eligible_accounts = [ EligibleAccountLevelData( account_id=i, country_of_tax_residence='USA', currency_code='USD', country_of_tax_policy='USA', ) for i in range(1, 101) ] test_processor._total_count = 100 test_processor.process() mock_set_mapping.assert_called_once() mock_set_accounts.assert_called_once() mock_set_count.assert_called_once() # Should process exactly 1 batch: offset 0 assert mock_process_batch.call_count == 1 mock_process_batch.assert_called_once_with(0) @patch.object(payoneer_processor.PayoneerProcessor, '_process_batch') @patch.object(payoneer_processor.PayoneerProcessor, '_set_total_count') @patch.object(payoneer_processor.PayoneerProcessor, '_set_all_eligible_accounts') @patch.object( payoneer_processor.PayoneerProcessor, '_set_payment_entity_policy_country_mapping', ) def test_process_no_mapping( self, mock_set_mapping: Mock, mock_set_accounts: Mock, mock_set_count: Mock, mock_process_batch: Mock, ) -> None: """Test process method returns early when no payment entity mapping exists.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._payment_entity_policy_country_mapping = {} test_processor.process() mock_set_mapping.assert_called_once() mock_set_accounts.assert_not_called() mock_set_count.assert_not_called() mock_process_batch.assert_not_called() @patch.object(payoneer_processor.PayoneerProcessor, '_process_batch') @patch.object(payoneer_processor.PayoneerProcessor, '_set_total_count') @patch.object(payoneer_processor.PayoneerProcessor, '_set_all_eligible_accounts') @patch.object( payoneer_processor.PayoneerProcessor, '_set_payment_entity_policy_country_mapping', ) def test_process_no_eligible_accounts( self, mock_set_mapping: Mock, mock_set_accounts: Mock, mock_set_count: Mock, mock_process_batch: Mock, ) -> None: """Test process method returns early when no eligible accounts.""" test_processor = payoneer_processor.PayoneerProcessor(mock_group, mock_event) test_processor._payment_entity_policy_country_mapping = {1: 'USA'} test_processor._all_eligible_accounts = [] test_processor._total_count = 0 test_processor.process() mock_set_mapping.assert_called_once() mock_set_accounts.assert_called_once() mock_set_count.assert_called_once() mock_process_batch.assert_not_called()