forked from enviPath/enviPy
This implements a version of #274, relying on Pydantics built in JSON schema and JSON rendering. Requires additional UI tagging in the ai model repo but will remove HTML tags. Example scenario with filled information: 5882df9c-dae1-4d80-a40e-db4724271456/scenario/3a4d395a-6a6d-4154-8ce3-ced667fceec0 Reviewed-on: enviPath/enviPy#282 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
115 lines
5.2 KiB
Python
115 lines
5.2 KiB
Python
"""
|
|
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}"
|
|
)
|