"""Tests for payment group payment logic.""" from unittest.mock import call, MagicMock, patch import pytest from payment.constants.error import ( ERROR_PAYMENT_NOT_FOUND, ERROR_PAYMENT_UPDATE_INVALID_STATUS, ) from payment.logic import payment_group_payment as logic from payment.logic.exceptions import LogicError from tests.utils.factories import PaymentGroupFactory, PaymentGroupPaymentFactory @patch('payment.logic.payment_group_payment.PaymentGroup') @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_create_payment_group_payment_success( mock_payment_group_payment, mock_payment_group ): """Test successful creation of payment group payment.""" payment_group = PaymentGroupFactory.create() payment_group_payment = PaymentGroupPaymentFactory.create( payment_group_id=payment_group.payment_group_id ) mock_payment_group.find_by_id_or_error.return_value = payment_group mock_payment_group_payment.create.return_value = payment_group_payment payment_name = 'a payment name' res = logic.create_payment_group_payment( payment_group.payment_group_id, payment_name ) assert res == payment_group_payment mock_payment_group_payment.create.assert_called_once_with( payment_group_id=payment_group.payment_group_id, payment_name=payment_name ) @patch('payment.logic.payment_group_payment.payment_allocation_repository') @patch('payment.logic.payment_group_payment.payable_details_repository') @patch('payment.logic.payment_group_payment.WorksheetPayableBalanceAfterTax') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccountDetail') @patch('payment.logic.payment_group_payment.PaymentGroupPayment') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccount') def test_delete_payment_group_payment( payment_account_model_mock, payment_model_mock, payment_detail_model_mock, worksheet_model_mock, payable_details_repository_mock, payment_allocation_repository_mock, ): """Test delete_payment_group_payment method.""" payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment.is_posted = MagicMock(return_value=False) payment_model_mock.get_by_id_or_error.return_value = payment_group_payment payment_account_model_mock.soft_delete_by_payment_group_payment.return_value = None payment_allocation_repository_mock.reset_to_init_by_payment_group_payment.return_value = 2 logic.delete_payment_group_payment(payment_group_payment.payment_group_payment_id) payment_model_mock.delete_by_id_or_error.assert_called_once_with( payment_group_payment.payment_group_payment_id, soft_delete=True ) payment_account_model_mock.soft_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment.payment_group_payment_id, commit=False ) payment_detail_model_mock.soft_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment.payment_group_payment_id, commit=False ) worksheet_model_mock.soft_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment.payment_group_payment_id, commit=False ) payable_details_repository_mock.soft_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment.payment_group_payment_id, commit=False ) payment_allocation_repository_mock.reset_to_init_by_payment_group_payment.assert_called_once_with( payment_group_payment.payment_group_payment_id, commit=False ) @patch('payment.logic.payment_group_payment.payment_allocation_repository') @patch('payment.logic.payment_group_payment.payable_details_repository') @patch('payment.logic.payment_group_payment.WorksheetPayableBalanceAfterTax') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccountDetail') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccount') @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_delete_payment_group_payment_resets_allocations_before_soft_deleting_details( payment_model_mock, payment_account_model_mock, payment_detail_model_mock, worksheet_model_mock, payable_details_repository_mock, payment_allocation_repository_mock, ): """Test that allocation reset happens before account details are soft-deleted. The repository reset function queries PaymentGroupPaymentAccountDetail to find contract IDs. If those records are already soft-deleted, the subquery returns no results and allocations are never reset. """ payment_group_payment = PaymentGroupPaymentFactory.create() payment_group_payment.is_posted = MagicMock(return_value=False) payment_model_mock.get_by_id_or_error.return_value = payment_group_payment manager = MagicMock() manager.attach_mock( payment_allocation_repository_mock.reset_to_init_by_payment_group_payment, 'reset_allocations', ) manager.attach_mock( payment_detail_model_mock.soft_delete_by_payment_group_payment, 'soft_delete_details', ) logic.delete_payment_group_payment(payment_group_payment.payment_group_payment_id) expected_order = [ call.reset_allocations( payment_group_payment.payment_group_payment_id, commit=False ), call.soft_delete_details( payment_group_payment.payment_group_payment_id, commit=False ), ] assert manager.mock_calls[:2] == expected_order @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_delete_payment_group_payment_for_posted_payment(payment_model_mock): """Test delete_payment_group_payment method for posted payments.""" payment_mock = MagicMock(payment_group_payment_id=111) payment_mock.is_posted = MagicMock(return_value=True) payment_model_mock.get_by_id_or_error.return_value = payment_mock with pytest.raises(LogicError, match=ERROR_PAYMENT_UPDATE_INVALID_STATUS): logic.delete_payment_group_payment(payment_mock.payment_group_payment_id) assert not payment_model_mock.delete_by_id_or_error.called @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccount') def test_get_payment_status_overview(mock_payment_account): """Test get_payment_status_overview function.""" payment_group_payment_id = 501 mock_payment_account.get_payment_group_payment_account_status_overview.return_value = { 'number_of_canceled_payments': 1, 'number_of_failed_payments': 1, 'number_of_pending_payments': 2, 'number_of_successful_payments': 1, 'number_of_payments_failed_with_batch': 2, 'payment_group_payment_id': payment_group_payment_id, } response = logic.get_payment_status_overview(payment_group_payment_id) assert response == { 'number_of_canceled_payments': 1, 'number_of_failed_payments': 1, 'number_of_pending_payments': 2, 'number_of_successful_payments': 1, 'number_of_payments_failed_with_batch': 2, 'payment_group_payment_id': payment_group_payment_id, } mock_payment_account.get_payment_group_payment_account_status_overview.assert_called_once_with( payment_group_payment_id ) @patch('payment.logic.payment_group_payment.repository') def test_get_payment_status_overview_by_ids(mock_repository): """Test get_payment_status_overview function.""" payment_group_payment_id = 501 mock_repository.get_payment_group_payment_account_status_overviews.return_value = [ { 'number_of_canceled_payments': 1, 'number_of_failed_payments': 1, 'number_of_pending_payments': 2, 'number_of_successful_payments': 1, 'number_of_payments_failed_with_batch': 2, 'payment_group_payment_id': payment_group_payment_id, } ] res = logic.dataload_payment_status_overviews([payment_group_payment_id]) assert res == { 'items': [ { 'data': { 'number_of_canceled_payments': 1, 'number_of_failed_payments': 1, 'number_of_pending_payments': 2, 'number_of_successful_payments': 1, 'number_of_payments_failed_with_batch': 2, 'payment_group_payment_id': payment_group_payment_id, } } ] } mock_repository.get_payment_group_payment_account_status_overviews.assert_called_once_with( [payment_group_payment_id] ) @patch('payment.logic.payment_group_payment.ReportPaymentGroupPayment') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccountDetail') @patch('payment.logic.payment_group_payment.PaymentGroupPaymentAccount') def test_irrevocable_revert_payment_group_payment( mock_payment_account, mock_payment_detail, mock_report, ): """Test irrevocable_revert_payment_group_payment hard deletes related data.""" payment_group_payment_id = 123 logic.irrevocable_revert_payment_group_payment(payment_group_payment_id) mock_payment_detail.hard_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment_id, commit=False ) mock_payment_account.hard_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment_id, commit=False ) mock_report.hard_delete_by_payment_group_payment.assert_called_once_with( payment_group_payment_id, commit=False ) @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_get_payment_group_payment_success(mock_payment_group_payment): payment_group_payment_id = 123 mock_obj = MagicMock() mock_obj.deleted_at = None mock_payment_group_payment.get_by_id.return_value = mock_obj result = logic.get_payment_group_payment(payment_group_payment_id) assert result == mock_obj mock_payment_group_payment.get_by_id.assert_called_once_with( payment_group_payment_id ) @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_get_payment_group_payment_not_found(mock_payment_group_payment): payment_group_payment_id = 123 mock_payment_group_payment.get_by_id.return_value = None with pytest.raises(LogicError, match=ERROR_PAYMENT_NOT_FOUND): logic.get_payment_group_payment(payment_group_payment_id) @patch('payment.logic.payment_group_payment.PaymentGroupPayment') def test_get_payment_group_payment_deleted(mock_payment_group_payment): payment_group_payment_id = 123 mock_obj = MagicMock() mock_obj.deleted_at = '2024-01-01' mock_payment_group_payment.get_by_id.return_value = mock_obj with pytest.raises(LogicError, match=ERROR_PAYMENT_NOT_FOUND): logic.get_payment_group_payment(payment_group_payment_id)