"""Functional tests for contract_lifecycle.""" from datetime import date, datetime, timedelta from unittest.mock import MagicMock, patch import pytest from abacus_common_logic.constants.error import ERROR_ENTITY_DOES_NOT_EXIST from abacus_common_logic.utils.dates import current_timestamp from dateutil.relativedelta import relativedelta from flask.testing import FlaskClient from abacus_contract.constants.constants import ( CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES, CONTRACT_LIFECYCLE_STATUSES, ) from abacus_contract.constants.error import ( ERROR_CAN_NOT_TERMINATE_CONTRACT_LIFECYCLE, ERROR_CODE_AUTHORIZATION, ERROR_CONTRACT_LIFECYCLE_SCHEDULE_NOT_MATCHED, ERROR_CONTRACT_LIFECYCLE_SCHEDULES_NOT_EXIST, ERROR_CONTRACT_LIFECYCLE_START_AND_END_DATE_NOT_VALID, ERROR_INVALID_RENEWAL_EFFECTIVE_DATE, ERROR_INVALID_STATUS_FOR_RENEWING_CONTRACT_LIFECYCLE, ERROR_INVALID_STATUS_FOR_TERMINATING_CONTRACT_LIFECYCLE, ERROR_SUBSEQUENT_LIFECYCLE_SCHEDULE_NOT_EXIST, ) from abacus_contract.models.contract_lifecycle_schedule import ContractLifecycleSchedule from abacus_contract.tests.utils.factories import ( ContractFactory, ContractLifecycleFactory, ContractLifecycleScheduleDetailFactory, ContractLifecycleScheduleFactory, ) def test_get_contract_lifecycle_by_id(fixture_client): """Test getting a contract_lifecycle by id.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_lifecycle_id = mock_contract_lifecycle.contract_lifecycle_id res = fixture_client.get(f'/contract-lifecycle/{contract_lifecycle_id}') assert res.status_code == 200 assert res.json == { 'contract_lifecycle_id': mock_contract_lifecycle.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle.lifecycle_status, 'lifecycle_term_start': str(mock_contract_lifecycle.lifecycle_term_start), 'lifecycle_term_end': mock_contract_lifecycle.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle.termination_notice_received, 'termination_effective': mock_contract_lifecycle.termination_effective, 'collection_start': mock_contract_lifecycle.collection_start, 'collection_end': mock_contract_lifecycle.collection_end, } def test_get_contract_lifecycle_by_id_error(fixture_client): """Test getting a contract_lifecycle by invalid id.""" contract_lifecycle_id = 12345 res = fixture_client.get(f'/contract-lifecycle/{contract_lifecycle_id}') assert res.status_code == 404 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycle', object_id=contract_lifecycle_id ) def test_get_contract_lifecycle_by_contract_id(fixture_client): """Test getting a contract lifecycle by contract_id.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_id = mock_contract_lifecycle.contract_id res = fixture_client.get(f'/contract/{contract_id}/contract-lifecycle') assert res.status_code == 200 assert res.json == { 'contract_lifecycle_id': mock_contract_lifecycle.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle.lifecycle_status, 'lifecycle_term_start': str(mock_contract_lifecycle.lifecycle_term_start), 'lifecycle_term_end': mock_contract_lifecycle.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle.termination_notice_received, 'termination_effective': mock_contract_lifecycle.termination_effective, 'collection_start': mock_contract_lifecycle.collection_start, 'collection_end': mock_contract_lifecycle.collection_end, } def test_update_contract_lifecycle_by_contract_id(fixture_client): """Test updating a contract lifecycle by contract_id.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_id = mock_contract_lifecycle.contract_id updated_term_start = str(date(2025, 6, 4)) res = fixture_client.put( f'/contract/{contract_id}/contract-lifecycle', json={'lifecycle_term_start': updated_term_start}, ) assert res.status_code == 200 assert res.json['lifecycle_term_start'] == updated_term_start def test_update_contract_lifecycle_by_contract_id_empty(fixture_client): """Test updating a contract lifecycle by contract_id with empty body.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_id = mock_contract_lifecycle.contract_id res = fixture_client.put(f'/contract/{contract_id}/contract-lifecycle', json={}) assert res.status_code == 400 assert res.json['message'] == 'Lifecycle data is required' def test_update_contract_lifecycle_by_contract_id_not_found(fixture_client): """Test updating a contract lifecycle by contract_id for non-existent contract.""" contract_id = '12345' updated_term_start = str(date(2025, 6, 4)) res = fixture_client.put( f'/contract/{contract_id}/contract-lifecycle', json={'lifecycle_term_start': updated_term_start}, ) assert res.status_code == 400 assert res.json['message'] == 'Contract 12345 does not exist.' def test_update_contract_lifecycle_by_contract_id_not_allowed_fields(fixture_client): """Test updating a contract lifecycle with fields that are not allowed.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_id = mock_contract_lifecycle.contract_id updated_term_start = str(date(2025, 6, 4)) res = fixture_client.put( f'/contract/{contract_id}/contract-lifecycle', json={'lifecycle_term_start': updated_term_start, 'nope': 'shant'}, ) assert res.status_code == 400 assert res.json['message'] == 'Non-updatable fields provided in lifecycle.' def test_get_contract_lifecycle_by_contract_id_error(fixture_client): """Test getting a contract lifecycle with invalid contract_id.""" contract_id = 12345 res = fixture_client.get(f'/contract/{contract_id}/contract-lifecycle') assert res.status_code == 400 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='Contract', object_id=contract_id ) def test_get_deleted_contract_lifecycle_by_contract_id(fixture_client): """Test getting a deleted contract lifecycle by contract_id.""" mock_contract_lifecycle = ContractLifecycleFactory.create( deleted_by='Test User', deleted_at='2024-07-01' ) contract_id = mock_contract_lifecycle.contract_id res = fixture_client.get(f'/contract/{contract_id}/contract-lifecycle') assert res.status_code == 200 assert not res.json @pytest.mark.parametrize( ( 'authorize_many_accounts_return', 'profile_type', 'profile_role', 'expected_status_code', ), [ pytest.param( None, 'AbacusProfile', 'administrator', 200, id='standlone check passes' ), pytest.param( True, 'Account360Profile', 'account360', 200, id='pdp check authorized' ), pytest.param( False, 'Account360Profile', 'account360', 403, id='pdp check not authorized' ), ], ) @patch('abacus_contract.blueprints.contract_lifecycle.pdp_authorize_many_accounts') def test_get_contract_lifecycles_by_contract_ids( mock_authorize_many_accounts: MagicMock, fixture_client: FlaskClient, authorize_many_accounts_return: bool, profile_type: str, profile_role: str, expected_status_code: int, ): """Test getting a list of contract_lifecycles by contract ids.""" mock_contract_lifecycle_1 = ContractLifecycleFactory.create() mock_contract_lifecycle_2 = ContractLifecycleFactory.create() mock_contract_lifecycle_3 = ContractLifecycleFactory.create() mock_contract_lifecycle_4 = ContractLifecycleFactory.create( deleted_at='2024-07-12', deleted_by='Test User' ) mock_authorize_many_accounts.return_value = authorize_many_accounts_return ids = [ mock_contract_lifecycle_1.contract_id, mock_contract_lifecycle_2.contract_id, mock_contract_lifecycle_3.contract_id, mock_contract_lifecycle_4.contract_id, ] res = fixture_client.post( '/contract/contract-lifecycle/dataloader', json=ids, headers={ 'Content-Type': 'application/json', 'Orchard-Identity-Id': 'd5ca8ac3-7e51-4793-8775-50d11282504c', 'Orchard-Profile-Id': '35109', 'Orchard-Profile-Type': profile_type, 'Orchard-Roles': profile_role, 'Orchard-Requestor-Service': 'graphql-abacus', }, ) assert res.status_code == expected_status_code if expected_status_code == 403: assert res.json['message'] == 'Unauthorized' assert res.json['code'] == ERROR_CODE_AUTHORIZATION else: assert len(res.json) == len(ids) assert res.json == [ { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_1.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_1.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_1.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_1.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_1.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_1.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_1.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_1.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_1.termination_notice_received, 'termination_effective': mock_contract_lifecycle_1.termination_effective, 'collection_start': mock_contract_lifecycle_1.collection_start, 'collection_end': mock_contract_lifecycle_1.collection_end, } }, { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_2.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_2.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_2.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_2.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_2.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_2.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_2.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_2.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_2.termination_notice_received, 'termination_effective': mock_contract_lifecycle_2.termination_effective, 'collection_start': mock_contract_lifecycle_2.collection_start, 'collection_end': mock_contract_lifecycle_2.collection_end, } }, { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_3.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_3.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_3.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_3.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_3.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_3.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_3.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_3.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_3.termination_notice_received, 'termination_effective': mock_contract_lifecycle_3.termination_effective, 'collection_start': mock_contract_lifecycle_3.collection_start, 'collection_end': mock_contract_lifecycle_3.collection_end, } }, {'data': None}, ] def test_get_contract_lifecycles_by_ids(fixture_client): """Test getting a list of contract_lifecycles by ids.""" mock_contract_lifecycle_1 = ContractLifecycleFactory.create() mock_contract_lifecycle_2 = ContractLifecycleFactory.create() mock_contract_lifecycle_3 = ContractLifecycleFactory.create() mock_contract_lifecycle_4 = ContractLifecycleFactory.create( deleted_at='2024-07-12', deleted_by='Test User' ) ids = [ mock_contract_lifecycle_1.contract_lifecycle_id, mock_contract_lifecycle_2.contract_lifecycle_id, mock_contract_lifecycle_3.contract_lifecycle_id, mock_contract_lifecycle_4.contract_lifecycle_id, ] res = fixture_client.post('/contract-lifecycles/dataloader', json=ids) assert res.status_code == 200 assert len(res.json) == len(ids) assert res.json == [ { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_1.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_1.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_1.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_1.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_1.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_1.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_1.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_1.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_1.termination_notice_received, 'termination_effective': mock_contract_lifecycle_1.termination_effective, 'collection_start': mock_contract_lifecycle_1.collection_start, 'collection_end': mock_contract_lifecycle_1.collection_end, } }, { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_2.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_2.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_2.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_2.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_2.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_2.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_2.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_2.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_2.termination_notice_received, 'termination_effective': mock_contract_lifecycle_2.termination_effective, 'collection_start': mock_contract_lifecycle_2.collection_start, 'collection_end': mock_contract_lifecycle_2.collection_end, } }, { 'data': { 'contract_lifecycle_id': mock_contract_lifecycle_3.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle_3.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle_3.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle_3.lifecycle_status, 'lifecycle_term_start': str( mock_contract_lifecycle_3.lifecycle_term_start ), 'lifecycle_term_end': mock_contract_lifecycle_3.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle_3.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle_3.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle_3.termination_notice_received, 'termination_effective': mock_contract_lifecycle_3.termination_effective, 'collection_start': mock_contract_lifecycle_3.collection_start, 'collection_end': mock_contract_lifecycle_3.collection_end, } }, {'data': None}, ] def test_get_contract_lifecycle_by_schedule_id(fixture_client): """Test getting a contract lifecycle by contract_lifecycle_schedule_id.""" mock_contract_lifecycle = ContractLifecycleFactory.create() contract_lifecycle_schedule_id = ( mock_contract_lifecycle.contract_lifecycle_schedule_id ) res = fixture_client.get( f'/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle' ) assert res.status_code == 200 assert res.json == { 'contract_lifecycle_id': mock_contract_lifecycle.contract_lifecycle_id, 'contract_id': mock_contract_lifecycle.contract_id, 'contract_lifecycle_schedule_id': mock_contract_lifecycle.contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': mock_contract_lifecycle.lifecycle_status, 'lifecycle_term_start': str(mock_contract_lifecycle.lifecycle_term_start), 'lifecycle_term_end': mock_contract_lifecycle.lifecycle_term_end, 'renewal_effective': mock_contract_lifecycle.renewal_effective, 'termination_notice_deadline': mock_contract_lifecycle.termination_notice_deadline, 'termination_notice_received': mock_contract_lifecycle.termination_notice_received, 'termination_effective': mock_contract_lifecycle.termination_effective, 'collection_start': mock_contract_lifecycle.collection_start, 'collection_end': mock_contract_lifecycle.collection_end, } def test_get_contract_lifecycle_by_schedule_id_error(fixture_client): """Test getting a contract lifecycle with invalid contract_lifecycle_schedule_id.""" contract_lifecycle_schedule_id = 12345 res = fixture_client.get( f'/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle' ) assert res.status_code == 400 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycleSchedule', object_id=contract_lifecycle_schedule_id, ) def test_get_deleted_contract_lifecycle_by_schedule_id(fixture_client): """Test getting a deleted contract lifecycle by contract_lifecycle_schedule_id.""" mock_contract_lifecycle = ContractLifecycleFactory.create( deleted_by='Test User', deleted_at='2024-07-01' ) contract_lifecycle_schedule_id = ( mock_contract_lifecycle.contract_lifecycle_schedule_id ) res = fixture_client.get( f'/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle' ) assert res.status_code == 200 assert not res.json def test_get_contract_lifecycle_by_deleted_schedule_id(fixture_client): """Test getting a contract lifecycle by deleted contract_lifecycle_schedule_id.""" mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( deleted_by='Test User', deleted_at='2024-07-01' ) mock_contract_lifecycle = ContractLifecycleFactory.create( contract_lifecycle_schedule=mock_contract_lifecycle_schedule, deleted_by='Test User', deleted_at='2028-07-15', ) contract_lifecycle_schedule_id = ( mock_contract_lifecycle.contract_lifecycle_schedule_id ) res = fixture_client.get( f'/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle' ) assert res.status_code == 404 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycleSchedule', object_id=mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id, ) def test_create_contract_lifecycle_contract_not_exist(fixture_client): """Test creating a contract lifecycle when contract doesn't exist.""" contract_id = 9999 mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create() contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2024-10-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 400 assert res.json['message'] == '400 Bad Request: Contract 9999 does not exist.' def test_create_contract_lifecycle_lifecycle_schedule_not_exist(fixture_client): """Test creating a contract lifecycle when contract_lifecycle_schedule doesn't exist.""" mock_contract = ContractFactory.create() contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = 9999 mock_post_request_payload = {'lifecycle_term_start': '2024-10-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 400 assert ( res.json['message'] == '400 Bad Request: ContractLifecycleSchedule 9999 does not exist.' ) def test_create_contract_lifecycle_with_invalid_lifecycle_schedule(fixture_client): """Test creating a contract lifecycle with invalid contract_lifecycle_schedule.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create() contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2024-10-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 400 assert res.json['message'] == ERROR_CONTRACT_LIFECYCLE_SCHEDULE_NOT_MATCHED.format( contract_lifecycle_schedule_id, contract_id ) def test_create_contract_lifecycle_with_continuously_active_schedule(fixture_client): """Test creating a contract lifecycle for continuously_active lifecycle schedule. creates a contract_lifecycle with Null values for lifecycle_term_end, renewal_effective_date and termination_notice_deadline. """ mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2024-06-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 201 assert res.json == { 'contract_lifecycle_id': 1, 'contract_id': contract_id, 'contract_lifecycle_schedule_id': contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': CONTRACT_LIFECYCLE_STATUSES.ACTIVE, 'lifecycle_term_start': mock_post_request_payload['lifecycle_term_start'], 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, } def test_create_contract_lifecycle_with_renew_after_certain_date_schedule( fixture_client, ): """Test creating a contract lifecycle for renew_after_certain_date lifecycle schedule. creates a contract_lifecycle with - lifecycle_term_end and renewal_effective date which is same as the schedule_end date of lifecycle schedule. - termination_notice_deadline as (lifecycle_term_end - termination_notice_schedule). """ mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule_detail = ( ContractLifecycleScheduleDetailFactory.create( period_interval=1, period_type='month' ) ) mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2025-06-30', termination_notice_detail=mock_contract_lifecycle_schedule_detail, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2024-06-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 201 assert res.json == { 'contract_lifecycle_id': 1, 'contract_id': contract_id, 'contract_lifecycle_schedule_id': contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': CONTRACT_LIFECYCLE_STATUSES.INIT, 'lifecycle_term_start': mock_post_request_payload['lifecycle_term_start'], 'lifecycle_term_end': str(mock_contract_lifecycle_schedule.schedule_end), 'renewal_effective': str(mock_contract_lifecycle_schedule.schedule_end), 'termination_notice_deadline': '2025-05-30', 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, } def test_create_contract_lifecycle_with_renew_periodically_schedule(fixture_client): """Test creating a contract lifecycle for renew_periodically lifecycle schedule. creates a contract_lifecycle with - lifecycle_term_end and renewal_effective as (lifecycle_start_date + renewal_offset_schedule). - And termination_notice_deadline as (lifecycle_term_end - termination_notice_schedule). """ mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule_details = [ ContractLifecycleScheduleDetailFactory.create( period_interval=36, period_type='month' ), ContractLifecycleScheduleDetailFactory.create( period_interval=48, period_type='month' ), ] mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY, termination_notice_detail=mock_contract_lifecycle_schedule_details[0], renewal_offset_detail=mock_contract_lifecycle_schedule_details[1], ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2024-06-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 201 assert res.json == { 'contract_lifecycle_id': 1, 'contract_id': contract_id, 'contract_lifecycle_schedule_id': contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': CONTRACT_LIFECYCLE_STATUSES.ACTIVE, 'lifecycle_term_start': mock_post_request_payload['lifecycle_term_start'], 'lifecycle_term_end': '2028-06-30', 'renewal_effective': '2028-06-30', 'termination_notice_deadline': '2025-06-30', 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, } def test_create_contract_lifecycle_with_future_start_date(fixture_client): """Test creating a contract lifecycle with future start date.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': '2090-06-30'} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 201 assert res.json == { 'contract_lifecycle_id': 1, 'contract_id': contract_id, 'contract_lifecycle_schedule_id': contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': CONTRACT_LIFECYCLE_STATUSES.INIT, 'lifecycle_term_start': mock_post_request_payload['lifecycle_term_start'], 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, } def test_create_contract_lifecycle_with_current_start_date(fixture_client): """Test creating a contract lifecycle with current start date.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = { 'lifecycle_term_start': str(current_timestamp().date()) } res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 201 assert res.json == { 'contract_lifecycle_id': 1, 'contract_id': contract_id, 'contract_lifecycle_schedule_id': contract_lifecycle_schedule_id, 'last_renewed': None, 'lifecycle_status': CONTRACT_LIFECYCLE_STATUSES.ACTIVE, 'lifecycle_term_start': mock_post_request_payload['lifecycle_term_start'], 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, } def test_create_contract_lifecycle_with_start_date_after_term_end(fixture_client): """Test creating a contract lifecycle. returns an error when lifecycle_term_start is after lifecycle_term_end. """ lifecycle_term_start = '2024-08-15' schedule_end = '2024-07-30' mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end=schedule_end, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': lifecycle_term_start} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 400 assert res.json[ 'message' ] == ERROR_CONTRACT_LIFECYCLE_START_AND_END_DATE_NOT_VALID.format( lifecycle_term_start, schedule_end ) def test_create_contract_lifecycle_with_same_start_and_end_date(fixture_client): """Test creating a contract lifecycle. returns an error when lifecycle_term_start is same as lifecycle_term_end. """ schedule_end = lifecycle_term_start = '2024-07-30' mock_contract = ContractFactory.create() mock_contract_lifecycle_schedule = ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end=schedule_end, ) contract_id = mock_contract.contract_id contract_lifecycle_schedule_id = ( mock_contract_lifecycle_schedule.contract_lifecycle_schedule_id ) mock_post_request_payload = {'lifecycle_term_start': lifecycle_term_start} res = fixture_client.post( f'/contract/{contract_id}/contract-lifecycle-schedule/{contract_lifecycle_schedule_id}/contract-lifecycle', json=mock_post_request_payload, ) assert res.status_code == 400 assert res.json[ 'message' ] == ERROR_CONTRACT_LIFECYCLE_START_AND_END_DATE_NOT_VALID.format( lifecycle_term_start, schedule_end ) def test_get_contract_lifecycles_to_be_renewed(fixture_client): """Test getting 'active' contract_lifecycle records to be renewed today.""" today = date.today() renewing_records = ContractLifecycleFactory.create_batch( 3, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=today ) not_active = ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INIT, renewal_effective=today ) not_renewing = ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=None ) res = fixture_client.get('/contract-lifecycles/renewable/') assert res.json['total_count'] == len(renewing_records) assert len(res.json['items']) == len(renewing_records) assert all(item['renewal_effective'] == str(today) for item in res.json['items']) assert all( item['lifecycle_status'] == CONTRACT_LIFECYCLE_STATUSES.ACTIVE for item in res.json['items'] ) assert not_active.contract_lifecycle_id not in [ item['contract_lifecycle_id'] for item in res.json['items'] ] assert not_renewing.contract_lifecycle_id not in [ item['contract_lifecycle_id'] for item in res.json['items'] ] def test_get_by_termination_effective_or_collection_end(fixture_client): """Test to get contract_lifecycle records to be terminated.""" today = date.today() terminating_records = ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=today, ) ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=today, deleted_by='Test User', ) in_collection_period_records = ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.IN_COLLECTION_PERIOD, collection_end=today, ) ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.IN_COLLECTION_PERIOD, collection_end=today, deleted_by='Test User', ) ContractLifecycleFactory.create(lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE) records = terminating_records + in_collection_period_records lifecycle_status = [ CONTRACT_LIFECYCLE_STATUSES.IN_COLLECTION_PERIOD, CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, ] res = fixture_client.get('/contract-lifecycles/terminable/?limit=10') assert res.json['total_count'] == len(records) assert all( item['lifecycle_status'] in lifecycle_status for item in res.json['items'] ) assert all( item['termination_effective'] == str(today) or item['collection_end'] == str(today) for item in res.json['items'] ) def test_get_contract_lifecycles_to_be_activated(fixture_client): """Test getting contract_lifecycle records that are ready to be activated.""" today = date.today() init_records = ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INIT, lifecycle_term_start=today ) active_record = ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, lifecycle_term_start=today ) res = fixture_client.get('/contract-lifecycles/activable') assert res.json['total_count'] == len(init_records) assert len(res.json['items']) == len(init_records) assert all( item['lifecycle_status'] == CONTRACT_LIFECYCLE_STATUSES.INIT for item in res.json['items'] ) assert active_record.contract_lifecycle_id not in [ item['contract_lifecycle_id'] for item in res.json['items'] ] def test_terminate_deleted_contract_lifecycles(fixture_client): """Test to terminate deleted contract lifecycle.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, deleted_by='Test User', ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 404 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycle', object_id=mock_contract_lifecycles[0].contract_lifecycle_id, ) def test_terminate_contract_lifecycles_invalid_termination_status(fixture_client): """Test error is thrown for invalid lifecycle_status.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TERMINATED ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json[ 'message' ] == ERROR_INVALID_STATUS_FOR_TERMINATING_CONTRACT_LIFECYCLE.format( mock_contract_lifecycles[0].contract_lifecycle_id ) def test_terminate_contract_lifecycles_null_termination_effective(fixture_client): """Test error is thrown if termination_effective date is null.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=date.today(), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=None, ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_CAN_NOT_TERMINATE_CONTRACT_LIFECYCLE.format( mock_contract_lifecycles[1].contract_lifecycle_id ) def test_terminate_contract_lifecycles_future_termination_effective(fixture_client): """Test error is thrown if termination_effective date is in future.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=date.today(), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=datetime(2050, 10, 20), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_CAN_NOT_TERMINATE_CONTRACT_LIFECYCLE.format( mock_contract_lifecycles[1].contract_lifecycle_id ) def test_terminate_contract_lifecycles_past_or_current_termination_effective( fixture_client, ): """Test terminating contract lifecycles successfully. updates lifecycle_status to "terminated" if termination_effective date of contract_lifecycles are in past/future. """ today = date.today() mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=today, ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=datetime(2024, 7, 20), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] assert all( contract_lifecycle.lifecycle_status == CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED for contract_lifecycle in mock_contract_lifecycles ) res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 200 assert res.json == [ { 'contract_id': 1, 'contract_lifecycle_schedule_id': 1, 'last_renewed': None, 'lifecycle_status': 'terminated', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': str(today), 'collection_start': None, 'collection_end': None, 'contract_lifecycle_id': 1, }, { 'contract_id': 3, 'contract_lifecycle_schedule_id': 2, 'last_renewed': None, 'lifecycle_status': 'terminated', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': '2024-07-20', 'collection_start': None, 'collection_end': None, 'contract_lifecycle_id': 2, }, ] def test_terminate_contract_lifecycles_future_or_past_collection_end(fixture_client): """Test terminating contract lifecycles successfully. updates lifecycle_status to "terminated" if collection_end date is in past. updates lifecycle_status to "in_collection_period" if collection_end date is in future. """ today = date.today() mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=today, collection_start=today, collection_end=datetime(2050, 10, 20), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=datetime(2023, 9, 12), collection_start=datetime(2023, 9, 12), collection_end=datetime(2024, 9, 12), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] assert all( contract_lifecycle.lifecycle_status == CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED for contract_lifecycle in mock_contract_lifecycles ) res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 200 assert res.json == [ { 'contract_id': 1, 'contract_lifecycle_schedule_id': 1, 'last_renewed': None, 'lifecycle_status': 'in_collection_period', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': str(today), 'collection_start': str(today), 'collection_end': '2050-10-20', 'contract_lifecycle_id': 1, }, { 'contract_id': 3, 'contract_lifecycle_schedule_id': 2, 'last_renewed': None, 'lifecycle_status': 'terminated', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': '2023-09-12', 'collection_start': '2023-09-12', 'collection_end': '2024-09-12', 'contract_lifecycle_id': 2, }, ] def test_terminate_contract_lifecycles_current_or_past_collection_end(fixture_client): """Test terminating contract lifecycles successfully. updates lifecycle_status to "terminated" if collection_end date is today or is in past. """ today = date.today() mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=datetime(2023, 9, 18), collection_start=datetime(2023, 9, 18), collection_end=today, ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, lifecycle_term_start=datetime(2022, 9, 12), termination_effective=datetime(2023, 10, 10), collection_start=datetime(2023, 10, 10), collection_end=datetime(2024, 9, 12), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] assert all( contract_lifecycle.lifecycle_status == CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED for contract_lifecycle in mock_contract_lifecycles ) res = fixture_client.post( '/contract-lifecycles/terminate', json=mock_post_request_payload ) assert res.status_code == 200 assert res.json == [ { 'contract_id': 1, 'contract_lifecycle_schedule_id': 1, 'last_renewed': None, 'lifecycle_status': 'terminated', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': '2023-09-18', 'collection_start': '2023-09-18', 'collection_end': str(today), 'contract_lifecycle_id': 1, }, { 'contract_id': 3, 'contract_lifecycle_schedule_id': 2, 'last_renewed': None, 'lifecycle_status': 'terminated', 'lifecycle_term_start': '2022-09-12', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': '2023-10-10', 'collection_start': '2023-10-10', 'collection_end': '2024-09-12', 'contract_lifecycle_id': 2, }, ] def test_activate_contract_lifecycles_with_valid_records(fixture_client): """Test activating contract_lifecycle records.""" today = date.today() init_records = ContractLifecycleFactory.create_batch( 2, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INIT, lifecycle_term_start=today ) record_ids = [item.contract_lifecycle_id for item in init_records] res = fixture_client.post('/contract-lifecycles/activate', json=record_ids) assert res.status_code == 200 assert all( item['lifecycle_status'] == CONTRACT_LIFECYCLE_STATUSES.ACTIVE for item in res.json ) def test_activate_contract_lifecycles_with_invalid_records(fixture_client): """Test error is thrown due to some records not having init status.""" today = date.today() init_records = ContractLifecycleFactory.create_batch( 1, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INIT, lifecycle_term_start=today ) to_be_terminated_records = ContractLifecycleFactory.create_batch( 1, lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.TO_BE_TERMINATED, termination_effective=today, ) combined_records = init_records + to_be_terminated_records record_ids = [item.contract_lifecycle_id for item in combined_records] res = fixture_client.post('/contract-lifecycles/activate', json=record_ids) assert res.status_code == 400 assert ( res.json['message'] == 'lifecycle status of contract_lifecycle 2 should be init' ) def test_activate_contract_lifecycles_with_nonexisting_records(fixture_client): """Test error is thrown for a non-existing record.""" record_ids = [1, 2, 3] res = fixture_client.post('/contract-lifecycles/activate', json=record_ids) assert res.status_code == 400 assert res.json['message'] == '400 Bad Request: ContractLifecycle 1 does not exist.' def test_activate_contract_lifecycles_with_deleted_record(fixture_client): """Test error is thrown for deleted records.""" today = date.today() deleted_record = ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INACTIVE, lifecycle_term_start=today, deleted_by='Test User', ) record_ids = [deleted_record.contract_lifecycle_id] res = fixture_client.post('/contract-lifecycles/activate', json=record_ids) assert res.status_code == 404 assert res.json['message'] == 'Contract Lifecycle 1 does not exist.' def test_renew_deleted_contract_lifecycles(fixture_client): """Test to renew deleted contract lifecycle.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, deleted_by='Test User' ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) assert res.status_code == 404 assert res.json['message'] == ERROR_ENTITY_DOES_NOT_EXIST.format( object_type='ContractLifecycle', object_id=mock_contract_lifecycles[0].contract_lifecycle_id, ) def test_renew_contract_lifecycles_invalid_renewal_status(fixture_client): """Test error is thrown for invalid lifecycle_status.""" mock_contract_lifecycles = [ ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.INACTIVE ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json[ 'message' ] == ERROR_INVALID_STATUS_FOR_RENEWING_CONTRACT_LIFECYCLE.format( mock_contract_lifecycles[0].contract_lifecycle_id ) def test_renew_contract_lifecycles_null_renewal_effective(fixture_client): """Test error is thrown if renewal_effective date is null.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedules = [ ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2024-06-30', ), ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ), ] mock_contract_lifecycles = [ ContractLifecycleFactory.create( contract=mock_contract, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[0], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=None ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_INVALID_RENEWAL_EFFECTIVE_DATE.format( mock_contract_lifecycles[1].contract_lifecycle_id ) def test_renew_contract_lifecycles_future_renewal_effective(fixture_client): """Test error is thrown if renewal_effective date is in future.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedules = [ ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2024-06-30', ), ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ), ] mock_contract_lifecycles = [ ContractLifecycleFactory.create( contract=mock_contract, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[0], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=datetime(2050, 10, 20), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_INVALID_RENEWAL_EFFECTIVE_DATE.format( mock_contract_lifecycles[0].contract_lifecycle_id ) def test_renew_contract_lifecycles_no_schedules_found(fixture_client): """Test error is thrown if schedules are not found for contract_id of lifecycle.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedules = [ ContractLifecycleScheduleFactory.create( renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2024-06-30', ), ContractLifecycleScheduleFactory.create( renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE ), ] mock_contract_lifecycles = [ ContractLifecycleFactory.create( contract=mock_contract, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[0], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_CONTRACT_LIFECYCLE_SCHEDULES_NOT_EXIST.format( mock_contract_lifecycles[0].contract_id ) def test_renew_contract_lifecycles_subsequent_schedule_not_exist(fixture_client): """Test error is thrown if a subsequent schedule does not exist.""" mock_contract = ContractFactory.create() mock_contract_lifecycle_schedules = [ ContractLifecycleScheduleFactory.create( contract=mock_contract, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ) ] mock_contract_lifecycles = [ ContractLifecycleFactory.create( contract=mock_contract, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[0], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ContractLifecycleFactory.create( lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, renewal_effective=date.today(), ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) assert res.status_code == 400 assert res.json['message'] == ERROR_SUBSEQUENT_LIFECYCLE_SCHEDULE_NOT_EXIST.format( mock_contract_lifecycles[0].contract_id ) def test_renew_contract_lifecycles(fixture_client): """Test renewing contract lifecycles.""" today = date.today() mock_contract_1 = ContractFactory.create() mock_contract_2 = ContractFactory.create() mock_contract_3 = ContractFactory.create() contract_id_1 = mock_contract_1.contract_id contract_id_2 = mock_contract_2.contract_id contract_id_3 = mock_contract_3.contract_id mock_contract_lifecycle_schedule_details = [ ContractLifecycleScheduleDetailFactory.create( period_interval=1, period_type='month' ), ContractLifecycleScheduleDetailFactory.create( period_interval=3, period_type='year' ), ] mock_contract_lifecycle_schedules = [ ContractLifecycleScheduleFactory.create( contract=mock_contract_1, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2024-06-30', termination_notice_detail=mock_contract_lifecycle_schedule_details[0], ), ContractLifecycleScheduleFactory.create( contract=mock_contract_1, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE, ), ContractLifecycleScheduleFactory.create( contract=mock_contract_2, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_AFTER_CERTAIN_DATE, schedule_end='2024-08-30', termination_notice_detail=mock_contract_lifecycle_schedule_details[0], ), ContractLifecycleScheduleFactory.create( contract=mock_contract_2, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY, termination_notice_detail=mock_contract_lifecycle_schedule_details[0], renewal_offset_detail=mock_contract_lifecycle_schedule_details[1], ), ContractLifecycleScheduleFactory.create( contract=mock_contract_3, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY, termination_notice_detail=mock_contract_lifecycle_schedule_details[0], renewal_offset_detail=mock_contract_lifecycle_schedule_details[1], ), ContractLifecycleScheduleFactory.create( contract=mock_contract_3, renewal_type=CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY, termination_notice_detail=mock_contract_lifecycle_schedule_details[0], renewal_offset_detail=mock_contract_lifecycle_schedule_details[1], ), ] mock_contract_lifecycles = [ ContractLifecycleFactory.create( contract=mock_contract_1, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[0], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, lifecycle_term_start='2020-01-01', renewal_effective=datetime(2024, 9, 24), ), ContractLifecycleFactory.create( contract=mock_contract_2, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[2], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, lifecycle_term_start='2019-02-01', renewal_effective=datetime(2024, 9, 24), ), ContractLifecycleFactory.create( contract=mock_contract_3, contract_lifecycle_schedule=mock_contract_lifecycle_schedules[4], lifecycle_status=CONTRACT_LIFECYCLE_STATUSES.ACTIVE, lifecycle_term_start='2019-02-01', renewal_effective=today, ), ] mock_post_request_payload = [ contract_lifecycle.contract_lifecycle_id for contract_lifecycle in mock_contract_lifecycles ] res = fixture_client.post( '/contract-lifecycles/renew/', json=mock_post_request_payload ) lifecycle_term_start = today + timedelta(days=1) renewal_effective = lifecycle_term_start + relativedelta(years=3) termination_notice_deadline = ( lifecycle_term_start + relativedelta(years=3) - relativedelta(months=1) ) today = str(current_timestamp().date()) assert res.status_code == 200 assert res.json == [ { 'contract_id': 1, 'contract_lifecycle_schedule_id': 2, 'last_renewed': today, 'lifecycle_status': 'active', 'lifecycle_term_start': '2024-09-25', 'lifecycle_term_end': None, 'renewal_effective': None, 'termination_notice_deadline': None, 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, 'contract_lifecycle_id': 4, }, { 'contract_id': 2, 'contract_lifecycle_schedule_id': 4, 'last_renewed': today, 'lifecycle_status': 'active', 'lifecycle_term_start': '2024-09-25', 'lifecycle_term_end': '2027-09-25', 'renewal_effective': '2027-09-25', 'termination_notice_deadline': '2027-08-25', 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, 'contract_lifecycle_id': 5, }, { 'contract_id': 3, 'contract_lifecycle_schedule_id': 6, 'last_renewed': today, 'lifecycle_status': 'active', 'lifecycle_term_start': str(lifecycle_term_start), 'lifecycle_term_end': str(renewal_effective), 'renewal_effective': str(renewal_effective), 'termination_notice_deadline': str(termination_notice_deadline), 'termination_notice_received': None, 'termination_effective': None, 'collection_start': None, 'collection_end': None, 'contract_lifecycle_id': 6, }, ] schedules_for_first_contract = ( ContractLifecycleSchedule.get_contract_lifecycle_schedules_by_contract_id( contract_id_1 ) ) assert len(schedules_for_first_contract) == 1 assert ( schedules_for_first_contract[0].renewal_type == CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.CONTINUOUSLY_ACTIVE ) schedules_for_second_contract = ( ContractLifecycleSchedule.get_contract_lifecycle_schedules_by_contract_id( contract_id_2 ) ) assert len(schedules_for_second_contract) == 2 assert all( [ schedule.renewal_type == CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY for schedule in schedules_for_second_contract ] ) schedules_for_third_contract = ( ContractLifecycleSchedule.get_contract_lifecycle_schedules_by_contract_id( contract_id_3 ) ) assert len(schedules_for_third_contract) == 2 assert all( [ schedule.renewal_type == CONTRACT_LIFECYCLE_SCHEDULE_RENEWAL_TYPES.RENEW_PERIODICALLY for schedule in schedules_for_third_contract ] )