Spaces:
Runtime error
Runtime error
| """Tests for the documentation generation script.""" | |
| import pytest | |
| from scripts.generate_documentation import ( | |
| _normalise_cancer_label, | |
| _unique_qcancer_sites, | |
| build_field_usage_map, | |
| cancer_types_for_model, | |
| discover_risk_models, | |
| extract_field_attributes, | |
| extract_model_requirements, | |
| format_field_path, | |
| gather_spec_details, | |
| group_fields_by_requirements, | |
| prettify_field_name, | |
| traverse_user_input_structure, | |
| ) | |
| class TestUtilityFunctions: | |
| """Test utility functions for documentation generation.""" | |
| def test_prettify_field_name(self): | |
| """Test field name prettification.""" | |
| assert prettify_field_name("female_specific") == "Female Specific" | |
| assert prettify_field_name("family_history[]") == "Family History" | |
| assert prettify_field_name("age_years") == "Age Years" | |
| assert prettify_field_name("test") == "Test" | |
| def test_format_field_path(self): | |
| """Test field path formatting.""" | |
| assert ( | |
| format_field_path("demographics.age_years") == "Demographics\n - Age Years" | |
| ) | |
| assert ( | |
| format_field_path("family_history[].relation") | |
| == "Family History\n - Relation" | |
| ) | |
| assert format_field_path("simple_field") == "Simple Field" | |
| def test_normalise_cancer_label(self): | |
| """Test cancer label normalization.""" | |
| assert _normalise_cancer_label("Lung Cancer") == "Lung" | |
| assert _normalise_cancer_label("breast-cancer") == "Breast" | |
| assert _normalise_cancer_label("colorectal_cancer") == "Colorectal" | |
| assert _normalise_cancer_label("Prostate") == "Prostate" | |
| def test_unique_qcancer_sites(self): | |
| """Test QCancer sites extraction.""" | |
| sites = _unique_qcancer_sites() | |
| assert isinstance(sites, list) | |
| assert len(sites) > 0 | |
| # Check that sites are normalized | |
| for site in sites: | |
| assert "cancer" not in site.lower() | |
| assert "_" not in site | |
| assert "-" not in site | |
| def test_cancer_types_for_model(self): | |
| """Test cancer type extraction for models.""" | |
| # Mock a risk model | |
| class MockModel: | |
| """Mock risk model for testing.""" | |
| def __init__(self, name, cancer_type): | |
| """Initialize mock model. | |
| Args: | |
| name: Model name. | |
| cancer_type: Cancer type string. | |
| """ | |
| self.name = name | |
| self._cancer_type = cancer_type | |
| def cancer_type(self): | |
| """Return cancer type. | |
| Returns: | |
| str: Cancer type string. | |
| """ | |
| return self._cancer_type | |
| # Test regular model | |
| model = MockModel("gail", "breast") | |
| types = cancer_types_for_model(model) | |
| assert types == ["Breast"] | |
| # Test QCancer model | |
| qcancer_model = MockModel("qcancer", "multiple") | |
| qcancer_types = cancer_types_for_model(qcancer_model) | |
| assert isinstance(qcancer_types, list) | |
| assert len(qcancer_types) > 0 | |
| def test_group_fields_by_requirements(self): | |
| """Test field grouping by requirements.""" | |
| # Mock requirements data | |
| requirements = [ | |
| ("demographics.age_years", int, True), | |
| ("demographics.sex", str, True), | |
| ("family_history.relation", str, False), | |
| ("family_history.cancer_type", str, False), | |
| ] | |
| grouped = group_fields_by_requirements(requirements) | |
| assert len(grouped) == 2 | |
| # Check demographics group | |
| dem_group = next((g for g in grouped if g[0] == "Demographics"), None) | |
| assert dem_group is not None | |
| assert len(dem_group[1]) == 2 | |
| # Check family history group | |
| fh_group = next((g for g in grouped if g[0] == "Family History"), None) | |
| assert fh_group is not None | |
| assert len(fh_group[1]) == 2 | |
| def test_gather_spec_details_regular(self): | |
| """Test spec details gathering for regular fields.""" | |
| note = "Test note" | |
| note_text, required_text, unit_text, range_text = gather_spec_details( | |
| None, None, note | |
| ) | |
| assert note_text == "Test note" | |
| assert required_text == "Optional" | |
| assert unit_text == "-" | |
| assert range_text == "-" | |
| def test_gather_spec_details_clinical_observation(self): | |
| """Test spec details gathering for clinical observations.""" | |
| note = "multivitamin - Yes/No" | |
| note_text, required_text, unit_text, range_text = gather_spec_details( | |
| None, None, note | |
| ) | |
| assert "Multivitamin usage status" in note_text | |
| assert required_text == "Optional" | |
| assert unit_text == "-" | |
| assert range_text == "Yes/No" | |
| def test_gather_spec_details_unknown_observation(self): | |
| """Test spec details gathering for unknown clinical observations.""" | |
| note = "unknown_obs - Some values" | |
| note_text, required_text, unit_text, range_text = gather_spec_details( | |
| None, None, note | |
| ) | |
| assert "Clinical observation: unknown_obs" in note_text | |
| assert required_text == "Optional" | |
| assert unit_text == "-" | |
| assert range_text == "Some values" | |
| class TestMainFunctionality: | |
| """Test main functionality of the documentation generator.""" | |
| def test_discover_risk_models(self): | |
| """Test risk model discovery.""" | |
| models = discover_risk_models() | |
| assert isinstance(models, list) | |
| assert len(models) > 0 | |
| # Check that all models have required attributes | |
| for model in models: | |
| assert hasattr(model, "name") | |
| assert hasattr(model, "cancer_type") | |
| assert hasattr(model, "description") | |
| assert hasattr(model, "interpretation") | |
| assert hasattr(model, "references") | |
| def test_main_function_import(self): | |
| """Test that the main function can be imported without errors.""" | |
| from scripts.generate_documentation import main | |
| assert callable(main) | |
| class TestEdgeCases: | |
| """Test edge cases and error handling.""" | |
| def test_empty_field_grouping(self): | |
| """Test field grouping with empty input.""" | |
| grouped = group_fields_by_requirements([]) | |
| assert grouped == [] | |
| def test_single_segment_path(self): | |
| """Test field path formatting with single segment.""" | |
| result = format_field_path("single_field") | |
| assert result == "Single Field" | |
| def test_empty_cancer_label(self): | |
| """Test cancer label normalization with empty input.""" | |
| result = _normalise_cancer_label("") | |
| assert result == "" | |
| def test_none_cancer_label(self): | |
| """Test cancer label normalization with None input.""" | |
| # The function should handle None input gracefully | |
| with pytest.raises(AttributeError): | |
| _normalise_cancer_label(None) | |
| def test_gather_spec_details_none_inputs(self): | |
| """Test spec details gathering with None inputs.""" | |
| note_text, required_text, unit_text, range_text = gather_spec_details( | |
| None, None, "" | |
| ) | |
| assert note_text == "-" | |
| assert required_text == "Optional" | |
| assert unit_text == "-" | |
| assert range_text == "-" | |
| def test_gather_spec_details_empty_note(self): | |
| """Test spec details gathering with empty note.""" | |
| note_text, required_text, unit_text, range_text = gather_spec_details( | |
| None, None, "" | |
| ) | |
| assert note_text == "-" | |
| assert required_text == "Optional" | |
| assert unit_text == "-" | |
| assert range_text == "-" | |
| class TestUserInputStructureExtraction: | |
| """Test functions for extracting and processing UserInput structure.""" | |
| def test_traverse_user_input_structure(self): | |
| """Test UserInput structure traversal.""" | |
| from sentinel.user_input import UserInput | |
| structure = traverse_user_input_structure(UserInput) | |
| assert isinstance(structure, list) | |
| assert len(structure) > 0 | |
| # Check that we have both parent models and leaf fields | |
| parent_models = [item for item in structure if item[2] is not None] | |
| leaf_fields = [item for item in structure if item[2] is None] | |
| assert len(parent_models) > 0 | |
| assert len(leaf_fields) > 0 | |
| # Check structure format: (path, name, model_class) | |
| for path, name, model_class in structure: | |
| assert isinstance(path, str) | |
| assert isinstance(name, str) | |
| assert model_class is None or hasattr(model_class, "model_fields") | |
| def test_extract_model_requirements(self): | |
| """Test model requirements extraction.""" | |
| from sentinel.risk_models.gail import GailRiskModel | |
| model = GailRiskModel() | |
| requirements = extract_model_requirements(model) | |
| assert isinstance(requirements, list) | |
| assert len(requirements) > 0 | |
| # Check format: (field_path, field_type, is_required) | |
| for field_path, field_type, is_required in requirements: | |
| assert isinstance(field_path, str) | |
| # field_type can be Annotated types, so we check it's not None | |
| assert field_type is not None | |
| assert isinstance(is_required, bool) | |
| def test_build_field_usage_map(self): | |
| """Test field usage mapping.""" | |
| from sentinel.risk_models.claus import ClausRiskModel | |
| from sentinel.risk_models.gail import GailRiskModel | |
| models = [GailRiskModel(), ClausRiskModel()] | |
| usage_map = build_field_usage_map(models) | |
| assert isinstance(usage_map, dict) | |
| assert len(usage_map) > 0 | |
| # Check format: field_path -> [(model_name, is_required), ...] | |
| for field_path, usage_list in usage_map.items(): | |
| assert isinstance(field_path, str) | |
| assert isinstance(usage_list, list) | |
| for model_name, is_required in usage_list: | |
| assert isinstance(model_name, str) | |
| assert isinstance(is_required, bool) | |
| def test_extract_field_attributes(self): | |
| """Test field attributes extraction.""" | |
| from sentinel.user_input import UserInput | |
| # Get a field from UserInput | |
| field_info = UserInput.model_fields["demographics"] | |
| field_type = field_info.annotation | |
| description, examples, constraints, used_by, enum_class = ( | |
| extract_field_attributes(field_info, field_type) | |
| ) | |
| assert isinstance(description, str) | |
| assert isinstance(examples, str) | |
| assert isinstance(constraints, str) | |
| assert isinstance(used_by, str) | |
| assert enum_class is None or isinstance(enum_class, type) | |