"""Contract Term Condition unit tests.""" from unittest.mock import patch from abacus_common_logic.constants.error import ERROR_ENTITY_DOES_NOT_EXIST from pytest import approx from abacus_contract.constants.error import ( ERROR_CANNOT_DELETE_CONTRACT_TERM_CONDITION, ERROR_INVALID_RELATIONSHIP, ) from abacus_contract.logic import contract_term_condition as logic from abacus_contract.tests.utils.factories import ( ContractTermConditionFactory, ContractTermFactory, ) from abacus_contract.utils.format_error import validation_error @patch('abacus_contract.logic.contract_term_condition.models') def test_create_contract_term_conditions(mock_models): """Test create contract term conditions logic.""" new_condition = ContractTermConditionFactory.build() mock_models.ContractTerm.get_by_id_or_error.return_value = ( new_condition.contract_term ) mock_models.ContractTermCondition.build.return_value = new_condition params = [ { 'conditions': [], 'priority': 1, 'term_rate': 50, 'contract_term_condition_name': 'test condition name', } ] result = logic.create_contract_term_conditions( new_condition.contract_term_id, params ) assert result.status == 201 mock_models.ContractTermCondition.build.assert_called_once_with( contract_term_id=new_condition.contract_term_id, conditions=params[0]['conditions'], priority=params[0]['priority'], term_rate=params[0]['term_rate'], commission=50, contract_term_condition_name=params[0]['contract_term_condition_name'], ) mock_models.ContractTermCondition.commit_changes.assert_called_once() @patch('abacus_contract.logic.contract_term_condition.models') def test_create_contract_term_conditions_with_commission(mock_models): """Test to create contract term conditions logic with commission field .""" new_condition = ContractTermConditionFactory.build() mock_models.ContractTerm.get_by_id_or_error.return_value = ( new_condition.contract_term ) mock_models.ContractTermCondition.build.return_value = new_condition params = [{'conditions': [], 'priority': 1, 'commission': 67.56}] result = logic.create_contract_term_conditions( new_condition.contract_term_id, params ) assert result.status == 201 mock_models.ContractTermCondition.build.assert_called_once_with( contract_term_id=new_condition.contract_term_id, conditions=params[0]['conditions'], priority=params[0]['priority'], commission=params[0]['commission'], term_rate=32.44, contract_term_condition_name=None, ) mock_models.ContractTermCondition.commit_changes.assert_called_once() @patch('abacus_contract.logic.contract_term_condition.models') def test_get_conditions_by_term_id(mock_models): """Test get contract_term_conditions by contract term id.""" contract_term = ContractTermFactory.create() other_contract_term = ContractTermFactory.create() contract_term_condition = ContractTermConditionFactory.create( contract_term=other_contract_term ) mock_models.ContractTermCondition.get_active_term_conditions_with_order_by.return_value = [ contract_term_condition ] res = logic.get_conditions_by_term_id( contract_term.contract_term_id, ) assert res.status == 200 assert ( res.message[0]['contract_term_id'] == contract_term_condition.contract_term_id ) @patch('abacus_contract.logic.contract_term_condition.models') def test_get_condition_records_by_term_ids(mock_models): """Test serializing conditions for authorized term ids as flat records.""" contract_term = ContractTermFactory.create() condition_1 = ContractTermConditionFactory.create( contract_term=contract_term, priority=1 ) condition_2 = ContractTermConditionFactory.create( contract_term=contract_term, priority=2 ) mock_models.ContractTermCondition.get_active_term_conditions_by_term_ids.return_value = [ condition_1, condition_2, ] res = logic.get_condition_records_by_term_ids([contract_term.contract_term_id]) mock_models.ContractTermCondition.get_active_term_conditions_by_term_ids.assert_called_once_with( [contract_term.contract_term_id] ) # A flat list of records, not the {'data': ...} dataload wrapping. assert len(res) == 2 assert ( res[0]['contract_term_condition_id'] == condition_1.contract_term_condition_id ) assert all( record['contract_term_id'] == contract_term.contract_term_id for record in res ) @patch('abacus_contract.logic.contract_term_condition.models') def test_get_condition_records_by_term_ids_no_conditions(mock_models): """No conditions found for the authorized ids: returns an empty flat list.""" mock_models.ContractTermCondition.get_active_term_conditions_by_term_ids.return_value = [] res = logic.get_condition_records_by_term_ids([101, 202]) assert res == [] @patch('abacus_contract.logic.contract_term_condition.models') def test_get_condition_records_by_term_ids_empty_list_skips_query(mock_models): """No authorized ids: returns an empty list without querying. Regression test for the batch endpoint's auth bypass: an id that never resolved to an account, or whose account wasn't authorized, must never reach this function's query, since the dataloader helper only ever passes the authorized ids through. """ res = logic.get_condition_records_by_term_ids([]) assert res == [] mock_models.ContractTermCondition.get_active_term_conditions_by_term_ids.assert_not_called() @patch('abacus_contract.logic.contract_term_condition._reprioritize') @patch('abacus_contract.logic.contract_term_condition._validate_soft_deletion') @patch('abacus_contract.logic.contract_term_condition.models') def test_soft_delete_contract_term_conditions_success( mock_models, mock_validate_deletion, mock_reprioritize ): """Successfully soft deleting contract term conditions and reprioritizing.""" contract_term = ContractTermFactory.create() contract_term_conditions = [ ContractTermConditionFactory.create( contract_term=contract_term, priority=priority ) for priority in [1, 2, 3] ] contract_term_condition_ids = [ condition.contract_term_condition_id for condition in contract_term_conditions ] mock_models.ContractTermCondition.get_all_by_id.return_value = ( contract_term_conditions[:1] ) mock_validate_deletion.return_value = None mock_reprioritize.return_value = None res = logic.soft_delete_contract_term_conditions(contract_term_condition_ids[:1]) assert res.status == 200 mock_validate_deletion.assert_called_once_with( contract_term_conditions[:1], contract_term_condition_ids[:1], [contract_term.contract_term_id], ) mock_reprioritize.assert_called_once_with([contract_term.contract_term_id]) mock_models.ContractTermCondition.commit_changes.assert_called_once() @patch('abacus_contract.logic.contract_term_condition._reprioritize') @patch('abacus_contract.logic.contract_term_condition._validate_soft_deletion') @patch('abacus_contract.logic.contract_term_condition.models') def test_soft_delete_contract_term_conditions_failure( mock_models, mock_validate_deletion, mock_reprioritize ): """Test soft deletion returns error on validation failure.""" contract_term = ContractTermFactory.create() contract_term_conditions = [ ContractTermConditionFactory.create( contract_term=contract_term, priority=priority ) for priority in [1, 2, 3] ] contract_term_condition_ids = [ condition.contract_term_condition_id for condition in contract_term_conditions ] mock_models.ContractTermCondition.get_all_by_id.return_value = ( contract_term_conditions ) mock_validate_deletion.return_value = validation_error('NOPE') res = logic.soft_delete_contract_term_conditions(contract_term_condition_ids) assert res.status == 400 assert res.errors['code'] == 'error' assert res.errors['message'] == 'NOPE' mock_validate_deletion.assert_called_once_with( contract_term_conditions, contract_term_condition_ids, [contract_term.contract_term_id], ) mock_reprioritize.assert_not_called() mock_models.ContractTermCondition.commit_changes.assert_not_called() @patch('abacus_contract.logic.contract_term_condition.models') def test_update_contract_term_conditions_success(mock_models): """Test update one or more contract term conditions successfully.""" contract_term = ContractTermFactory.create() contract_term_condition = ContractTermConditionFactory.create( contract_term=contract_term ) mock_models.ContractTerm.get_by_id_or_error.return_value = contract_term mock_models.ContractTermCondition.get_by_id_or_error.return_value = ( contract_term_condition ) update_params = [ { 'contract_term_condition_id': contract_term_condition.contract_term_condition_id, 'conditions': contract_term_condition.conditions, 'priority': contract_term_condition.priority, 'term_rate': 20.20, } ] assert not contract_term_condition.term_rate == update_params[0]['term_rate'] res = logic.update_contract_term_conditions( contract_term.contract_term_id, update_params ) assert res.status == 200 assert len(res.message) == len(update_params) mock_models.ContractTermCondition.commit_changes.assert_called_once() assert contract_term_condition.term_rate == update_params[0]['term_rate'] assert contract_term_condition.commission == approx(79.80) @patch('abacus_contract.logic.contract_term_condition.models') def test_update_contract_term_conditions_for_commission_field(mock_models): """Test to update commission field.""" contract_term = ContractTermFactory.create() contract_term_condition = ContractTermConditionFactory.create( contract_term=contract_term ) mock_models.ContractTerm.get_by_id_or_error.return_value = contract_term mock_models.ContractTermCondition.get_by_id_or_error.return_value = ( contract_term_condition ) update_params = [ { 'contract_term_condition_id': contract_term_condition.contract_term_condition_id, 'commission': 11.89, } ] res = logic.update_contract_term_conditions( contract_term.contract_term_id, update_params ) assert res.status == 200 assert len(res.message) == len(update_params) mock_models.ContractTermCondition.commit_changes.assert_called_once() assert contract_term_condition.commission == update_params[0]['commission'] assert contract_term_condition.term_rate == approx(88.11) @patch('abacus_contract.logic.contract_term_condition.models') def test_update_contract_term_conditions_invalid_contract_term(mock_models): """Test updating contract_term_conditions returns error for invalid parent term.""" contract_term = ContractTermFactory.create() other_contract_term = ContractTermFactory.create() contract_term_condition = ContractTermConditionFactory.create( contract_term=other_contract_term ) mock_models.ContractTerm.get_by_id_or_error.return_value = contract_term mock_models.ContractTermCondition.get_by_id_or_error.return_value = ( contract_term_condition ) update_params = [ { 'contract_term_condition_id': contract_term_condition.contract_term_condition_id, 'conditions': contract_term_condition.conditions, 'priority': contract_term_condition.priority, 'term_rate': 20.20, } ] assert not contract_term_condition.contract_term == contract_term res = logic.update_contract_term_conditions( contract_term.contract_term_id, update_params ) assert res.status == 400 assert res.errors['code'] == 'error' assert res.errors['message'] == ERROR_INVALID_RELATIONSHIP.format( child='contract_term_condition', child_id=contract_term_condition.contract_term_condition_id, parent='contract_term', parent_id=contract_term.contract_term_id, ) mock_models.ContractTermCondition.commit_changes.assert_not_called() assert not contract_term_condition.term_rate == update_params[0]['term_rate'] @patch('abacus_contract.logic.contract_term_condition.models') def test_reprioritize(mock_models): """Test active contract_term_conditions are reprioritized.""" remaining_priorities = [3, 4] contract_term = ContractTermFactory.create() contract_term_conditions = [ ContractTermConditionFactory.create( contract_term=contract_term, priority=priority ) for priority in remaining_priorities ] mock_models.ContractTermCondition.get_active_term_conditions_with_order_by.return_value = contract_term_conditions logic._reprioritize([contract_term.contract_term_id]) for condition in contract_term_conditions: assert condition.priority not in remaining_priorities assert contract_term_conditions[0].priority == 1 assert contract_term_conditions[1].priority == 2 @patch('abacus_contract.logic.contract_term_condition.models') def test_validate_soft_deletion_condition_does_not_exist(mock_models): """Validation returns error if contract_term_condition_id is invalid.""" contract_term = ContractTermFactory.create() contract_term_conditions = ContractTermConditionFactory.create_batch( 2, contract_term=contract_term ) contract_term_condition_ids = [ condition.contract_term_condition_id for condition in contract_term_conditions ] contract_term_conditions.append(99) mock_models.ContractTermCondition.get_class_name.return_value = ( 'ContractTermCondition' ) res = logic._validate_soft_deletion( contract_term_conditions, contract_term_condition_ids, [contract_term.contract_term_id], ) assert res.status == 400 assert res.errors['code'] == 'error' assert res.errors['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractTermCondition', object_id='' ) def test_validate_soft_deletion_cannot_delete_only_condition(): """Validation returns error when trying to delete only active condition.""" contract_term_condition = ContractTermConditionFactory.create() res = logic._validate_soft_deletion( [contract_term_condition], [contract_term_condition.contract_term_condition_id], [contract_term_condition.contract_term_id], ) assert res.status == 400 assert res.errors['code'] == 'error' assert res.errors['message'] == ERROR_CANNOT_DELETE_CONTRACT_TERM_CONDITION def test_validate_soft_deletion_cannot_delete_all_conditions(): """Validation returns error when trying to delete all active conditions.""" contract_term = ContractTermFactory.create() contract_term_conditions = ContractTermConditionFactory.create_batch( 3, contract_term=contract_term ) contract_term_condition_ids = [ condition.contract_term_condition_id for condition in contract_term_conditions ] res = logic._validate_soft_deletion( contract_term_conditions, contract_term_condition_ids, [contract_term.contract_term_id], ) assert res.status == 400 assert res.errors['code'] == 'error' assert res.errors['message'] == ERROR_CANNOT_DELETE_CONTRACT_TERM_CONDITION