"""Tests for schemas.rows — ContractRow parsing from CSV dicts.""" import pytest from pydantic import ValidationError from schemas import ContractRow, ContractType, PeriodType, RenewalType MINIMAL_ROW = { 'Account ID': '123', 'Contract Name': 'Test Contract', 'Contract Type': 'Distribution', 'Current Period Start Date': '02/01/2026', } class TestContractRowParsing: def test_minimal_row(self): row = ContractRow(**MINIMAL_ROW) assert row.account_id == 123 assert row.contract_name == 'Test Contract' assert row.contract_type == ContractType.DISTRIBUTION assert row.current_period_start == '2026-02-01' def test_empty_optional_fields_are_none(self): row = ContractRow(**MINIMAL_ROW) assert row.signing_entity is None assert row.run_controller is None assert row.execution_date is None assert row.is_excluded is None assert row.is_primary is None def test_extra_columns_ignored(self): data = {**MINIMAL_ROW, 'Unknown Column': 'whatever'} row = ContractRow(**data) assert row.account_id == 123 def test_empty_string_account_id_is_none(self): data = {**MINIMAL_ROW, 'Account ID': ''} row = ContractRow(**data) assert row.account_id is None def test_na_account_id_is_none(self): data = {**MINIMAL_ROW, 'Account ID': 'N/A'} row = ContractRow(**data) assert row.account_id is None def test_whitespace_contract_name_is_none(self): data = {**MINIMAL_ROW, 'Contract Name': ' '} row = ContractRow(**data) assert row.contract_name is None class TestContractRowDates: def test_mm_dd_yyyy(self): data = {**MINIMAL_ROW, 'Execution Date': '01/15/2026'} row = ContractRow(**data) assert row.execution_date == '2026-01-15' def test_yyyy_mm_dd(self): data = {**MINIMAL_ROW, 'Execution Date': '2026-01-15'} row = ContractRow(**data) assert row.execution_date == '2026-01-15' def test_dd_mon_yyyy(self): data = {**MINIMAL_ROW, 'Execution Date': '18-Apr-2026'} row = ContractRow(**data) assert row.execution_date == '2026-04-18' def test_empty_date_is_none(self): data = {**MINIMAL_ROW, 'Execution Date': ''} row = ContractRow(**data) assert row.execution_date is None def test_invalid_date_raises_validation_error(self): data = {**MINIMAL_ROW, 'Execution Date': 'not-a-date'} with pytest.raises(ValidationError, match='Unrecognized date format'): ContractRow(**data) class TestContractRowBooleans: @pytest.mark.parametrize('value', ['Yes', 'yes', 'true', '1', 'on']) def test_truthy(self, value): data = {**MINIMAL_ROW, 'Is Excluded From Accounting Run': value} row = ContractRow(**data) assert row.is_excluded is True @pytest.mark.parametrize('value', ['No', 'no', 'false', '0', 'off']) def test_falsy(self, value): data = {**MINIMAL_ROW, 'Is Primary Contract': value} row = ContractRow(**data) assert row.is_primary is False def test_empty_boolean_is_none(self): data = {**MINIMAL_ROW, 'Is Excluded From Accounting Run': ''} row = ContractRow(**data) assert row.is_excluded is None class TestContractRowEnums: def test_contract_type_normalized(self): data = {**MINIMAL_ROW, 'Contract Type': 'Legacy Distribution'} row = ContractRow(**data) assert row.contract_type == ContractType.LEGACY_DISTRIBUTION def test_contract_type_case_insensitive(self): data = {**MINIMAL_ROW, 'Contract Type': 'DISTRIBUTION'} row = ContractRow(**data) assert row.contract_type == ContractType.DISTRIBUTION def test_invalid_contract_type_is_none(self): data = {**MINIMAL_ROW, 'Contract Type': 'bogus'} row = ContractRow(**data) assert row.contract_type is None def test_renewal_type(self): data = {**MINIMAL_ROW, 'Renewal Rules': 'Renew Periodically'} row = ContractRow(**data) assert row.renewal_rules == RenewalType.RENEW_PERIODICALLY def test_period_type_alias(self): data = {**MINIMAL_ROW, 'Termination Notice Period (Type)': 'Days'} row = ContractRow(**data) assert row.termination_type == PeriodType.DAY class TestContractRowIntegers: def test_valid_integer(self): data = {**MINIMAL_ROW, 'Termination Notice Period (Interval)': '60'} row = ContractRow(**data) assert row.termination_interval == 60 def test_empty_integer_is_none(self): data = {**MINIMAL_ROW, 'Termination Notice Period (Interval)': ''} row = ContractRow(**data) assert row.termination_interval is None def test_invalid_integer_raises_validation_error(self): data = {**MINIMAL_ROW, 'Termination Notice Period (Interval)': 'abc'} with pytest.raises(ValidationError, match='Invalid integer'): ContractRow(**data) class TestContractRowFullRow: def test_all_fields(self): data = { 'Account ID': '456', 'Contract Name': 'Full Contract', 'Contract Type': 'Neighbouring Rights', 'Signing Entity': 'Acme Corp', 'Run Controller': 'Controller A', '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) assert row.account_id == 456 assert row.contract_name == 'Full Contract' assert row.contract_type == ContractType.NEIGHBOURING_RIGHTS assert row.signing_entity == 'Acme Corp' assert row.run_controller == 'Controller A' assert row.execution_date == '2026-01-15' assert row.is_excluded is True assert row.is_primary is False assert row.current_period_start == '2026-02-01' assert row.renewal_rules == RenewalType.RENEW_PERIODICALLY assert row.termination_interval == 60 assert row.termination_type == PeriodType.DAY assert row.renew_after_interval == 12 assert row.renew_after_type == PeriodType.MONTH assert row.current_period_end == '2027-01-31' assert row.collection_interval == 3 assert row.collection_type == PeriodType.MONTH