"""Contract Reserve functional tests.""" from unittest.mock import patch from abacus_contract.constants.error import ERROR_RESERVE_ALREADY_EXISTS from abacus_contract.models import ContractReserve from abacus_contract.tests.utils.factories import ( AccountContractFactory, ContractFactory, ContractReserveFactory, ) def test_contract_reserve_without_release_schedule(fixture_client): """POST contract_reserve without release_schedule field.""" contract = ContractFactory.create() params = { 'reserve_rate': 7, 'reserve_release_offset_in_months': 1, 'installments_in_months': 3, } res = fixture_client.post( f'/contract/{contract.contract_id}/reserves/', json=params ) assert res.status_code == 201 assert res.json == { 'contract_id': 1, 'contract_reserve_id': 1, 'installments_in_months': 3, 'reserve_release_offset_in_months': 1, 'reserve_rate': '7.00', 'release_schedule': ['0.333333333333', '0.333333333333', '0.333333333334'], } assert contract.contract_reserve def test_contract_reserve_with_release_schedule(fixture_client): """POST contract_reserve with release_schedule field.""" contract = ContractFactory.create() params = { 'reserve_rate': 20, 'reserve_release_offset_in_months': 1, 'installments_in_months': 2, 'release_schedule': ['0.9', '0.1'], } res = fixture_client.post( f'/contract/{contract.contract_id}/reserves/', json=params ) assert res.status_code == 201 assert res.json == { 'contract_id': 1, 'contract_reserve_id': 1, 'installments_in_months': 2, 'reserve_release_offset_in_months': 1, 'reserve_rate': '20.00', 'release_schedule': ['0.9', '0.1'], } def test_contract_reserve_already_exists(fixture_client): """POST contract_reserve returns error when contract already has one.""" contract = ContractFactory.create() ContractReserveFactory.create(contract=contract) params = { 'reserve_rate': 20, 'reserve_release_offset_in_months': 1, 'installments_in_months': 2, 'release_schedule': ['10.00', '10.00'], } res = fixture_client.post( f'/contract/{contract.contract_id}/reserves/', json=params ) assert res.status_code == 400 assert res.json['message'] == ERROR_RESERVE_ALREADY_EXISTS def test_update_contract_reserve_by_contract_id(fixture_client): """Update contract reserve by contract id.""" contract_reserve = ContractReserveFactory() params = { 'reserve_rate': 20, 'reserve_release_offset_in_months': 1, 'installments_in_months': 4, } res = fixture_client.put( f'/contract/{contract_reserve.contract_id}/reserves/', json=params ) assert res.status_code == 201 assert res.json['contract_reserve_id'] != contract_reserve.contract_reserve_id assert res.json['contract_id'] == contract_reserve.contract_id assert res.json == { 'contract_id': 1, 'contract_reserve_id': 2, 'installments_in_months': 4, 'reserve_release_offset_in_months': 1, 'reserve_rate': '20.00', 'release_schedule': [ '0.250000000000', '0.250000000000', '0.250000000000', '0.250000000000', ], } def test_get_reserve_by_contract_id(fixture_client): """GET contract_reserve for a specified contract.""" contract = ContractFactory.create() ContractReserveFactory.create(contract=contract) res = fixture_client.get(f'/contract/{contract.contract_id}/reserves/') assert res.status_code == 200 assert res.json == { 'contract_id': 1, 'contract_reserve_id': 1, 'installments_in_months': 4, 'reserve_rate': '80.00', 'reserve_release_offset_in_months': 2, 'release_schedule': [ '0.250000000000', '0.250000000000', '0.250000000000', '0.250000000000', ], } def test_get_contract_reserves_by_ids(create_mock_account, fixture_client): """POST request to get reserves by list of contract_reserve_ids.""" contracts = ContractFactory.create_batch(3) account_contracts = [ AccountContractFactory.create(contract=contract) for contract in contracts ] contract_reserves = [ ContractReserveFactory.create(contract=contracts[i]) for i in [0, 2] ] contract_reserve_ids = [cr.contract_reserve_id for cr in contract_reserves] res = fixture_client.post('/contract-reserves/', json=contract_reserve_ids) assert res.status_code == 200 assert len(res.json) == 2 assert res.json == [ { 'account_id': account_contracts[0].account_id, 'contract_id': contracts[0].contract_id, 'contract_reserve_id': contract_reserves[0].contract_reserve_id, 'installments_in_months': contract_reserves[0].installments_in_months, 'reserve_rate': str(contract_reserves[0].reserve_rate), 'reserve_release_offset_in_months': contract_reserves[ 0 ].reserve_release_offset_in_months, 'release_schedule': contract_reserves[0].release_schedule, }, { 'account_id': account_contracts[1].account_id, 'contract_id': contracts[2].contract_id, 'contract_reserve_id': contract_reserves[1].contract_reserve_id, 'installments_in_months': contract_reserves[1].installments_in_months, 'reserve_rate': str(contract_reserves[1].reserve_rate), 'reserve_release_offset_in_months': contract_reserves[ 1 ].reserve_release_offset_in_months, 'release_schedule': contract_reserves[1].release_schedule, }, ] def test_get_contract_reserves_by_contract_ids(fixture_client): """POST request to get reserves by list of contract_ids.""" contracts = ContractFactory.create_batch(3) contract_reserve = ContractReserveFactory.create(contract=contracts[0]) contract_ids = [c.contract_id for c in contracts] res = fixture_client.post('/contract/contract-reserves/', json=contract_ids) assert res.status_code == 200 assert len(res.json) == 1 assert res.json == [ { 'contract_id': contract_reserve.contract_id, 'contract_reserve_id': contract_reserve.contract_reserve_id, 'installments_in_months': contract_reserve.installments_in_months, 'reserve_rate': str(contract_reserve.reserve_rate), 'reserve_release_offset_in_months': contract_reserve.reserve_release_offset_in_months, 'release_schedule': contract_reserve.release_schedule, } ] def test_get_reserve_by_contract_id_excludes_deleted_reserves(fixture_client): """GET contract_reserve for a specified contract filters out deleted reserves.""" contracts = ContractFactory.create_batch(2) contract_ids = [contract.contract_id for contract in contracts] deleted_reserve = ContractReserveFactory.create(contract=contracts[0]) deleted_reserve.deleted_at = '2023-01-01' deleted_reserve.deleted_by = 'your mama' active_reserves = [ ContractReserveFactory.create(contract=contract) for contract in contracts ] res = fixture_client.post('/contract/contract-reserves/', json=contract_ids) assert res.status_code == 200 assert len(res.json) == 2 assert deleted_reserve.contract_reserve_id not in [ reserve.get('contract_reserve_id') for reserve in res.json ] assert active_reserves[0].contract_reserve_id in [ reserve.get('contract_reserve_id') for reserve in res.json ] assert active_reserves[1].contract_reserve_id in [ reserve.get('contract_reserve_id') for reserve in res.json ] assert set(contract_ids) == set( [reserve.get('contract_id') for reserve in res.json] ) BULK_UPDATE_FF_PATH = ( 'abacus_contract.blueprints.contract_reserve.is_abacus_bulk_entity_update_enabled' ) @patch(BULK_UPDATE_FF_PATH) def test_delete_contract_reserve_by_contract_id(mock_ff, fixture_client): """DELETE contract reserve soft-deletes the active reserve for a contract.""" mock_ff.return_value = True contract_reserve = ContractReserveFactory.create() contract_reserve_id = contract_reserve.contract_reserve_id res = fixture_client.delete(f'/contract/{contract_reserve.contract_id}/reserves/') assert res.status_code == 204 deleted = ContractReserve.get_by_id(contract_reserve_id) assert deleted.deleted_at is not None assert deleted.deleted_by is not None @patch(BULK_UPDATE_FF_PATH) def test_delete_contract_reserve_by_contract_id_no_active_reserve( mock_ff, fixture_client ): """DELETE contract reserve returns 400 when contract has no active reserve.""" mock_ff.return_value = True contract = ContractFactory.create() res = fixture_client.delete(f'/contract/{contract.contract_id}/reserves/') assert res.status_code == 400 @patch(BULK_UPDATE_FF_PATH) def test_delete_contract_reserve_feature_flag_disabled(mock_ff, fixture_client): """DELETE contract reserve returns 403 when feature flag is disabled.""" mock_ff.return_value = False contract_reserve = ContractReserveFactory.create() res = fixture_client.delete(f'/contract/{contract_reserve.contract_id}/reserves/') assert res.status_code == 403 @patch(BULK_UPDATE_FF_PATH) def test_delete_contract_reserve_contract_not_found(mock_ff, fixture_client): """DELETE contract reserve returns 400 when contract does not exist.""" mock_ff.return_value = True res = fixture_client.delete('/contract/99999/reserves/') assert res.status_code == 400