"""Tests for contract_input_builder — ContractInputBuilder.""" from datetime import datetime from domain.contract_input_builder import ContractInputBuilder from schemas import ( BuildSkip, BuildSuccess, ContractRow, ContractType, MissingFieldPolicy, PeriodType, ReferenceSigningEntity, RenewalType, RunController, ) MINIMAL_ROW = { 'Account ID': '123', 'Contract Name': 'Test Contract', 'Contract Type': 'Distribution', 'Current Period Start Date': '02/01/2026', } def _signing(name: str, entity_id: int) -> ReferenceSigningEntity: return ReferenceSigningEntity( reference_signing_entity_id=entity_id, legal_name=name, company_code='0001', ) def _rc(name: str, rc_id: int) -> RunController: return RunController(run_controller_id=rc_id, run_controller_name=name) def _fixed_clock(): return datetime(2026, 6, 15) def _builder(signing=None, run_ctrl=None, clock=_fixed_clock): return ContractInputBuilder( signing_entities=signing or [], run_controllers=run_ctrl or [], clock=clock, ) class TestBuildPayload: def test_minimal_row(self): row = ContractRow(**MINIMAL_ROW) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.account_id == 123 assert result.input.contract.contract_name == 'Test Contract' assert result.input.contract.contract_type == ContractType.DISTRIBUTION def test_missing_account_id_returns_skip(self): row = ContractRow(**{**MINIMAL_ROW, 'Account ID': ''}) result = _builder().build(row) assert isinstance(result, BuildSkip) assert 'Account ID' in result.reason def test_missing_contract_name_returns_skip(self): row = ContractRow(**{**MINIMAL_ROW, 'Contract Name': ''}) result = _builder().build(row) assert isinstance(result, BuildSkip) assert 'Contract Name' in result.reason def test_missing_contract_type_returns_skip(self): row = ContractRow(**{**MINIMAL_ROW, 'Contract Type': 'bogus'}) result = _builder().build(row) assert isinstance(result, BuildSkip) assert 'contract type' in result.reason def test_signing_entity_lookup(self): row = ContractRow(**{**MINIMAL_ROW, 'Signing Entity': 'acme'}) result = _builder(signing=[_signing('acme', 7)]).build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.reference_signing_entity_id == 7 def test_signing_entity_zero_is_valid(self): row = ContractRow(**{**MINIMAL_ROW, 'Signing Entity': 'acme'}) result = _builder(signing=[_signing('acme', 0)]).build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.reference_signing_entity_id == 0 def test_signing_entity_not_found_skips_when_policy_skip(self): row = ContractRow(**{**MINIMAL_ROW, 'Signing Entity': 'unknown'}) result = _builder().build(row, policy=MissingFieldPolicy.SKIP) assert isinstance(result, BuildSkip) assert 'Signing entity' in result.reason def test_run_controller_lookup(self): row = ContractRow(**{**MINIMAL_ROW, 'Run Controller': 'ctrl'}) result = _builder(run_ctrl=[_rc('ctrl', 5)]).build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.run_controller_id == 5 def test_boolean_defaults(self): row = ContractRow(**MINIMAL_ROW) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.is_excluded_from_accounting_run is False assert result.input.contract.is_primary_contract is True def test_boolean_overrides(self): data = { **MINIMAL_ROW, 'Is Excluded From Accounting Run': 'Yes', 'Is Primary Contract': 'No', } row = ContractRow(**data) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.is_excluded_from_accounting_run is True assert result.input.contract.is_primary_contract is False def test_renewal_defaults_to_continuously_active(self): row = ContractRow(**MINIMAL_ROW) result = _builder().build(row) assert isinstance(result, BuildSuccess) schedule = result.input.lifecycle_schedules[0] assert schedule.renewal_type == RenewalType.CONTINUOUSLY_ACTIVE def test_termination_defaults_to_30_days(self): row = ContractRow(**MINIMAL_ROW) result = _builder().build(row) assert isinstance(result, BuildSuccess) schedule = result.input.lifecycle_schedules[0] assert schedule.termination_notice_detail_interval == 30 assert schedule.termination_notice_detail_type == PeriodType.DAY def test_full_row(self): data = { 'Account ID': '456', 'Contract Name': 'Full Contract', 'Contract Type': 'Neighbouring Rights', 'Signing Entity': 'ent', 'Run Controller': 'ctrl', 'Execution Date': '01/15/2026', 'Is Excluded From Accounting Run': 'Yes', 'Is Primary Contract': 'No', 'Current Period Start Date': '02/01/2026', 'Renewal Rules': 'Renew Periodically', 'Termination Notice Period (Interval)': '60', 'Termination Notice Period (Type)': 'Days', 'Renew After (Interval)': '12', 'Renew After (Type)': 'Months', 'Current Period End Date': '01/31/2027', 'Collection Period (Interval)': '3', 'Collection Period (Type)': 'Months', } row = ContractRow(**data) builder = _builder(signing=[_signing('ent', 10)], run_ctrl=[_rc('ctrl', 20)]) result = builder.build(row) assert isinstance(result, BuildSuccess) assert result.input.contract.account_id == 456 assert result.input.contract.contract_type == ContractType.NEIGHBOURING_RIGHTS assert result.input.contract.execution_date == '2026-01-15' assert result.input.contract.reference_signing_entity_id == 10 assert result.input.contract.run_controller_id == 20 schedule = result.input.lifecycle_schedules[0] assert schedule.renewal_type == RenewalType.RENEW_PERIODICALLY assert schedule.termination_notice_detail_interval == 60 assert schedule.renewal_offset_detail_interval == 12 assert schedule.renewal_offset_detail_type == PeriodType.MONTH assert schedule.collection_period_detail_interval == 3 assert schedule.collection_period_detail_type == PeriodType.MONTH class TestDefaultsTracking: """Verify that defaults_applied is populated correctly.""" def test_full_row_has_no_defaults(self): data = { **MINIMAL_ROW, 'Renewal Rules': 'Continuously Active', 'Termination Notice Period (Interval)': '60', 'Termination Notice Period (Type)': 'Days', } row = ContractRow(**data) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert result.defaults_applied == () def test_missing_renewal_records_default(self): row = ContractRow(**MINIMAL_ROW) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert any('renewal_type' in d for d in result.defaults_applied) def test_missing_termination_records_default(self): data = {**MINIMAL_ROW, 'Renewal Rules': 'Continuously Active'} row = ContractRow(**data) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert any('termination_notice' in d for d in result.defaults_applied) def test_missing_period_start_records_default(self): data = {**MINIMAL_ROW, 'Current Period Start Date': ''} row = ContractRow(**data) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert 'lifecycle_term_start=2026-06-15' in result.defaults_applied def test_missing_period_start_skips_under_skip_policy(self): data = {**MINIMAL_ROW, 'Current Period Start Date': ''} row = ContractRow(**data) result = _builder().build(row, policy=MissingFieldPolicy.SKIP) assert isinstance(result, BuildSkip) assert 'Period Start' in result.reason def test_multiple_defaults_tracked(self): """Row missing renewal, termination, and period start → 3 defaults.""" data = {**MINIMAL_ROW, 'Current Period Start Date': ''} row = ContractRow(**data) result = _builder().build(row) assert isinstance(result, BuildSuccess) assert len(result.defaults_applied) == 3