"""Unit tests for payment allocation schemas.""" import re from marshmallow import ValidationError import pytest from payment.constants.constants import ( DEFAULT_BULK_MAX_LIMIT, PAYMENT_ALLOCATION_LEDGER_STATUSES, PAYMENT_ALLOCATION_STATUSES, ) from payment.constants.error import ( ERROR_ALLOCATION_DUPLICATE, ERROR_BULK_MAX_LIMIT, ERROR_BULK_REQUIRED_FIELDS, ) from payment.schemas.payment_allocation import ( PaymentAllocationFlowthroughBulkDeleteSchema, PaymentAllocationFlowthroughBulkRequestSchema, PaymentAllocationFlowthroughBulkUpdateSchema, PaymentAllocationFlowthroughSchema, ) def test_bulk_request_schema_valid_with_payment_allocation_ids(): """Test valid bulk request schema with payment_allocation_ids.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'payment_allocation_ids': [1, 2, 3], } result = schema.load(data) assert result['payment_allocation_ids'] == [1, 2, 3] def test_bulk_request_schema_valid_with_contract_ids(): """Test valid bulk request schema with contract_ids.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'contract_ids': [100, 200], } result = schema.load(data) assert result['contract_ids'] == [100, 200] def test_bulk_request_schema_valid_with_payment_statuses(): """Test valid bulk request schema with payment_statuses.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'payment_statuses': [ PAYMENT_ALLOCATION_STATUSES.INIT, PAYMENT_ALLOCATION_STATUSES.PAID, ], } result = schema.load(data) assert len(result['payment_statuses']) == 2 def test_bulk_request_schema_valid_with_ledger_statuses(): """Test valid bulk request schema with ledger_statuses.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'ledger_statuses': [PAYMENT_ALLOCATION_LEDGER_STATUSES.DEBITED], } result = schema.load(data) assert len(result['ledger_statuses']) == 1 def test_bulk_request_schema_valid_with_multiple_filters(): """Test valid bulk request schema with multiple filters.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'payment_allocation_ids': [1, 2], 'contract_ids': [100], 'payment_statuses': [PAYMENT_ALLOCATION_STATUSES.INIT], 'ledger_statuses': [PAYMENT_ALLOCATION_LEDGER_STATUSES.DEBITED], } result = schema.load(data) assert result['payment_allocation_ids'] == [1, 2] assert result['contract_ids'] == [100] assert len(result['payment_statuses']) == 1 assert len(result['ledger_statuses']) == 1 def test_bulk_request_schema_missing_filter_raises_error(): """Test that missing all filters raises ValidationError.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = {} with pytest.raises(ValidationError) as exc_info: schema.load(data) assert 'At least one filter must be provided' in str(exc_info.value) def test_bulk_request_schema_duplicate_payment_allocation_ids_raises_error(): """Test that duplicate payment_allocation_ids raises ValidationError.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'payment_allocation_ids': [1, 2, 3, 2], # duplicate 2 } with pytest.raises(ValidationError) as exc_info: schema.load(data) assert 'payment_allocation_ids contains duplicate values' in str(exc_info.value) def test_bulk_request_schema_duplicate_contract_ids_raises_error(): """Test that duplicate contract_ids raises ValidationError.""" schema = PaymentAllocationFlowthroughBulkRequestSchema() data = { 'contract_ids': [100, 200, 100], # duplicate 100 } with pytest.raises(ValidationError) as exc_info: schema.load(data) assert 'contract_ids contains duplicate values' in str(exc_info.value) def test_flowthrough_schema_serialization(): """Test that PaymentAllocationFlowthroughSchema serializes correctly.""" from datetime import datetime from unittest.mock import Mock schema = PaymentAllocationFlowthroughSchema() # Create a mock object with all required attributes allocation = Mock() allocation.payment_allocation_id = 1 allocation.contract_id = 100 allocation.payee_type = 'payee' allocation.payee_id = 1 allocation.statement_period_id = 1 allocation.payment_allocation_type = 'flowthrough' allocation.amount_to_payment = '1000.00' allocation.payment_status = PAYMENT_ALLOCATION_STATUSES.INIT allocation.payment_status_modified = None allocation.amount_to_ledger = '1000.00' allocation.ledger_status = PAYMENT_ALLOCATION_LEDGER_STATUSES.DEBITED allocation.ledger_status_modified = None allocation.currency_code = 'USD' allocation.description = 'Test allocation' allocation.created_at = datetime(2024, 1, 1, 0, 0, 0) allocation.created_by = 'test_user' allocation.last_modified = datetime(2024, 1, 2, 0, 0, 0) allocation.last_modified_by = 'test_user_2' allocation.deleted_at = None result = schema.dump(allocation) assert result['payment_allocation_id'] == 1 assert result['contract_id'] == 100 assert result['currency_code'] == 'USD' assert result['amount_to_payment'] == '1000.00' assert result['amount_to_ledger'] == '1000.00' assert result['created_by'] == 'test_user' assert result['last_modified_by'] == 'test_user_2' assert 'created_at' in result assert 'last_modified' in result def test_bulk_update_schema_valid_single(): schema = PaymentAllocationFlowthroughBulkUpdateSchema() data = _make_valid_item() result = schema.load(data) assert result['payment_allocation_id'] == 1 assert result['payment_status'] == PAYMENT_ALLOCATION_STATUSES[0] assert result['ledger_status'] == PAYMENT_ALLOCATION_LEDGER_STATUSES[0] def test_bulk_update_schema_valid_many(): schema = PaymentAllocationFlowthroughBulkUpdateSchema(many=True) data = [_make_valid_item(i) for i in range(3)] result = schema.load(data) assert len(result) == 3 def test_bulk_update_schema_missing_required(): schema = PaymentAllocationFlowthroughBulkUpdateSchema() data = {} with pytest.raises(ValidationError) as exc: schema.load(data) errors = exc.value.messages assert 'payment_allocation_id' in errors assert 'payment_status' not in errors assert 'ledger_status' not in errors def test_bulk_update_schema_invalid_enum(): schema = PaymentAllocationFlowthroughBulkUpdateSchema() data = { 'payment_allocation_id': 1, 'payment_status': 'INVALID_STATUS', 'ledger_status': 'INVALID_LEDGER', } with pytest.raises(ValidationError) as exc: schema.load(data) errors = exc.value.messages assert 'payment_status' in errors assert 'ledger_status' in errors def test_bulk_update_schema_negative_id(): schema = PaymentAllocationFlowthroughBulkUpdateSchema() data = _make_valid_item() data['payment_allocation_id'] = -5 with pytest.raises(ValidationError) as exc: schema.load(data) assert 'payment_allocation_id' in exc.value.messages def test_bulk_update_schema_bulk_max_limit(): schema = PaymentAllocationFlowthroughBulkUpdateSchema(many=True) data = [_make_valid_item() for _ in range(DEFAULT_BULK_MAX_LIMIT + 1)] with pytest.raises(ValidationError) as exc: schema.load(data) assert str(DEFAULT_BULK_MAX_LIMIT) in str(exc.value) def test_bulk_update_schema_duplicate_ids(): schema = PaymentAllocationFlowthroughBulkUpdateSchema(many=True) item = _make_valid_item() data = [item, dict(item)] with pytest.raises(ValidationError, match=ERROR_ALLOCATION_DUPLICATE): schema.load(data) def test_bulk_update_schema_item_missing_both_statuses_raises_error_single_item(): schema = PaymentAllocationFlowthroughBulkUpdateSchema(many=True) data = [ { 'payment_allocation_id': 1, } ] with pytest.raises( ValidationError, match=re.escape(ERROR_BULK_REQUIRED_FIELDS.format([1])) ): schema.load(data) def test_bulk_update_schema_item_missing_both_statuses_raises_error_in_mixed_batch(): schema = PaymentAllocationFlowthroughBulkUpdateSchema(many=True) data = [ _make_valid_item(1), { 'payment_allocation_id': 2, }, { 'payment_allocation_id': 3, 'payment_status': PAYMENT_ALLOCATION_STATUSES[0], }, { 'payment_allocation_id': 4, 'ledger_status': PAYMENT_ALLOCATION_LEDGER_STATUSES[0], }, ] with pytest.raises( ValidationError, match=re.escape(ERROR_BULK_REQUIRED_FIELDS.format([2])) ): schema.load(data) def _make_valid_item(payment_allocation_id=1): return { 'payment_allocation_id': payment_allocation_id, 'payment_status': PAYMENT_ALLOCATION_STATUSES[0], 'ledger_status': PAYMENT_ALLOCATION_LEDGER_STATUSES[0], } def test_bulk_delete_schema_valid(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() data = {'payment_allocation_ids': [1, 2, 3]} result = schema.load(data) assert result['payment_allocation_ids'] == [1, 2, 3] def test_bulk_delete_schema_missing_field_raises(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() with pytest.raises(ValidationError) as exc: schema.load({}) assert 'payment_allocation_ids' in exc.value.messages @pytest.mark.parametrize( 'payload', [ {'payment_allocation_ids': None}, {'payment_allocation_ids': '1'}, {'payment_allocation_ids': 1}, ], ) def test_bulk_delete_schema_invalid_type_raises(payload): schema = PaymentAllocationFlowthroughBulkDeleteSchema() with pytest.raises(ValidationError) as exc: schema.load(payload) assert 'payment_allocation_ids' in exc.value.messages def test_bulk_delete_schema_negative_id_raises(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() with pytest.raises(ValidationError) as exc: schema.load({'payment_allocation_ids': [-1]}) assert 'payment_allocation_ids' in exc.value.messages def test_bulk_delete_schema_non_integer_id_raises(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() with pytest.raises(ValidationError) as exc: schema.load({'payment_allocation_ids': ['abc']}) assert 'payment_allocation_ids' in exc.value.messages def test_bulk_delete_schema_empty_list_allowed(): # Schema does not currently enforce non-empty list, so empty should load. schema = PaymentAllocationFlowthroughBulkDeleteSchema() result = schema.load({'payment_allocation_ids': []}) assert result['payment_allocation_ids'] == [] def test_bulk_delete_schema_over_bulk_max_limit_raises(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() payload = {'payment_allocation_ids': list(range(1, DEFAULT_BULK_MAX_LIMIT + 2))} with pytest.raises(ValidationError) as exc: schema.load(payload) assert str(ERROR_BULK_MAX_LIMIT.format(DEFAULT_BULK_MAX_LIMIT)) in str(exc.value) def test_bulk_delete_schema_duplicate_ids_raises(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() payload = {'payment_allocation_ids': [1, 2, 2]} with pytest.raises(ValidationError) as exc: schema.load(payload) assert str(ERROR_ALLOCATION_DUPLICATE) in str(exc.value) def test_bulk_delete_schema_at_bulk_max_limit_is_allowed(): schema = PaymentAllocationFlowthroughBulkDeleteSchema() payload = {'payment_allocation_ids': list(range(1, DEFAULT_BULK_MAX_LIMIT + 1))} result = schema.load(payload) assert len(result['payment_allocation_ids']) == DEFAULT_BULK_MAX_LIMIT