""" Property-based tests for schema generation. Tests that verify schema generation works correctly for all models, regardless of their structure. """ import pytest from typing import Type from pydantic import BaseModel from envipy_additional_information import registry, EnviPyModel from epapi.utils.schema_transformers import build_rjsf_output class TestSchemaGeneration: """Test that all models can generate valid RJSF schemas.""" @pytest.mark.parametrize("model_name,model_cls", list(registry.list_models().items())) def test_all_models_generate_rjsf(self, model_name: str, model_cls: Type[BaseModel]): """Every model in the registry should generate valid RJSF format.""" # Skip non-EnviPyModel classes (parsers, etc.) if not issubclass(model_cls, EnviPyModel): pytest.skip(f"{model_name} is not an EnviPyModel") # Should not raise exception result = build_rjsf_output(model_cls) # Verify structure assert isinstance(result, dict), f"{model_name}: Result should be a dict" assert "schema" in result, f"{model_name}: Missing 'schema' key" assert "uiSchema" in result, f"{model_name}: Missing 'uiSchema' key" assert "formData" in result, f"{model_name}: Missing 'formData' key" assert "groups" in result, f"{model_name}: Missing 'groups' key" # Verify types assert isinstance(result["schema"], dict), f"{model_name}: schema should be dict" assert isinstance(result["uiSchema"], dict), f"{model_name}: uiSchema should be dict" assert isinstance(result["formData"], dict), f"{model_name}: formData should be dict" assert isinstance(result["groups"], list), f"{model_name}: groups should be list" # Verify schema has properties assert "properties" in result["schema"], f"{model_name}: schema should have 'properties'" assert isinstance(result["schema"]["properties"], dict), ( f"{model_name}: properties should be dict" ) @pytest.mark.parametrize("model_name,model_cls", list(registry.list_models().items())) def test_ui_schema_matches_schema_fields(self, model_name: str, model_cls: Type[BaseModel]): """uiSchema keys should match schema properties (or be nested for intervals).""" if not issubclass(model_cls, EnviPyModel): pytest.skip(f"{model_name} is not an EnviPyModel") result = build_rjsf_output(model_cls) schema_props = set(result["schema"]["properties"].keys()) ui_schema_keys = set(result["uiSchema"].keys()) # uiSchema should have entries for all top-level properties # (intervals may have nested start/end, but the main field should be present) assert ui_schema_keys.issubset(schema_props), ( f"{model_name}: uiSchema has keys not in schema: {ui_schema_keys - schema_props}" ) @pytest.mark.parametrize("model_name,model_cls", list(registry.list_models().items())) def test_groups_is_list_of_strings(self, model_name: str, model_cls: Type[BaseModel]): """Groups should be a list of strings.""" if not issubclass(model_cls, EnviPyModel): pytest.skip(f"{model_name} is not an EnviPyModel") result = build_rjsf_output(model_cls) groups = result["groups"] assert isinstance(groups, list), f"{model_name}: groups should be list" assert all(isinstance(g, str) for g in groups), ( f"{model_name}: all groups should be strings, got {groups}" ) assert len(groups) > 0, f"{model_name}: should have at least one group" @pytest.mark.parametrize("model_name,model_cls", list(registry.list_models().items())) def test_form_data_matches_schema(self, model_name: str, model_cls: Type[BaseModel]): """formData keys should match schema properties.""" if not issubclass(model_cls, EnviPyModel): pytest.skip(f"{model_name} is not an EnviPyModel") result = build_rjsf_output(model_cls) schema_props = set(result["schema"]["properties"].keys()) form_data_keys = set(result["formData"].keys()) # formData should only contain keys that are in schema assert form_data_keys.issubset(schema_props), ( f"{model_name}: formData has keys not in schema: {form_data_keys - schema_props}" ) class TestWidgetTypes: """Test that widget types are valid.""" @pytest.mark.parametrize("model_name,model_cls", list(registry.list_models().items())) def test_widget_types_are_valid(self, model_name: str, model_cls: Type[BaseModel]): """All widget types in uiSchema should be valid WidgetType values.""" from envipy_additional_information.ui_config import WidgetType if not issubclass(model_cls, EnviPyModel): pytest.skip(f"{model_name} is not an EnviPyModel") result = build_rjsf_output(model_cls) valid_widgets = {wt.value for wt in WidgetType} for field_name, ui_config in result["uiSchema"].items(): widget = ui_config.get("ui:widget") if widget: assert widget in valid_widgets, ( f"{model_name}.{field_name}: Invalid widget '{widget}'. Valid: {valid_widgets}" )