Spaces:
Runtime error
Runtime error
| """Tests for the minimal strict V1 input schema.""" | |
| from datetime import date | |
| import pytest | |
| from pydantic import ValidationError | |
| from sentinel.user_input import ( | |
| AlcoholConsumption, | |
| # Models | |
| Anthropometrics, | |
| AspirinUse, | |
| BreastHealthHistory, | |
| CancerType, | |
| ChronicCondition, | |
| ClinicalTests, | |
| ComplexionLevel, | |
| Country, | |
| Demographics, | |
| DermatologicProfile, | |
| DREResult, | |
| Ethnicity, | |
| FamilyMemberCancer, | |
| FamilyRelation, | |
| FamilySide, | |
| FemaleSmallMolesCategory, | |
| FemaleSpecific, | |
| FemaleTanResponse, | |
| FrecklingIntensity, | |
| GeneticMutation, | |
| HormoneUse, | |
| HormoneUseHistory, | |
| Lifestyle, | |
| MaleSmallMolesCategory, | |
| MenstrualHistory, | |
| NSAIDUse, | |
| ParityHistory, | |
| PersonalMedicalHistory, | |
| PhysicalActivityLevel, | |
| PSATest, | |
| RelationshipDegree, | |
| Sex, | |
| SmokingHistory, | |
| SmokingStatus, | |
| SymptomEntry, | |
| SymptomType, | |
| UserInput, | |
| USGeographicRegion, | |
| ) | |
| class TestStrictSchema: | |
| """Test schema strictness and validation.""" | |
| def test_strict_forbids_extra_fields(self): | |
| """Test that extra fields are rejected.""" | |
| with pytest.raises(ValidationError) as exc_info: | |
| UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| extra_field="should_fail", | |
| ) | |
| assert "extra inputs are not permitted" in str(exc_info.value).lower() | |
| def test_required_fields_enforced(self): | |
| """Test that required fields must be present.""" | |
| with pytest.raises(ValidationError): | |
| UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| # Missing sex | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| ) | |
| def test_enum_validation(self): | |
| """Test that only valid enum values are accepted.""" | |
| with pytest.raises(ValidationError): | |
| UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex="invalid_sex", # Should be Sex enum | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| ) | |
| def test_numeric_constraints(self): | |
| """Test that numeric fields adhere to their defined ranges.""" | |
| with pytest.raises(ValidationError): | |
| UserInput( | |
| demographics=Demographics( | |
| age_years=150, # Too high | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| ) | |
| def test_date_parsing(self): | |
| """Test that date fields correctly accept datetime.date objects.""" | |
| test_date = date(2023, 1, 15) | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| clinical_tests=ClinicalTests( | |
| psa=PSATest( | |
| value_ng_ml=2.5, | |
| date=test_date, | |
| ) | |
| ), | |
| ) | |
| assert user.clinical_tests.psa.date == test_date | |
| def test_minimal_valid_input(self): | |
| """Test a minimal valid input.""" | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| ) | |
| assert user.demographics.age_years == 45 | |
| assert user.demographics.sex == Sex.FEMALE | |
| assert user.schema_version == "v1.0" | |
| def test_comprehensive_input(self): | |
| """Test a comprehensive input with all sections populated.""" | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| ethnicity=Ethnicity.WHITE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| country=Country.UNITED_STATES, | |
| education_level=16, | |
| townsend_index=0.5, | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory( | |
| status=SmokingStatus.FORMER, | |
| cigarettes_per_day=20.0, | |
| years_smoked=10.0, | |
| years_since_quit=5.0, | |
| pack_years=10.0, | |
| ), | |
| alcohol_consumption=AlcoholConsumption.MODERATE, | |
| physical_activity_level=PhysicalActivityLevel.MODERATE, | |
| ), | |
| family_history=[ | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MOTHER, | |
| cancer_type=CancerType.BREAST, | |
| age_at_diagnosis=55, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ) | |
| ], | |
| personal_medical_history=PersonalMedicalHistory( | |
| chronic_conditions=[ChronicCondition.DIABETES], | |
| previous_cancers=[], | |
| genetic_mutations=[], | |
| has_polyps=False, | |
| aspirin_use=AspirinUse.CURRENT, | |
| nsaid_use=NSAIDUse.NEVER, | |
| ), | |
| female_specific=FemaleSpecific( | |
| menstrual=MenstrualHistory( | |
| age_at_menarche=12, | |
| age_at_menopause=None, | |
| ), | |
| parity=ParityHistory( | |
| num_live_births=2, | |
| age_at_first_live_birth=28, | |
| ), | |
| hormone_use=HormoneUseHistory(estrogen_use=HormoneUse.NEVER), | |
| breast_health=BreastHealthHistory( | |
| num_biopsies=1, | |
| atypical_hyperplasia=False, | |
| ), | |
| ), | |
| clinical_tests=ClinicalTests( | |
| psa=PSATest( | |
| value_ng_ml=2.5, | |
| date=date(2023, 1, 15), | |
| ) | |
| ), | |
| symptoms=[ | |
| SymptomEntry( | |
| symptom_type=SymptomType.ABDOMINAL_PAIN, | |
| onset_date=date(2023, 1, 10), | |
| duration_days=5, | |
| ) | |
| ], | |
| dermatologic=DermatologicProfile( | |
| region=USGeographicRegion.CENTRAL, | |
| complexion=ComplexionLevel.MEDIUM, | |
| freckling=FrecklingIntensity.MILD, | |
| female_tan=FemaleTanResponse.MODERATE, | |
| female_small_moles=FemaleSmallMolesCategory.LESS_THAN_FIVE, | |
| male_has_two_or_more_big_moles=False, | |
| solar_damage=False, | |
| ), | |
| ) | |
| assert user.demographics.ethnicity == Ethnicity.WHITE | |
| assert user.lifestyle.smoking.status == SmokingStatus.FORMER | |
| assert len(user.family_history) == 1 | |
| assert user.family_history[0].cancer_type == CancerType.BREAST | |
| assert user.female_specific is not None | |
| assert user.female_specific.parity.num_live_births == 2 | |
| def test_female_specific_optional(self): | |
| """Test that female_specific is optional.""" | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.MALE, # Male user | |
| anthropometrics=Anthropometrics(height_cm=175, weight_kg=80), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| female_specific=None, # Should be allowed for males | |
| ) | |
| assert user.female_specific is None | |
| def test_nested_strict_models(self): | |
| """Test that nested models also forbid extra fields.""" | |
| with pytest.raises(ValidationError): | |
| Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| extra_field="should_fail", | |
| ) | |
| def test_list_fields_default_to_empty(self): | |
| """Test that list fields default to empty lists.""" | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.FEMALE, | |
| anthropometrics=Anthropometrics(height_cm=165, weight_kg=70), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| ) | |
| assert user.family_history == [] | |
| assert user.clinical_tests.psa is None | |
| assert user.symptoms == [] | |
| def test_enum_completeness(self): | |
| """Test that all enums are covered.""" | |
| # Test that we can instantiate all enum values | |
| assert Sex.MALE == "male" | |
| assert Ethnicity.WHITE == "white" | |
| assert Country.UNITED_STATES == "us" | |
| def test_chronic_condition_enum_includes_anaemia_and_endometrial(self): | |
| """Test that new chronic conditions are in the enum.""" | |
| assert ChronicCondition.ANAEMIA == "anaemia" | |
| assert ChronicCondition.ENDOMETRIAL_POLYPS == "endometrial_polyps" | |
| assert SmokingStatus.CURRENT == "current" | |
| assert AlcoholConsumption.HEAVY == "heavy" | |
| assert PhysicalActivityLevel.HIGH == "high" | |
| assert CancerType.MELANOMA == "melanoma" | |
| assert GeneticMutation.BRCA1 == "brca1" | |
| assert ChronicCondition.COPD == "copd" | |
| assert FamilyRelation.MOTHER == "mother" | |
| assert RelationshipDegree.FIRST == "1" | |
| assert FamilySide.MATERNAL == "maternal" | |
| assert DREResult.NORMAL == "normal" | |
| assert HormoneUse.CURRENT == "current" | |
| assert USGeographicRegion.SOUTHERN == "southern" | |
| # Test str, Enum values (converted from IntEnum) | |
| assert ComplexionLevel.DARK == "dark" | |
| assert FrecklingIntensity.SEVERE == "severe" | |
| assert FemaleTanResponse.NONE == "none" | |
| assert MaleSmallMolesCategory.SEVENTEEN_OR_MORE == "seventeen_or_more" | |
| assert FemaleSmallMolesCategory.TWELVE_OR_MORE == "twelve_or_more" | |
| def test_boolean_fields(self): | |
| """Test boolean fields in dermatologic profile.""" | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=45, | |
| sex=Sex.MALE, | |
| anthropometrics=Anthropometrics(height_cm=175, weight_kg=80), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| alcohol_consumption=AlcoholConsumption.NONE, | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(), | |
| dermatologic=DermatologicProfile( | |
| region=USGeographicRegion.CENTRAL, | |
| complexion=ComplexionLevel.MEDIUM, | |
| freckling=FrecklingIntensity.MILD, | |
| male_sunburn=True, | |
| male_has_two_or_more_big_moles=False, | |
| male_small_moles=MaleSmallMolesCategory.SEVEN_TO_SIXTEEN, | |
| solar_damage=True, | |
| ), | |
| ) | |
| assert user.dermatologic.male_sunburn is True | |
| assert user.dermatologic.male_has_two_or_more_big_moles is False | |
| assert user.dermatologic.solar_damage is True | |