"""Tests for account_payment_term logic.""" from unittest.mock import MagicMock, patch from abacus_common_logic.connectors.database import db import pytest from sqlalchemy import event from abacus_account.constants import constants, error from abacus_account.logic import account_payment_term as logic from abacus_account.models import AccountPayee from abacus_account.schemas.account_payment_term import AccountPaymentTermDetailSchema from tests.utils.factories import AccountFactory from tests.utils.factories import AccountPayeeFactory from tests.utils.factories import AccountPaymentTermFactory from tests.utils.factories import ReferencePaymentTypeFactory from tests.utils.factories import ReferencePayoneerProgramFactory @patch('abacus_account.logic.account_payment_term.Account') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_create_account_payment_term_success( mock_account_payment_term_model, mock_account_model, reference_payment_entity_fixture ): """Test create_account_payment_term function.""" account = AccountFactory.create() mock_response = { 'account_id': account.account_id, 'account_payment_term_id': 1, 'currency_code': 'USD', 'payment_entity_id': 4, 'payment_minimum': '67.00', 'payment_schedule': constants.PAYMENT_SCHEDULE.SCHEDULE_30_DAYS_MONTH } mock_account_model.get_by_id_or_error.return_value = account mock_account_payment_term_model.create.return_value = mock_response res = logic.create_account_payment_term( account_id=account.account_id, currency_code='USD', payment_minimum='67.00', payment_schedule=constants.PAYMENT_SCHEDULE.SCHEDULE_30_DAYS_MONTH, payment_entity_id=4 ) assert res.status == 201 assert res.message == mock_response mock_account_payment_term_model.create.assert_called_once_with( account_id=account.account_id, currency_code='USD', payment_minimum='67.00', payment_schedule=constants.PAYMENT_SCHEDULE.SCHEDULE_30_DAYS_MONTH, payment_entity_id=4 ) @patch('abacus_account.logic.account_payment_term.Account') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_create_payment_term_with_invalid_currency_code( mock_account_payment_term_model, mock_account_model, reference_payment_entity_fixture ): """Test create_account_payment_term function for an invalid currency code.""" account = AccountFactory.create() mock_account_model.get_by_id_or_error.return_value = account res = logic.create_account_payment_term( account_id=account.account_id, currency_code='Test', payment_entity_id=4, payment_minimum='67.00', payment_schedule=constants.PAYMENT_SCHEDULE.SCHEDULE_30_DAYS_MONTH ) assert res.status == 400 assert res.errors['message'] == error.ERROR_UNKNOWN_CURRENCY.format(code='Test') mock_account_payment_term_model.create.assert_not_called() @patch('abacus_account.logic.account_payment_term.Account') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_create_payment_term_error_already_exists( mock_account_payment_term_model, mock_account_model, reference_payment_entity_fixture ): """Test create_account_payment_term function for existing payment-term.""" account_payment_term = AccountPaymentTermFactory.create() mock_account_model.get_by_id_or_error.return_value = account_payment_term.account res = logic.create_account_payment_term( account_id=account_payment_term.account_id, currency_code='USD', payment_entity_id=5, payment_minimum='67.00', payment_schedule=constants.PAYMENT_SCHEDULE.SCHEDULE_30_DAYS_MONTH ) assert res.status == 400 assert res.errors['message'] == \ error.ERROR_PAYMENT_TERM_ALREADY_EXISTS.format( object_type='account_payment_term', object_id=account_payment_term.account_id ) mock_account_payment_term_model.create.assert_not_called() @patch('abacus_account.logic.account_payment_term.validate_payment_type') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_update_account_payment_term( mock_model, mock_validate, reference_payment_entity_fixture ): """Test update_account_payment_term function.""" account_payment_term = AccountPaymentTermFactory.create() mock_validate.return_value = None put_body = {'currency_code': 'CAD', 'payment_minimum': '11.90'} res = logic.update_account_payment_term(account_payment_term, **put_body) assert res.status == 200 mock_model.commit_changes.assert_called_once() mock_validate.assert_called_once() @patch('abacus_account.logic.account_payment_term.validate_payment_type') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_update_account_payment_term_payment_entity( mock_model, mock_validate, reference_payment_entity_fixture ): """Test update_account_payment_term function to update payment_enitity_id.""" account_payment_term = AccountPaymentTermFactory.create() mock_validate.return_value = None put_body = {'payment_enitity_id': 6} res = logic.update_account_payment_term(account_payment_term, **put_body) assert res.status == 200 mock_model.commit_changes.assert_called_once() mock_validate.assert_called_once() @pytest.mark.parametrize( 'payment_entity_id,reference_payment_type_id', ((2, 7), (4, 9), (5, 8)) ) @patch('abacus_account.logic.account_payment_term.validate_payment_type') @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_update_account_payee_reference_payment_type( mock_model, mock_validate, payment_entity_id, reference_payment_type_id, reference_payment_entity_fixture ): """Test account_payee reference_payment_type_id by changing payment_enitity_id.""" ReferencePaymentTypeFactory.create( reference_payment_type_id=8 ) ReferencePaymentTypeFactory.create( reference_payment_type_id=9 ) account_payment_term = AccountPaymentTermFactory.create() account_payee = AccountPayeeFactory.create( account=account_payment_term.account, reference_payment_type_id=7 ) mock_validate.return_value = None put_body = {'payment_entity_id': payment_entity_id} result = logic.update_account_payment_term(account_payment_term, **put_body) assert result.status == 200 mock_model.commit_changes.assert_called_once() mock_validate.assert_called_once() account_payee = AccountPayee.get_by_id(account_payee.account_payee_id) assert account_payee.reference_payment_type_id == reference_payment_type_id @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_update_account_payment_term_error_validation_payment_type( mock_model, reference_payment_entity_fixture ): """Test update_account_payment_term function.""" account_payment_term = AccountPaymentTermFactory.create( payment_entity_id=4 ) put_body = { 'currency_code': 'CAD', 'payment_minimum': '11.90', 'payment_entity_id': 4, 'agreement_type_id': 3 } res = logic.update_account_payment_term(account_payment_term, **put_body) assert res.status == 400 assert res.errors['message'] == error.ERROR_KNR_ACCOUNT_CANT_MODIFY_AGREEMENT_TYPE\ .format(payment_term_id=account_payment_term.account_payment_term_id) mock_model.commit_changes.assert_not_called() def test_get_payment_term_by_account_id(reference_payment_entity_fixture): """Test get_payment_term_by_account_id function.""" account_payment_term = AccountPaymentTermFactory.create() account_id = account_payment_term.account_id expected_response = AccountPaymentTermDetailSchema().dump(account_payment_term) res = logic.get_payment_term_by_account_id(account_id) assert res.status == 200 assert res.message == expected_response def test_get_payment_term_by_account_id_dataloaded(reference_payment_entity_fixture): """Test get_payment_term_by_account_id_dataloaded function.""" account_payment_term = AccountPaymentTermFactory.create() account_ids = [account_payment_term.account_id, 12345] expected_response = [ { 'data': AccountPaymentTermDetailSchema().dump(account_payment_term) }, { 'data': None } ] res = logic.get_payment_term_by_account_id_dataloaded(account_ids) assert res.status == 200 assert res.message == expected_response def test_get_payment_term_by_account_id_dataloaded_no_n_plus_1( reference_payment_entity_fixture ): """Test that joinedload prevents N+1 queries.""" terms = [AccountPaymentTermFactory.create() for _ in range(5)] account_ids = [t.account_id for t in terms] query_count = 0 def count_queries(conn, cursor, statement, parameters, context, executemany): nonlocal query_count query_count += 1 event.listen(db.engine, 'before_cursor_execute', count_queries) try: res = logic.get_payment_term_by_account_id_dataloaded(account_ids) finally: event.remove(db.engine, 'before_cursor_execute', count_queries) assert res.status == 200 assert query_count == 1 @patch('abacus_account.logic.account_payment_term.AccountPaymentTerm') def test_account_payment_terms_export(mock_model, reference_payment_entity_fixture): """Test account_payment_terms_export method when FF is enabled..""" acct_1 = AccountFactory.create(account_id=1) acct_2 = AccountFactory.create(account_id=2) payment_terms = [ AccountPaymentTermFactory.create(account=acct_1), AccountPaymentTermFactory.create(account=acct_2, payment_entity_id=6) ] mock_model.stream_all.return_value = iter(payment_terms) result = '' for chunk in logic.account_payment_terms_export(): result += chunk mock_model.stream_all.assert_called_once_with(None) assert result == 'account_payment_term_id\taccount_id\tcurrency_code\t' \ 'payment_minimum\tpayment_entity_id\tpayment_schedule\t' \ 'agreement_type_id\n' \ '1\t1\tUSD\t35.00\t5\t30_days_after_month_end\t1\n' \ '2\t2\tUSD\t35.00\t6\t30_days_after_month_end\t1\n' @pytest.mark.parametrize( 'payment_entity_id,payment_entity_name,reference_payment_type_id', ( (2, 'awal123', 7), (5, '1orchard2', 8), (4, '11knr', 9), (4, 'k1nr', None), (4, None, None), (6, '', None), (None, 'knr', None), ), ) @patch( 'abacus_account.logic.account_payment_term.is_feature_enabled', return_value=False ) @patch('abacus_account.logic.account_payment_term.ReferencePaymentEntity') def test_get_mapped_reference_payment_type_id_by_payment_entity_id( mock_model, feature, payment_entity_id, payment_entity_name, reference_payment_type_id, reference_payment_entity_fixture ): """Test get_mapped_reference_payment_type_id_by_payment_entity_id.""" mock_model.get_by_id.return_value = MagicMock( payment_entity_id=payment_entity_id, payment_entity_name=payment_entity_name ) if payment_entity_name else None result = logic.get_mapped_reference_payment_type_id_by_payment_entity_id( payment_entity_id ) assert result == reference_payment_type_id @pytest.mark.parametrize( 'payment_entity_id,payment_entity_name,reference_payment_type_id', ( (2, 'awal123', 8), (5, '1orchard2', 8), (4, '11knr', 9), (4, 'k1nr', None), (4, None, None), (6, '', None), (None, 'knr', None), ), ) @patch( 'abacus_account.logic.account_payment_term.is_feature_enabled', return_value=True ) @patch('abacus_account.logic.account_payment_term.ReferencePaymentEntity') def test_get_mapped_reference_payment_type_id_by_payment_entity_id_whitelabel( mock_model, feature, payment_entity_id, payment_entity_name, reference_payment_type_id, reference_payment_entity_fixture ): """Test get_mapped_reference_payment_type_id_by_payment_entity_id.""" mock_model.get_by_id.return_value = MagicMock( payment_entity_id=payment_entity_id, payment_entity_name=payment_entity_name ) if payment_entity_name else None result = logic.get_mapped_reference_payment_type_id_by_payment_entity_id( payment_entity_id ) assert result == reference_payment_type_id @pytest.mark.parametrize( 'initial_reference_payment_type_id,expected_reference_payment_type_id', ((None, 7), (9, 8), (9, 9)), ) @patch( 'abacus_account.logic.account_payment_term' '.get_mapped_reference_payment_type_id_by_payment_entity_id' ) def test__update_account_payee_reference_payment_type( mock_mapping_func, initial_reference_payment_type_id, expected_reference_payment_type_id, reference_payment_entity_fixture ): """Test _update_account_payee_reference_payment_type.""" ReferencePaymentTypeFactory.create( reference_payment_type_id=8 ) ReferencePaymentTypeFactory.create( reference_payment_type_id=9 ) mock_mapping_func.return_value = expected_reference_payment_type_id payment_entity_id = 5 account_payment_term = AccountPaymentTermFactory.create() account_payee = AccountPayeeFactory.create( account=account_payment_term.account, reference_payment_type_id=initial_reference_payment_type_id, ) logic._update_account_payee_reference_payment_type( payment_entity_id, account_payee.account.account_id ) mock_mapping_func.assert_called_once_with(payment_entity_id) account_payee = AccountPayee.get_by_account_id(account_payee.account.account_id) assert account_payee.reference_payment_type_id == expected_reference_payment_type_id @patch( 'abacus_account.logic.account_payment_term' '.get_mapped_reference_payment_type_id_by_payment_entity_id' ) def test__update_account_payee_reference_payment_type_no_account_payee( mock_mapping_func, reference_payment_entity_fixture ): """Test when account_payee doesn't exist.""" mock_mapping_func.return_value = 8 payment_entity_id = 5 account = AccountFactory.create() # Should not raise an error and return early logic._update_account_payee_reference_payment_type( payment_entity_id, account.account_id ) mock_mapping_func.assert_called_once_with(payment_entity_id) # Verify no account_payee was created account_payee = AccountPayee.get_by_account_id(account.account_id) assert account_payee is None @patch( 'abacus_account.logic.account_payment_term' '.get_mapped_reference_payment_type_id_by_payment_entity_id' ) def test__update_account_payee_reference_payment_type_same_reference_type( mock_mapping_func, reference_payment_entity_fixture ): """Test when reference_payment_type_id is already set.""" ReferencePaymentTypeFactory.create(reference_payment_type_id=8) mock_mapping_func.return_value = 8 payment_entity_id = 5 account_payment_term = AccountPaymentTermFactory.create() account_payee = AccountPayeeFactory.create( account=account_payment_term.account, reference_payment_type_id=8, ) logic._update_account_payee_reference_payment_type( payment_entity_id, account_payee.account.account_id ) mock_mapping_func.assert_called_once_with(payment_entity_id) # Verify reference_payment_type_id remains unchanged account_payee = AccountPayee.get_by_account_id(account_payee.account.account_id) assert account_payee.reference_payment_type_id == 8 @patch( 'abacus_account.logic.account_payment_term' '.get_mapped_reference_payment_type_id_by_payment_entity_id' ) def test__update_account_payee_reference_payment_type_wire_program( mock_mapping_func, reference_payment_entity_fixture ): """Test allows update for wire programs.""" ReferencePaymentTypeFactory.create(reference_payment_type_id=8) ReferencePaymentTypeFactory.create(reference_payment_type_id=12) mock_mapping_func.return_value = 8 payment_entity_id = 5 wire_program = ReferencePayoneerProgramFactory.create( payoneer_program_id=100253120 ) account_payment_term = AccountPaymentTermFactory.create() account_payee = AccountPayeeFactory.create( account=account_payment_term.account, reference_payment_type_id=12, reference_payoneer_program=wire_program, ) logic._update_account_payee_reference_payment_type( payment_entity_id, account_payee.account.account_id ) mock_mapping_func.assert_called_once_with(payment_entity_id) account_payee = AccountPayee.get_by_account_id(account_payee.account.account_id) assert account_payee.reference_payment_type_id == 8