"""Unit tests for the adjustments validation functions.""" from abacus_common_logic.adjustments_validation.constants import ( ACCOUNT_EXPENSE_ADJUSTMENT_TYPE, VALIDATION_ERRORS, ) from abacus_common_logic.adjustments_validation.validation_functions import ( validate_account, validate_activity_date, validate_adjustment_type, validate_amount, validate_client_facing_comments, validate_close_balance_status, validate_contract, validate_currency, validate_distribution_type, validate_flowthrough_payment, validate_statement_date, validate_upc, ) def test_validate_close_balance_status(): """Test validating that the payment entity balance is not closed.""" account_id = '1' account_payment_entity_map = {'1': '1', '2': '2'} payment_entity_close_balance_status_map = {'1': 'init', '2': 'complete'} assert validate_close_balance_status( account_id, account_payment_entity_map, payment_entity_close_balance_status_map ) assert ( validate_close_balance_status(None, {}, {}) == VALIDATION_ERRORS.ACCOUNT_REQUIRED ) assert ( validate_close_balance_status(account_id, {}, {}) == VALIDATION_ERRORS.ACCOUNT_NO_PAYMENT_ENTITY ) assert ( validate_close_balance_status(account_id, account_payment_entity_map, {}) == VALIDATION_ERRORS.ACCOUNT_NO_CLOSE_BALANCE ) assert ( validate_close_balance_status( '2', account_payment_entity_map, payment_entity_close_balance_status_map ) == VALIDATION_ERRORS.ACCOUNT_INVALID_CLOSE_BALANCE ) def test_validate_account(): """Test validating the `Account ID` field of an adjustment.""" account_id = '1' account_ids = {'1', '2'} assert validate_account(account_id, account_ids) assert validate_account(None, set()) == VALIDATION_ERRORS.ACCOUNT_REQUIRED assert validate_account('1.2', set()) == VALIDATION_ERRORS.ACCOUNT_INVALID assert validate_account('3', account_ids) == VALIDATION_ERRORS.ACCOUNT_MISSING def test_validate_contract(): """Test validating the `Contract ID` field of an adjustment.""" account_id = '1' contract_id = '11' account_contract_map = {'1': {'11'}} assert validate_contract(account_id, contract_id, account_contract_map) assert validate_contract(None, None, {}) == VALIDATION_ERRORS.ACCOUNT_REQUIRED assert ( validate_contract(account_id, None, {}) == VALIDATION_ERRORS.CONTRACT_REQUIRED ) assert ( validate_contract(account_id, '1.2', {}) == VALIDATION_ERRORS.CONTRACT_INVALID ) assert ( validate_contract('2', contract_id, account_contract_map) == VALIDATION_ERRORS.CONTRACT_MISSING ) assert ( validate_contract(account_id, '22', account_contract_map) == VALIDATION_ERRORS.CONTRACT_MISSING ) def test_validate_upc(): """Test validating the `UPC` field of an adjustment.""" upc = '111122223333' distribution_type = 'physical' contract_id = '1' contract_product_map = {'1': {'111122223333'}} contract_label_map = {'1': {'11'}} display_upc_upc_map = {'101020203030': {'111122223333'}} account_upc_map = {'11': {'111122223333'}} # The UPC field is optional assert validate_upc(None, None, contract_id, {}, {}, {}, {}) # The UPC is attached to a product term assert validate_upc( upc, distribution_type, contract_id, contract_product_map, {}, {}, {}, ) # The UPC is attached to a product term after cleaning up the product term's UPCs assert validate_upc( upc, distribution_type, contract_id, {'1': {'0111122223333'}}, {}, {}, {}, ) # The UPC is a "Display UPC" for a UPC attached to a product term assert validate_upc( '101020203030', distribution_type, contract_id, contract_product_map, {}, display_upc_upc_map, {}, ) # The UPC is attached to a label term assert validate_upc( upc, distribution_type, contract_id, {}, contract_label_map, {}, account_upc_map, ) assert ( validate_upc(upc, distribution_type, None, {}, {}, {}, {}) == VALIDATION_ERRORS.CONTRACT_REQUIRED ) assert ( validate_upc('ABC', distribution_type, contract_id, {}, {}, {}, {}) == VALIDATION_ERRORS.UPC_INVALID ) assert ( validate_upc('123', distribution_type, contract_id, {}, {}, {}, {}) == VALIDATION_ERRORS.UPC_LENGTH ) assert ( validate_upc(upc, None, contract_id, {}, {}, {}, {}) == VALIDATION_ERRORS.UPC_BLANK ) assert ( validate_upc(upc, distribution_type, contract_id, {}, {}, {}, {}) == VALIDATION_ERRORS.UPC_MISSING ) assert ( validate_upc( '444455556666', distribution_type, contract_id, contract_product_map, contract_label_map, display_upc_upc_map, account_upc_map, ) == VALIDATION_ERRORS.UPC_MISSING ) def test_validate_amount(): """Test validating the `Amount` field of an adjustment.""" amount = '1.23' assert validate_amount(amount) assert validate_amount(None) == VALIDATION_ERRORS.AMOUNT_REQUIRED assert validate_amount('ten') == VALIDATION_ERRORS.AMOUNT_INVALID assert validate_amount('0') == VALIDATION_ERRORS.AMOUNT_ZERO def test_validate_currency(): """Test validating the `Currency` field of an adjustment.""" currency = 'USD' assert validate_currency(currency) assert validate_currency('usd') assert validate_currency(None) == VALIDATION_ERRORS.CURRENCY_REQUIRED assert validate_currency('123') == VALIDATION_ERRORS.CURRENCY_INVALID assert validate_currency('X') == VALIDATION_ERRORS.CURRENCY_UNSUPPORTED def test_validate_activity_date(): """Test validating the `Activity Month/Year` fields of an adjustment.""" activity_month = '1' activity_year = '2025' statement_periods = {'1/2025': 'current'} assert validate_activity_date(activity_month, activity_year, statement_periods) assert ( validate_activity_date(None, None, {}) == VALIDATION_ERRORS.ACTIVITY_MONTH_REQUIRED ) assert ( validate_activity_date('Jan', None, {}) == VALIDATION_ERRORS.ACTIVITY_MONTH_INVALID ) assert ( validate_activity_date('13', None, {}) == VALIDATION_ERRORS.ACTIVITY_MONTH_LENGTH ) assert ( validate_activity_date(activity_month, None, {}) == VALIDATION_ERRORS.ACTIVITY_YEAR_REQUIRED ) assert ( validate_activity_date(activity_month, 'year', {}) == VALIDATION_ERRORS.ACTIVITY_YEAR_INVALID ) assert ( validate_activity_date(activity_month, '20250', {}) == VALIDATION_ERRORS.ACTIVITY_YEAR_LENGTH ) assert ( validate_activity_date('2', activity_year, statement_periods) == VALIDATION_ERRORS.ACTIVITY_PERIOD_MISSING ) assert ( validate_activity_date(activity_month, '2024', statement_periods) == VALIDATION_ERRORS.ACTIVITY_PERIOD_MISSING ) def test_validate_statement_date(): """Test validating the `Statement Month/Year` fields of an adjustment.""" statement_month = '1' statement_year = '2025' statement_periods = { '1/2025': 'current', '1/2024': 'closed', } assert validate_statement_date(statement_month, statement_year, statement_periods) assert ( validate_statement_date(None, None, {}) == VALIDATION_ERRORS.STATEMENT_MONTH_REQUIRED ) assert ( validate_statement_date('Jan', None, {}) == VALIDATION_ERRORS.STATEMENT_MONTH_INVALID ) assert ( validate_statement_date('13', None, {}) == VALIDATION_ERRORS.STATEMENT_MONTH_LENGTH ) assert ( validate_statement_date(statement_month, None, {}) == VALIDATION_ERRORS.STATEMENT_YEAR_REQUIRED ) assert ( validate_statement_date(statement_month, 'year', {}) == VALIDATION_ERRORS.STATEMENT_YEAR_INVALID ) assert ( validate_statement_date(statement_month, '20250', {}) == VALIDATION_ERRORS.STATEMENT_YEAR_LENGTH ) assert ( validate_statement_date('2', statement_year, statement_periods) == VALIDATION_ERRORS.STATEMENT_PERIOD_MISSING ) assert ( validate_statement_date(statement_month, '2026', statement_periods) == VALIDATION_ERRORS.STATEMENT_PERIOD_MISSING ) assert ( validate_statement_date(statement_month, '2024', statement_periods) == VALIDATION_ERRORS.STATEMENT_PERIOD_MISSING ) def test_validate_adjustment_type(): """Test validating the `Adjustment Type` field of an adjustment.""" adjustment_type = 'Settlement' adjustment_types = {'adjustment', 'settlement'} assert validate_adjustment_type(adjustment_type, adjustment_types) assert ( validate_adjustment_type(None, adjustment_types) == VALIDATION_ERRORS.ADJUSTMENT_TYPE_REQUIRED ) assert ( validate_adjustment_type('type', adjustment_types) == VALIDATION_ERRORS.ADJUSTMENT_TYPE_UNSUPPORTED ) assert ( validate_adjustment_type( ACCOUNT_EXPENSE_ADJUSTMENT_TYPE, {ACCOUNT_EXPENSE_ADJUSTMENT_TYPE} ) == VALIDATION_ERRORS.ADJUSTMENT_TYPE_EXPENSE ) def test_validate_client_facing_comments(): """Test validating the `Client Facing Comments` field of an adjustment.""" comments = 'Comments' assert validate_client_facing_comments(comments) assert validate_client_facing_comments(None) == VALIDATION_ERRORS.COMMENTS_REQUIRED assert ( validate_client_facing_comments('|'.join([str(i) for i in range(0, 200)])) == VALIDATION_ERRORS.COMMENTS_LENGTH ) def test_validate_distribution_type(): """Test validating the `Distribution Type` field of an adjustment.""" distribution_type = 'physical' upc = '111122223333' assert validate_distribution_type(distribution_type, upc) assert validate_distribution_type('Digital', upc) assert ( validate_distribution_type(distribution_type, None) == VALIDATION_ERRORS.DISTRIBUTION_TYPE_BLANK ) assert ( validate_distribution_type('type', upc) == VALIDATION_ERRORS.DISTRIBUTION_TYPE_UNSUPPORTED ) def test_validate_flowthrough_payment(): """Test validating the `Apply To Flowthrough Payment` field of an adjustment.""" assert validate_flowthrough_payment(None, 'Settlement') assert validate_flowthrough_payment('Y', 'Flowthrough') assert validate_flowthrough_payment('N', 'Flowthrough') assert ( validate_flowthrough_payment(None, 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_REQUIRED ) assert ( validate_flowthrough_payment('*', 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) class TestValidateFlowthroughPayment: """Exhaustive tests for validate_flowthrough_payment. Covers all combinations of adjustment type (Flowthrough / non-FT) and apply_to_flowthrough_payment value (truthy string, falsy string, boolean True/False, None, empty string, invalid). """ # --- Flowthrough type: truthy values accepted --- def test_ft_type_truthy_string_y_accepted(self): """Accept 'Y' for Flowthrough type.""" assert validate_flowthrough_payment('Y', 'Flowthrough') is True def test_ft_type_truthy_string_yes_accepted(self): """Accept 'Yes' for Flowthrough type.""" assert validate_flowthrough_payment('Yes', 'Flowthrough') is True def test_ft_type_truthy_string_true_accepted(self): """Accept 'True' for Flowthrough type.""" assert validate_flowthrough_payment('True', 'Flowthrough') is True def test_ft_type_truthy_string_t_accepted(self): """Accept 'T' for Flowthrough type.""" assert validate_flowthrough_payment('T', 'Flowthrough') is True def test_ft_type_truthy_string_1_accepted(self): """Accept '1' for Flowthrough type.""" assert validate_flowthrough_payment('1', 'Flowthrough') is True def test_ft_type_bool_true_accepted(self): """Accept boolean True for Flowthrough type.""" assert validate_flowthrough_payment(True, 'Flowthrough') is True # --- Flowthrough type: falsy values accepted (ACC-10310) --- def test_ft_type_falsy_string_n_accepted(self): """Accept 'N' for Flowthrough type.""" assert validate_flowthrough_payment('N', 'Flowthrough') is True def test_ft_type_falsy_string_no_accepted(self): """Accept 'No' for Flowthrough type.""" assert validate_flowthrough_payment('No', 'Flowthrough') is True def test_ft_type_falsy_string_false_accepted(self): """Accept 'False' for Flowthrough type.""" assert validate_flowthrough_payment('False', 'Flowthrough') is True def test_ft_type_falsy_string_f_accepted(self): """Accept 'F' for Flowthrough type.""" assert validate_flowthrough_payment('F', 'Flowthrough') is True def test_ft_type_falsy_string_0_accepted(self): """Accept '0' for Flowthrough type.""" assert validate_flowthrough_payment('0', 'Flowthrough') is True def test_ft_type_bool_false_accepted(self): """ACC-10310: boolean False must be accepted, not treated as missing.""" assert validate_flowthrough_payment(False, 'Flowthrough') is True # --- Flowthrough type: missing/empty rejected --- def test_ft_type_none_rejected(self): """Reject None for Flowthrough type (field is required).""" assert ( validate_flowthrough_payment(None, 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_REQUIRED ) def test_ft_type_empty_string_rejected(self): """Reject empty string for Flowthrough type (field is required).""" assert ( validate_flowthrough_payment('', 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_REQUIRED ) # --- Flowthrough type: invalid values rejected --- def test_ft_type_wildcard_rejected(self): """Reject '*' as an invalid boolean value.""" assert ( validate_flowthrough_payment('*', 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) def test_ft_type_all_rejected(self): """Reject 'all' as an invalid boolean value.""" assert ( validate_flowthrough_payment('all', 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) def test_ft_type_garbage_rejected(self): """Reject unrecognized string as an invalid boolean value.""" assert ( validate_flowthrough_payment('maybe', 'Flowthrough') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) # --- Non-FT type: field is optional, missing is fine --- def test_non_ft_type_none_accepted(self): """Accept None for non-Flowthrough type (field is optional).""" assert validate_flowthrough_payment(None, 'Settlement') is True def test_non_ft_type_empty_string_accepted(self): """Accept empty string for non-Flowthrough type (field is optional).""" assert validate_flowthrough_payment('', 'Settlement') is True def test_non_ft_type_bool_false_accepted(self): """Accept boolean False for non-Flowthrough type.""" assert validate_flowthrough_payment(False, 'Settlement') is True # --- Non-FT type: provided values still validated --- def test_non_ft_type_truthy_string_accepted(self): """Accept truthy string for non-Flowthrough type.""" assert validate_flowthrough_payment('Y', 'Settlement') is True def test_non_ft_type_falsy_string_accepted(self): """Accept falsy string for non-Flowthrough type.""" assert validate_flowthrough_payment('N', 'Settlement') is True def test_non_ft_type_wildcard_rejected(self): """Reject '*' even for non-Flowthrough type.""" assert ( validate_flowthrough_payment('*', 'Settlement') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) def test_non_ft_type_garbage_rejected(self): """Reject unrecognized string even for non-Flowthrough type.""" assert ( validate_flowthrough_payment('maybe', 'Settlement') == VALIDATION_ERRORS.FLOWTHROUGH_PAYMENT_INVALID ) # --- Case insensitivity on adjustment type --- def test_ft_type_case_insensitive(self): """Adjustment type matching is case-insensitive.""" assert validate_flowthrough_payment('Y', 'FLOWTHROUGH') is True assert validate_flowthrough_payment('Y', 'flowthrough') is True assert validate_flowthrough_payment('Y', 'Flowthrough') is True