"""Lambda test module.""" from unittest import mock from unittest.mock import call, Mock, patch import pytest from src import constants, main from src.connectors.exceptions import OwsEventException, OwsPaymentException from src.connectors.ows_state import OwsStateException from tests.unit.factories import ( EventFactory, PaymentGroupFactory, PaymentGroupPaymentFactory, ) @patch('src.main.get_payment_group') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.PayoneerProcessor.process', side_effect=Exception('Test')) @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_failure( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_payoneer_process: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_get_payment_group: mock.MagicMock, ) -> None: """Test main.handler failure.""" payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build() mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_get_payment_group_payment.assert_called_once_with(event.target_id) mock_payoneer_process.assert_called_once() mock_create_event.assert_not_called() mock_rollback_changes.assert_called_once_with(event) assert result['statusCode'] == 400 assert ( result['statusDescription'] == 'Failed to calculate payments, exception details: Test' ) # noqa: E501 @patch('src.main.create_event') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_failure_event_validation( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_create_event: mock.MagicMock, ) -> None: """Test main.handler.""" event_dict = {'not_existing_field': 'some_value'} result = main.handler(event_dict, None) mock_state_update_handler.assert_not_called() # no target id in event_dict mock_get_payment_group_payment.assert_not_called() mock_create_event.assert_not_called() assert result['statusCode'] == 400 @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_update_state_when_handler_failed( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, ) -> None: """Test main.handler failure""" event = EventFactory.build() mock_get_payment_group_payment.side_effect = OwsPaymentException('Test exception') result = main.handler(event.model_dump(), None) mock_get_payment_group_payment.assert_called_once_with(event.target_id) mock_create_event.assert_not_called() mock_rollback_changes.assert_called_once_with(event) mock_state_update_handler.assert_called_once_with( constants.PARENT_TABLE_NAME, event.model_dump()['target_id'], 'error' ) assert result['statusCode'] == 400 assert ( result['statusDescription'] == 'Failed to calculate payments, exception details: ows-payment failure: Test exception {}' ) @patch('src.main.get_payment_group') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.PayoneerProcessor.process') @patch('src.main.get_payment_group_payment') @patch( 'src.main._state_update_handler', side_effect=OwsStateException('state not found') ) def test_update_state_failed( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_payoneer_process: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_get_payment_group: mock.MagicMock, ) -> None: """Test main.handler failure""" payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build() mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group mock_payoneer_process.return_value = None mock_state_update_handler.return_value = None result = main.handler(event.model_dump(), None) mock_rollback_changes.assert_called_once_with(event) assert result['statusCode'] == 400 assert ( result['statusDescription'] == 'Failed to update abacus state: ows-abacus-state failure: state not found {} to status: complete' ) # noqa: E501 @patch('src.main.get_payment_group') @patch('src.main.create_event', side_effect=OwsEventException('error')) @patch('src.main._rollback_changes') @patch('src.main.PayoneerProcessor.process') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_create_event_failed( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_payoneer_process: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_get_payment_group: mock.MagicMock, ) -> None: """Test main.handler failure""" payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build() mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group mock_payoneer_process.return_value = None mock_state_update_handler.return_value = None result = main.handler(event.model_dump(), None) mock_rollback_changes.assert_called_once_with(event) mock_state_update_handler.assert_called_once_with( constants.PARENT_TABLE_NAME, event.model_dump()['target_id'], 'error' ) assert result['statusCode'] == 400 assert ( result['statusDescription'] == 'Failed to send generate payments event, exception details: ows-abacus-event failure: error {}' ) # noqa: E501 @patch('src.main.get_payment_group') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') @patch('src.main.CheckProcessor.process') def test_handler_check_payments( mock_check_processor_process: mock.MagicMock, mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_get_payment_group: mock.MagicMock, ) -> None: """Test handler uses the check processor for check payment groups.""" mock_criteria = {'reference_payment_type_id': 10} payment_group = PaymentGroupFactory.build(group_criteria=mock_criteria) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id, ) event = EventFactory.build() mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group mock_check_processor_process.return_value = None main.handler(event.model_dump(), None) mock_create_event.assert_called_once_with( constants.GENERATE_PAYMENTS_EVENT_NAME, event.target_type, event.target_id ) mock_state_update_handler.assert_called_once_with( constants.PARENT_TABLE_NAME, event.model_dump()['target_id'], 'complete' ) mock_rollback_changes.assert_not_called() mock_check_processor_process.assert_called_once() @patch('src.main.is_feature_enabled') @patch('src.main.PayoneerRefreshProcessor.process', side_effect=Exception('boom')) @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_payoneer_refresh_skips_rollback_on_error( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_refresh_process: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """Refresh processor errors must NOT trigger rollback (no soft-delete on refresh).""" mock_is_feature_enabled.return_value = True # draft_payment=True payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=True) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_refresh_process.assert_called_once() mock_rollback_changes.assert_not_called() mock_create_event.assert_not_called() assert result['statusCode'] == 400 @patch('src.main.is_feature_enabled') @patch('src.main.PayoneerRefreshProcessor') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_payoneer_retry_refreshes( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_refresh_cls: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """Production default: draft on + retry + non-check → plain refresh.""" mock_is_feature_enabled.return_value = True # draft_payment=True payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=True) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_refresh_cls.assert_called_once_with( payment_group=payment_group, abacus_event=event, is_check_payment=False ) mock_refresh_cls.return_value.process.assert_called_once() mock_create_event.assert_not_called() mock_rollback_changes.assert_not_called() assert result['statusCode'] == 200 @patch('src.main.is_feature_enabled') @patch('src.main.PayoneerProcessor.process') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_draft_ff_on_skips_create_event( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_payoneer_process: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """When the draft payments FF is on, the GENERATE_PAYMENTS event is not created.""" mock_is_feature_enabled.return_value = True # draft_payment=True payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=False) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_payoneer_process.assert_called_once() mock_create_event.assert_not_called() mock_rollback_changes.assert_not_called() assert result['statusCode'] == 200 @patch('src.main.is_feature_enabled') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_refresh_disabled_raises( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """Retry with draft FF off raises REFRESH_DISABLED and does not roll back.""" mock_is_feature_enabled.return_value = False # draft_payment=False payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=True) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_create_event.assert_not_called() # is_retry=True, so rollback is skipped: a retry reuses the original # event_id, and rolling back would soft-delete the original calculate's # real worksheet entries. mock_rollback_changes.assert_not_called() assert result['statusCode'] == 400 assert constants.REFRESH_DISABLED_ERR_MSG in result['statusDescription'] @patch('src.main.is_feature_enabled') @patch('src.main.PayoneerProcessor.process') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_payoneer_on_draft_off_creates_event( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_payoneer_process: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """Not a retry runs PayoneerProcessor; draft FF off keeps create_event.""" mock_is_feature_enabled.return_value = False # draft_payment=False payment_group = PaymentGroupFactory.build(group_criteria={}) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=False) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_payoneer_process.assert_called_once() mock_create_event.assert_called_once_with( constants.GENERATE_PAYMENTS_EVENT_NAME, event.target_type, event.target_id ) mock_rollback_changes.assert_not_called() assert result['statusCode'] == 200 @patch('src.main.is_feature_enabled') @patch('src.main.PayoneerRefreshProcessor') @patch('src.main.CheckProcessor.process') @patch('src.main.create_event') @patch('src.main._rollback_changes') @patch('src.main.get_payment_group') @patch('src.main.get_payment_group_payment') @patch('src.main._state_update_handler') def test_handler_check_payment_retry_refreshes_skipping_vat( mock_state_update_handler: mock.MagicMock, mock_get_payment_group_payment: mock.MagicMock, mock_get_payment_group: mock.MagicMock, mock_rollback_changes: mock.MagicMock, mock_create_event: mock.MagicMock, mock_check_process: mock.MagicMock, mock_refresh_cls: mock.MagicMock, mock_is_feature_enabled: mock.MagicMock, ) -> None: """A check-payment retry refreshes (type-agnostic) with VAT skipped, not CheckProcessor.""" mock_is_feature_enabled.return_value = True # draft_payment=True payment_group = PaymentGroupFactory.build( group_criteria={'reference_payment_type_id': 10} ) payment_group_payment = PaymentGroupPaymentFactory.build( payment_group_id=payment_group.payment_group_id ) event = EventFactory.build(is_retry=True) mock_get_payment_group_payment.return_value = payment_group_payment mock_get_payment_group.return_value = payment_group result = main.handler(event.model_dump(), None) mock_refresh_cls.assert_called_once_with( payment_group=payment_group, abacus_event=event, is_check_payment=True ) mock_refresh_cls.return_value.process.assert_called_once() mock_check_process.assert_not_called() mock_create_event.assert_not_called() mock_rollback_changes.assert_not_called() assert result['statusCode'] == 200 @patch('src.main.delete_balance_entries_after_tax') @patch('src.main.delete_payable_details_entries') def test_rollback_changes( mock_delete_balance_entries_after_tax: mock.MagicMock, mock_delete_payable_details_entries: mock.MagicMock, ) -> None: """Test main _rollback_changes method.""" event_id = 1 event = EventFactory.build(abacus_event_id=event_id) mock_delete_payable_details_entries.return_value = None mock_delete_balance_entries_after_tax.return_value = None main._rollback_changes(event) mock_delete_balance_entries_after_tax.assert_called_once_with(event_id) mock_delete_payable_details_entries.assert_called_once_with(event_id) def test_get_payment_processor_non_check_group() -> None: """Non-check payment returns PayoneerProcessor.""" from src.processors.payoneer_payments.payoneer_processor import PayoneerProcessor assert main.get_payment_processor(is_check_payment=False) == PayoneerProcessor def test_get_payment_processor_check_payment() -> None: """is_check_payment=True returns CheckProcessor (Payoneer FF not read).""" from src.processors.check_payments.check_processor import CheckProcessor assert main.get_payment_processor(is_check_payment=True) == CheckProcessor def test_is_check_payment_check_group() -> None: """A supported check reference_payment_type_id resolves to True.""" mock_group = PaymentGroupFactory.build( group_criteria={'reference_payment_type_id': 10} ) assert main._is_check_payment(mock_group) is True def test_is_check_payment_non_check_group() -> None: """A group without reference_payment_type_id resolves to False.""" mock_group = PaymentGroupFactory.build(group_criteria={}) assert main._is_check_payment(mock_group) is False def test_is_check_payment_unsupported_reference_payment_type() -> None: """An unsupported reference_payment_type_id raises.""" mock_group = PaymentGroupFactory.build( group_criteria={'reference_payment_type_id': 1} ) with pytest.raises(Exception, match=r'reference_payment_type_id not supported: 1'): main._is_check_payment(mock_group)