"""Test Payment Hold logic.""" from datetime import date, datetime from unittest.mock import MagicMock, patch from zoneinfo import ZoneInfo from abacus_common_logic.utils.dates import parse_date from abacus_account.constants.error import ( ERROR_INVALID_HOLD_STATUS_REASON, ERROR_INVALID_START_DATE) from abacus_account.logic import payment_hold as logic from abacus_account.schemas import PaymentHoldSchema from tests.utils.factories import AccountFactory, PaymentHoldFactory @patch('abacus_account.logic.payment_hold.models') def test_create_or_update_payment_hold_create_success(mock_models): """When account has no payment hold, test that one is created.""" account = AccountFactory.create() mock_models.Account.get_by_id_or_error.return_value = account params = { 'is_on_hold': True, 'reason': 'Playing hard to get', 'start_date': date.today() } mock_models.PaymentHold.create.return_value = PaymentHoldFactory.create(**params) res = logic.create_or_update_payment_hold(**params) assert res.status == 201 assert res.message['payment_hold_id'] assert res.message['is_on_hold'] == params['is_on_hold'] assert res.message['reason'] == params['reason'] assert parse_date(res.message['start_date']) == params['start_date'] @patch('abacus_account.logic.payment_hold.datetime') @patch('abacus_account.logic.payment_hold.models') def test_create_or_update_payment_hold_invalid_start_date(mock_models, mock_datetime): """Create or update returns error when start date is before today.""" account = AccountFactory.create() start_date = parse_date('2020-01-01') today = datetime(2025, 5, 1, tzinfo=ZoneInfo('UTC')) mock_datetime.now.return_value = today mock_models.Account.get_by_id_or_error.return_value = account params = {'start_date': start_date} res = logic.create_or_update_payment_hold(**params) assert res.status == 400 assert res.errors['message'] == ERROR_INVALID_START_DATE.format( start_date=start_date.strftime('%Y-%m-%d'), today=today.strftime('%Y-%m-%d'), tz=today.strftime('%Z%z'), ) @patch('abacus_account.logic.payment_hold.update_payment_hold') @patch('abacus_account.logic.payment_hold.models') def test_create_or_update_payment_hold_will_update(mock_models, mock_update): """When account already has a payment hold, hold should be updated and logged.""" account = AccountFactory.create() payment_hold = PaymentHoldFactory.create(account=account) mock_models.Account.get_by_id_or_error.return_value = account params = { 'is_on_hold': True, 'reason': 'Playing hard to get', 'start_date': date.today() } logic.create_or_update_payment_hold(**params) mock_update.assert_called_once_with(payment_hold, **params) @patch('abacus_account.logic.payment_hold.models') def test_update_payment_hold_success(mock_models): """Test that payment hold is updated.""" account = AccountFactory.create() payment_hold = PaymentHoldFactory.create(account=account) mock_models.Account.get_by_id_or_error.return_value = account params = { 'is_on_hold': not payment_hold.is_on_hold, 'reason': 'Playing hard to get', 'start_date': date.today() } res = logic.update_payment_hold(payment_hold, **params) assert res.status == 201 assert payment_hold.is_on_hold == params['is_on_hold'] assert payment_hold.reason == params['reason'] assert payment_hold.start_date == params['start_date'] @patch('abacus_account.logic.payment_hold.models') def test_update_payment_hold_change_reason(mock_models): """Updating reasons is allowed without is_on_hold changing.""" account = AccountFactory.create() payment_hold = PaymentHoldFactory.create(account=account) mock_models.Account.get_by_id_or_error.return_value = account params = {'is_on_hold': payment_hold.is_on_hold, 'reason': 'a new reason'} res = logic.update_payment_hold(payment_hold, **params) assert res.status == 201 assert payment_hold.is_on_hold == payment_hold.is_on_hold assert payment_hold.reason == 'a new reason' assert payment_hold.start_date == payment_hold.start_date @patch('abacus_account.logic.payment_hold.models') def test_update_payment_hold_invalid_hold_status(mock_models): """Update returns error is_on_hold and reason are not changed.""" account = AccountFactory.create() payment_hold = PaymentHoldFactory.create(account=account) mock_models.Account.get_by_id_or_error.return_value = account params = {'is_on_hold': payment_hold.is_on_hold, 'reason': payment_hold.reason} res = logic.update_payment_hold(payment_hold, **params) assert res.status == 400 assert res.errors['message'] == ( ERROR_INVALID_HOLD_STATUS_REASON.format( hold_status='on hold', reason=payment_hold.reason, ) ) @patch('abacus_account.logic.payment_hold.models') def test_get_payment_hold(mock_models): """Test getting a account's payment hold.""" account = AccountFactory.create() payment_hold = PaymentHoldFactory.create(account=account) mock_models.Account.get_by_id_or_error.return_value = account res = logic.get_payment_hold(account.account_id) assert res.status == 200 assert res.message == PaymentHoldSchema().dump(payment_hold) @patch('abacus_account.logic.payment_hold.schema') @patch('abacus_account.logic.payment_hold.models') def test_get_payment_holds(mock_models, mock_schema): """Test retrieving payment holds list.""" items = [MagicMock(), MagicMock()] mock_models.PaymentHold.get_filtered_items.return_value = (items, 2) mock_schema.dump.return_value = [{'payment_hold_id': 1}, {'payment_hold_id': 2}] res = logic.get_payment_holds(limit=25, offset=5, account_ids=[1, 2]) mock_models.PaymentHold.get_filtered_items.assert_called_once_with( limit=25, offset=5, account_ids=[1, 2], ) mock_schema.dump.assert_called_once_with(items, many=True) assert res.status == 200 assert res.message == { 'items': mock_schema.dump.return_value, 'total_count': 2, } @patch('abacus_account.logic.payment_hold.schema') @patch('abacus_account.logic.payment_hold.models') def test_get_payment_holds_without_filters(mock_models, mock_schema): """Test retrieving payment holds list without account filters.""" items = [MagicMock()] mock_models.PaymentHold.get_filtered_items.return_value = (items, 1) mock_schema.dump.return_value = [{'payment_hold_id': 1}] res = logic.get_payment_holds(limit=10, offset=0) mock_models.PaymentHold.get_filtered_items.assert_called_once_with( limit=10, offset=0, account_ids=None, ) mock_schema.dump.assert_called_once_with(items, many=True) assert res.status == 200 assert res.message == { 'items': mock_schema.dump.return_value, 'total_count': 1, }