"""Check Payments Test Suite.""" from typing import Any from unittest.mock import call, Mock, patch import pytest from src.models import AbacusState, AccountPaymentHold from src.processors.check_payments import check_processor from tests.unit.factories import ( AbacusStateFactory, AccountFactory, AccountPayeeFactory, AccountPaymentHoldFactory, AccountPaymentTermFactory, AccountTaxInfoFactory, EventFactory, GetAccountsResponseFactory, PaymentGroupFactory, ) mock_event = EventFactory.build() mock_group = PaymentGroupFactory.build( group_criteria={'reference_payment_type_id': 11, 'reference_agreement_types': [1]} ) mock_account = AccountFactory.build(account_id=1) mock_account_payment_term = AccountPaymentTermFactory.build( account_id=1, payment_schedule='30_days_after_month_end' ) mock_account_payee = AccountPayeeFactory.build(account_id=1) mock_account_tax_info = AccountTaxInfoFactory.build(account_id=1) class TestCheckProcessor: """Test CheckProcessor.""" @pytest.mark.parametrize('agreement_type_ids', [None, [], [1, 2, 3]]) @patch.object(check_processor, 'get_accounts') def test_set_total_count( self, mock_get_accounts: Mock, agreement_type_ids: list[int] | None ) -> None: """test set total count.""" mock_accounts_response = GetAccountsResponseFactory.build() mock_get_accounts.return_value = mock_accounts_response group_criteria: dict[str, Any] = {'reference_payment_type_id': 11} if agreement_type_ids is not None: group_criteria['reference_agreement_types'] = agreement_type_ids mock_group = PaymentGroupFactory.build(group_criteria=group_criteria) test_processor = check_processor.CheckProcessor(mock_group, mock_event) test_processor._set_total_count() mock_get_accounts.assert_called_once_with( limit=0, offset=0, reference_payment_type_id=11, agreement_type_ids=agreement_type_ids, ) assert test_processor._total_count == mock_accounts_response.total_count def test_is_account_eligible_true_with_bulk_query(self) -> None: """Test returns true when account is eligible with bulk query dictionaries.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = {} tax_eligibility_states_by_payee_id: dict[int, AbacusState] = { mock_account_payee.account_payee_id: AbacusStateFactory.build( action_status='complete' ) } test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is True def test_is_account_eligible_false_wrong_schedule_with_bulk_query(self) -> None: """Test returns false when accounts schedule does not match payment group with bulk query.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = {} tax_eligibility_states_by_payee_id: dict[int, AbacusState] = { mock_account_payee.account_payee_id: AbacusStateFactory.build( action_status='complete' ) } mock_alt_group = PaymentGroupFactory.build( group_criteria={ 'reference_payment_type_id': 11, 'payment_schedules': ['60_days_after_month_end'], } ) test_processor = check_processor.CheckProcessor(mock_alt_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is False def test_is_account_eligible_false_on_hold_with_bulk_query(self) -> None: """Test returns false when account is on hold with bulk query dictionaries.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = { mock_account.account_id: AccountPaymentHoldFactory.build(is_on_hold=True) } tax_eligibility_states_by_payee_id: dict[int, AbacusState] = { mock_account_payee.account_payee_id: AbacusStateFactory.build( action_status='complete' ) } test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is False def test_is_account_eligible_false_tax_ineligible_with_bulk_query(self) -> None: """Test returns false when account is tax ineligible with bulk query dictionaries.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = {} tax_eligibility_states_by_payee_id: dict[int, AbacusState] = { mock_account_payee.account_payee_id: AbacusStateFactory.build( action_status='init' ) } test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is False @pytest.mark.parametrize('agreement_type_ids', [None, [], [1, 2, 3]]) @patch.object(check_processor, 'bulk_query_abacus_states') @patch.object(check_processor, 'bulk_get_payment_holds') @patch.object(check_processor.CheckProcessor, '_is_account_eligible') @patch.object(check_processor, 'account_payee_dataloader') @patch.object(check_processor, 'fetch_all_account_tax_info_entries') @patch.object(check_processor, 'account_payment_term_dataloader') @patch.object(check_processor, 'get_accounts') def test_get_eligible_account_batch_data( self, mock_get_accounts: Mock, mock_account_payment_term_dataloader: Mock, mock_fetch_all_account_tax_info_entries: Mock, mock_account_payee_dataloader: Mock, mock__is_account_eligible: Mock, mock_bulk_get_payment_holds: Mock, mock_bulk_query_abacus_states: Mock, agreement_type_ids: list[int] | None, ) -> None: """Test get list of eligible accounts using bulk queries.""" mock_alt_account = AccountFactory.build(account_id=2) mock_alt_account_payment_term = AccountPaymentTermFactory.build(account_id=2) mock_alt_account_tax_info = AccountTaxInfoFactory.build(account_id=2) mock_alt_account_payee = AccountPayeeFactory.build(account_id=2) mock_get_accounts.return_value = GetAccountsResponseFactory.build( items=[mock_account, mock_alt_account], total_count=2 ) mock_account_payment_term_dataloader.return_value = { mock_account.account_id: mock_account_payment_term, mock_alt_account.account_id: mock_alt_account_payment_term, } mock_fetch_all_account_tax_info_entries.return_value = [ mock_account_tax_info, mock_alt_account_tax_info, ] mock_account_payee_dataloader.return_value = { mock_account.account_id: mock_account_payee, mock_alt_account.account_id: mock_alt_account_payee, } mock_bulk_get_payment_holds.return_value = Mock(items=[]) mock_bulk_query_abacus_states.return_value = Mock(items=[]) mock__is_account_eligible.side_effect = [False, True] group_criteria: dict[str, Any] = {'reference_payment_type_id': 11} if agreement_type_ids is not None: group_criteria['reference_agreement_types'] = agreement_type_ids mock_group = PaymentGroupFactory.build(group_criteria=group_criteria) test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._get_eligible_account_batch_data() assert len(response) == 1 assert response[0].account_id == mock_alt_account.account_id assert response[0].currency_code == mock_alt_account_payment_term.currency_code assert ( response[0].country_of_tax_residence == mock_alt_account_tax_info.country_of_tax_residence ) mock_get_accounts.assert_called_once_with( limit=100, offset=0, reference_payment_type_id=11, agreement_type_ids=agreement_type_ids, ) account_ids = [mock_account.account_id, mock_alt_account.account_id] mock_account_payment_term_dataloader.assert_called_once_with(account_ids) mock_fetch_all_account_tax_info_entries.assert_called_once_with(account_ids) mock_bulk_get_payment_holds.assert_called_once_with(account_ids) mock_bulk_query_abacus_states.assert_called_once() assert mock__is_account_eligible.call_count == 2 def test_is_account_eligible_true_payment_hold_not_active_with_bulk_query( self, ) -> None: """Test returns true when payment hold exists but is_on_hold is False with bulk query.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = { mock_account.account_id: AccountPaymentHoldFactory.build(is_on_hold=False) } tax_eligibility_states_by_payee_id: dict[int, AbacusState] = { mock_account_payee.account_payee_id: AbacusStateFactory.build( action_status='complete' ) } test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is True def test_is_account_eligible_false_missing_tax_state_with_bulk_query( self, ) -> None: """Test returns false when tax eligibility state is missing from dictionary with bulk query.""" payment_holds_by_account_id: dict[int, AccountPaymentHold] = {} tax_eligibility_states_by_payee_id: dict[int, AbacusState] = {} test_processor = check_processor.CheckProcessor(mock_group, mock_event) response = test_processor._is_account_eligible( mock_account, mock_account_payment_term, mock_account_payee, payment_holds_by_account_id, tax_eligibility_states_by_payee_id, ) assert response is False @patch.object(check_processor, 'PaymentBatchProcessor') @patch.object(check_processor.CheckProcessor, '_get_eligible_account_batch_data') def test_process_batch_disables_vat_corrections( self, mock_get_eligible_account_batch_data: Mock, mock_payment_batch_processor: Mock, ) -> None: """Test _process_batch passes append_vat_corrections=False to PaymentBatchProcessor.""" from src.processors.models import EligibleAccountLevelData mock_accounts = [ EligibleAccountLevelData( account_id=1, country_of_tax_residence='USA', currency_code='USD', country_of_tax_policy='USA', ) ] mock_get_eligible_account_batch_data.return_value = mock_accounts mock_processor_instance = Mock() mock_payment_batch_processor.return_value = mock_processor_instance test_processor = check_processor.CheckProcessor(mock_group, mock_event) test_processor._process_batch(offset=0) # Verify PaymentBatchProcessor is called with append_vat_corrections=False mock_payment_batch_processor.assert_called_once_with( test_processor._abacus_event, mock_accounts, append_vat_corrections=False, ) mock_processor_instance.process.assert_called_once()