"""Tests for _format_create_mass_payouts_request function.""" import pytest from payee.logic.payoneer import _format_create_mass_payouts_request from tests.utils.factories import AccountPayeeFactory class TestFormatCreateMassPayoutsRequest: """Test suite for _format_create_mass_payouts_request function.""" def test_format_basic_payout_list(self): """Test formatting a basic payout list without account_payees_map.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Payment for services', 'currency': 'USD', 'amount': 100.50, }, { 'client_reference_id': 'ref2', 'account_payee_id': '456', 'description': 'Invoice payment', 'currency': 'EUR', 'amount': 250.75, }, ] result = _format_create_mass_payouts_request(payout_list) assert result == { 'Payments': [ { 'client_reference_id': 'ref1', 'payee_id': '123', 'description': 'Payment for services', 'currency': 'USD', 'amount': 100.50, }, { 'client_reference_id': 'ref2', 'payee_id': '456', 'description': 'Invoice payment', 'currency': 'EUR', 'amount': 250.75, }, ] } def test_format_with_account_payee_description_override(self): """Test that account_payee.payment_description overrides payout description.""" account_payee = AccountPayeeFactory.create( account_payee_id=123, payment_description='Account Payment Description' ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Original description', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == 'Account Payment Description' def test_format_with_empty_account_payee_description(self): """Test that empty account_payee.payment_description uses payout description.""" account_payee = AccountPayeeFactory.create( account_payee_id=123, payment_description='' ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Fallback description', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == 'Fallback description' def test_format_with_missing_description_field(self): """Test formatting when description field is missing from payout.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'currency': 'USD', 'amount': 100.00, } ] with pytest.raises(ValueError) as exc_info: _format_create_mass_payouts_request(payout_list) assert 'Description is empty after sanitization' in str(exc_info.value) def test_format_with_missing_description_but_account_payee_has_one(self): """Test that account_payee description is used when payout has no description.""" account_payee = AccountPayeeFactory.create( account_payee_id=123, payment_description='Account Payment Description' ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == 'Account Payment Description' def test_sanitize_special_characters(self): """Test that special characters are properly sanitized from description.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Payment with special chars: !@#$%^&*()_+{}|:"<>?/.,;\'[]=-`~', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) # Only allowed characters should remain assert ( result['Payments'][0]['description'] == 'Payment with special chars: !@$%&()_+:">/.,\'-' ) def test_sanitize_removes_disallowed_characters(self): """Test that disallowed characters are removed during sanitization.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Test{}[]|<>~`description', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) # Disallowed characters should be removed assert '{}' not in result['Payments'][0]['description'] assert '[]' not in result['Payments'][0]['description'] assert '|' not in result['Payments'][0]['description'] assert '<>' not in result['Payments'][0]['description'] assert '~' not in result['Payments'][0]['description'] assert '`' not in result['Payments'][0]['description'] def test_truncate_description_to_250_characters(self): """Test that description is truncated to 250 characters max.""" long_description = 'A' * 500 # 500 characters payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': long_description, 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert len(result['Payments'][0]['description']) == 250 assert result['Payments'][0]['description'] == 'A' * 250 def test_truncate_after_sanitization(self): """Test that description is truncated after sanitization, not before.""" # Create a 300 character description with some special chars description = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': description, 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) # Should be truncated to 250 chars after sanitization assert len(result['Payments'][0]['description']) == 250 def test_empty_description_after_sanitization_raises_error(self): """Test that ValueError is raised if description is empty after sanitization.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': '?#{}[]|', # Only disallowed characters 'currency': 'USD', 'amount': 100.00, } ] with pytest.raises(ValueError) as exc_info: _format_create_mass_payouts_request(payout_list) assert 'Description is empty after sanitization' in str(exc_info.value) assert '?#{}[]|' in str(exc_info.value) def test_empty_payout_list(self): """Test formatting with an empty payout list.""" result = _format_create_mass_payouts_request([]) assert result == {'Payments': []} def test_none_payout_list(self): """Test formatting with None as payout list.""" result = _format_create_mass_payouts_request(None) assert result == {'Payments': []} def test_payout_description_used_when_account_payee_not_found(self): """Test that payout description is used when no matching account_payee exists.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Test payment', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == 'Test payment' def test_mixed_account_payee_descriptions(self): """Test mix of account_payees with and without payment_description.""" account_payee1 = AccountPayeeFactory.create( account_payee_id=123, payment_description='Override Description 1' ) account_payee2 = AccountPayeeFactory.create( account_payee_id=456, payment_description='' ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Original 1', 'currency': 'USD', 'amount': 100.00, }, { 'client_reference_id': 'ref2', 'account_payee_id': '456', 'description': 'Original 2', 'currency': 'EUR', 'amount': 200.00, }, ] result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == 'Override Description 1' assert result['Payments'][1]['description'] == 'Original 2' def test_account_payee_id_conversion_to_int(self): """Test that account_payee_id is properly converted to int.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Test', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) # payee_id should be string in output assert result['Payments'][0]['payee_id'] == '123' assert isinstance(result['Payments'][0]['payee_id'], str) def test_preserve_all_payout_fields(self): """Test that all payout fields are preserved in the output.""" payout_list = [ { 'client_reference_id': 'unique-ref-123', 'account_payee_id': '789', 'description': 'Payment description', 'currency': 'GBP', 'amount': 999.99, } ] result = _format_create_mass_payouts_request(payout_list) payment = result['Payments'][0] assert payment['client_reference_id'] == 'unique-ref-123' assert payment['payee_id'] == '789' assert payment['description'] == 'Payment description' assert payment['currency'] == 'GBP' assert payment['amount'] == 999.99 def test_multiple_payouts_processed_correctly(self): """Test that multiple payouts are all processed correctly.""" payout_list = [ { 'client_reference_id': f'ref{i}', 'account_payee_id': str(100 + i), 'description': f'Payment {i}', 'currency': 'USD', 'amount': 100.00 * i, } for i in range(1, 6) ] result = _format_create_mass_payouts_request(payout_list) assert len(result['Payments']) == 5 for i, payment in enumerate(result['Payments'], start=1): assert payment['client_reference_id'] == f'ref{i}' assert payment['payee_id'] == str(100 + i) assert payment['description'] == f'Payment {i}' assert payment['amount'] == 100.00 * i def test_account_payee_description_with_special_chars_sanitized(self): """Test that account_payee description is also sanitized.""" account_payee = AccountPayeeFactory.create( account_payee_id=123, payment_description='Payment with {}[]| special chars', ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Original', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) # Special characters should be removed from account_payee description too assert '{}' not in result['Payments'][0]['description'] assert '[]' not in result['Payments'][0]['description'] assert '|' not in result['Payments'][0]['description'] def test_account_payee_description_truncated_to_250_chars(self): """Test that account_payee description is truncated to 250 characters.""" long_description = 'B' * 300 account_payee = AccountPayeeFactory.create( account_payee_id=123, payment_description=long_description ) payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': 'Original', 'currency': 'USD', 'amount': 100.00, } ] result = _format_create_mass_payouts_request(payout_list) assert len(result['Payments'][0]['description']) == 250 assert result['Payments'][0]['description'] == 'B' * 250 def test_whitespace_only_description_after_sanitization(self): """Test that whitespace-only description after sanitization raises error.""" payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': ' ', # Only whitespace 'currency': 'USD', 'amount': 100.00, } ] # Whitespace should be preserved, so this should work result = _format_create_mass_payouts_request(payout_list) assert result['Payments'][0]['description'] == ' ' def test_allowed_special_characters_preserved(self): """Test that allowed special characters are preserved in description.""" description = 'Payment: $100.50 (Service) & Support - Invoice #123' payout_list = [ { 'client_reference_id': 'ref1', 'account_payee_id': '123', 'description': description, 'currency': 'USD', 'amount': 100.50, } ] result = _format_create_mass_payouts_request(payout_list) # These characters should be allowed based on the regex pattern result_desc = result['Payments'][0]['description'] assert '$' in result_desc assert '.' in result_desc assert '(' in result_desc assert ')' in result_desc assert '&' in result_desc assert '-' in result_desc