"""Tests for payoneer serialization.""" from marshmallow import ValidationError import pytest from payee.constants.constants import PAYONEER_STATUS_CODE, PAYONEER_STATUS_DESC from payee.schemas.payoneer import ( CreateMassPayoutsSchema, CreatePayoneerRegistrationLinkSchema, MovePayoneerProgramSchema, PayeeRegistrationFormatGetSchema, PayeeRegistrationFormatSchema, PayoneerStatusResponseSchema, ) from tests.utils.factories import ( PayeeRegistrationFormatDumpedFactory, PayeeRegistrationFormatFactory, PayeeRegistrationFormatGetFactory, ) def test_create_payoneer_registration_link_schema(): """Test payoneer post serialization.""" params = { 'payoneer_program_id': 456, 'account_payee_id': 123, 'payee': { 'first_name': 'Joen', 'last_name': 'Doe', 'email': 'some_email@fake.com', }, } result = CreatePayoneerRegistrationLinkSchema().load(params) assert result == params def test_create_payoneer_registration_link_schema_required(): """Test payoneer post serialization required fields.""" params = {} with pytest.raises(ValidationError): CreatePayoneerRegistrationLinkSchema().load(params) def test_move_payoneer_program_schema(): """Test move payoneer program serialization.""" params = { 'existing_payoneer_program_id': 123, 'new_payoneer_program_id': 345, } result = MovePayoneerProgramSchema().load(params) assert result == params def test_move_payoneer_program_schema_required(): """Test payoneer post serialization.""" params = {} with pytest.raises(ValidationError) as excinfo: MovePayoneerProgramSchema().load(params) assert excinfo.value.messages_dict == { 'existing_payoneer_program_id': ['Must be specified.'], 'new_payoneer_program_id': ['Must be specified.'], } def test_payee_registration_format_response_schema(): """Test payee registration format response serialization.""" result = PayeeRegistrationFormatSchema().dump( PayeeRegistrationFormatFactory.build() ) assert result == PayeeRegistrationFormatDumpedFactory.build() def test_payee_registration_format_request_schema(): """Test payee registration format request deserialization.""" data = PayeeRegistrationFormatGetFactory.build() result = PayeeRegistrationFormatGetSchema().load(data) assert result == data result = PayeeRegistrationFormatGetSchema().load( {k: f' {v} ' for k, v in data.items()} ) assert result == data def test_payee_registration_format_request_schema_failure_lower(): """Test payee registration format request deserialization failure lowercase.""" data = PayeeRegistrationFormatGetFactory.build( bank_account_type='individual', bank_country='ad', bank_currency='eur', ) with pytest.raises(ValidationError) as exc: PayeeRegistrationFormatGetSchema().load(data) assert exc == { 'bank_account_type': ['Must be one of: INDIVIDUAL, COMPANY.'], 'bank_country': ['String does not match expected pattern.'], 'bank_currency': ['String does not match expected pattern.'], } @pytest.mark.parametrize('status_code', list(PAYONEER_STATUS_CODE.values())) def test_payoneer_status_response_schema_valid(status_code): """Test PayoneerStatusResponseSchema with valid status codes.""" status_desc = PAYONEER_STATUS_DESC[status_code] # Get matching description data = {'status_code': status_code, 'status_desc': status_desc} # Test deserialization result = PayoneerStatusResponseSchema().load(data) assert result == data # Test serialization dumped = PayoneerStatusResponseSchema().dump(data) assert dumped == data @pytest.mark.parametrize( 'data,expected_error', [ ({}, 'status_code'), # Missing required fields ({'status_code': 200}, 'status_desc'), # Missing status_desc ({'status_desc': 'test'}, 'status_code'), # Missing status_code ( {'status_code': 'invalid', 'status_desc': 'test'}, 'status_code', ), # Invalid type ( {'status_code': None, 'status_desc': 'test'}, 'status_code', ), # None value for status_code ( {'status_code': 200, 'status_desc': None}, 'status_desc', ), # None value for status_desc ], ) def test_payoneer_status_response_schema_validation_errors(data, expected_error): """Test PayoneerStatusResponseSchema validation errors.""" with pytest.raises(ValidationError) as exc_info: PayoneerStatusResponseSchema().load(data) assert expected_error in exc_info.value.messages class TestCreateMassPayoutsSchema: """Tests for CreateMassPayoutsSchema validation.""" def test_valid_mass_payout_data(self): """Test schema with valid mass payout data.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'description': 'Payment for services', 'currency': 'USD', 'amount': 100.50, } result = CreateMassPayoutsSchema().load(data) assert result == {**data, 'payee_id': None} def test_valid_mass_payout_without_description(self): """Test schema with optional description field omitted.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'currency': 'USD', 'amount': 100.50, } result = CreateMassPayoutsSchema().load(data) assert result == {**data, 'payee_id': None} assert 'description' not in result def test_valid_mass_payout_with_null_description(self): """Test schema with null description.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'description': None, 'currency': 'USD', 'amount': 100.50, } result = CreateMassPayoutsSchema().load(data) assert result['description'] is None def test_long_description(self): """Test schema with long description.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'description': 'A' * 300, 'currency': 'USD', 'amount': 100, } result = CreateMassPayoutsSchema().load(data) assert len(result['description']) == 300 def test_description_with_special_characters(self): """Test schema with special characters in description.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'description': 'Payment for services (2024) - Invoice #12345', 'currency': 'USD', 'amount': 100, } result = CreateMassPayoutsSchema().load(data) assert result['description'] == 'Payment for services (2024) - Invoice #12345' def test_valid_mass_payout_with_integer_payee_id(self): """Test schema accepts an integer payee_id (legacy consumers) and coerces to str.""" data = { 'client_reference_id': 'split:20240101:1001', 'payee_id': 99001, 'description': 'Collaborator Payout', 'currency': 'USD', 'amount': 100, } result = CreateMassPayoutsSchema().load(data) assert result['payee_id'] == '99001' assert result['account_payee_id'] is None def test_invalid_negative_integer_payee_id(self): """Test schema rejects a negative integer payee_id.""" data = { 'client_reference_id': 'split:20240101:1001', 'payee_id': -1, 'description': 'Collaborator Payout', 'currency': 'USD', 'amount': 100, } with pytest.raises(ValidationError) as exc_info: CreateMassPayoutsSchema().load(data) assert 'payee_id' in exc_info.value.messages def test_valid_mass_payout_with_uuid_payee_id(self): """Test schema accepts a UUID string payee_id (collaborator payouts).""" data = { 'client_reference_id': 'split:20240101:1001', 'payee_id': 'a1b2c3d4-e5f6-4abc-8def-012345678901', 'description': 'Collaborator Payout', 'currency': 'USD', 'amount': 100, } result = CreateMassPayoutsSchema().load(data) assert result['payee_id'] == 'a1b2c3d4-e5f6-4abc-8def-012345678901' assert result['account_payee_id'] is None def test_invalid_non_uuid_string_payee_id(self): """Test schema rejects a non-UUID string payee_id.""" data = { 'client_reference_id': 'split:20240101:1001', 'payee_id': 'not-a-uuid', 'description': 'Collaborator Payout', 'currency': 'USD', 'amount': 100, } with pytest.raises(ValidationError) as exc_info: CreateMassPayoutsSchema().load(data) assert 'payee_id' in exc_info.value.messages def test_valid_mass_payout_with_account_payee_id_only(self): """Test schema still accepts account_payee_id-only entries (regression guard).""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'currency': 'USD', 'amount': 100, } result = CreateMassPayoutsSchema().load(data) assert result['account_payee_id'] == '56732' assert result['payee_id'] is None def test_invalid_missing_both_ids(self): """Test schema rejects an entry with neither account_payee_id nor payee_id.""" data = { 'client_reference_id': 'test123', 'currency': 'USD', 'amount': 100, } with pytest.raises(ValidationError) as exc: CreateMassPayoutsSchema().load(data) assert '_schema' in exc.value.messages def test_invalid_both_ids_present(self): """Test schema rejects an entry supplying both account_payee_id and payee_id.""" data = { 'client_reference_id': 'test123', 'account_payee_id': '56732', 'payee_id': 'a1b2c3d4-e5f6-4abc-8def-012345678901', 'currency': 'USD', 'amount': 100, } with pytest.raises(ValidationError) as exc: CreateMassPayoutsSchema().load(data) assert '_schema' in exc.value.messages def test_valid_many_schema_mixed(self): """Test schema accepts a mixed batch of account_payee and collaborator entries.""" data = [ { 'client_reference_id': 'acct1', 'account_payee_id': '56732', 'currency': 'USD', 'amount': 100, }, { 'client_reference_id': 'split:20240101:1001', 'payee_id': 'a1b2c3d4-e5f6-4abc-8def-012345678901', 'currency': 'USD', 'amount': 200, }, ] results = CreateMassPayoutsSchema(many=True).load(data) assert len(results) == 2 assert results[0]['account_payee_id'] == '56732' assert results[0]['payee_id'] is None assert results[1]['payee_id'] == 'a1b2c3d4-e5f6-4abc-8def-012345678901' assert results[1]['account_payee_id'] is None