diff --git a/test/validation_test.py b/test/validation_test.py index 8dae133..e6c31c7 100644 --- a/test/validation_test.py +++ b/test/validation_test.py @@ -219,5 +219,31 @@ def test_validation_timeseries(): schema_type="plant/energy_resource", ) +def test_restrictive_rejects_additional_properties_in_ref_schemas(): + """Test that restrictive validation catches additional properties in $ref-resolved schemas. + + The site schema is loaded via $ref from wind_energy_system.yaml. + Without the fix to propagate enforcement through the registry, + extra properties in $ref-resolved schemas would pass silently. + """ + plant_reference_path = Path(windIO.plant_ex.__file__).parent + data = windIO.load_yaml( + plant_reference_path / "wind_energy_system/IEA37_case_study_1_2_wind_energy_system.yaml" + ) + + # Inject an extra property into site, which is resolved via $ref + data["site"]["not_in_schema"] = "should be rejected" + + # restrictive=True (default) must reject it + with pytest.raises( + jsonschema.exceptions.ValidationError, + match="Additional properties are not allowed.*'not_in_schema'", + ): + windIO.validate(data, "plant/wind_energy_system", restrictive=True) + + # restrictive=False must allow it + windIO.validate(data, "plant/wind_energy_system", restrictive=False) + + if __name__ == "__main__": test_validate_raise() \ No newline at end of file diff --git a/windIO/validator.py b/windIO/validator.py index eec6b95..746067d 100644 --- a/windIO/validator.py +++ b/windIO/validator.py @@ -11,16 +11,20 @@ from .schemas import schemaPath, schema_validation_error_formatter -def retrieve_yaml(uri: str): - if not uri.endswith(".yaml"): - raise NoSuchResource(ref=uri) - uri = uri.removeprefix("windIO/") - path = schemaPath / Path(uri) - contents = load_yaml(path) - return Resource.from_contents(contents) +def _make_retrieve(restrictive=False): + def retrieve(uri: str): + if not uri.endswith(".yaml"): + raise NoSuchResource(ref=uri) + uri = uri.removeprefix("windIO/") + path = schemaPath / Path(uri) + contents = load_yaml(path) + if restrictive: + _enforce_no_additional_properties(contents) + return Resource.from_contents(contents) + return retrieve -registry = Registry(retrieve=retrieve_yaml) +registry = Registry(retrieve=_make_retrieve()) def _enforce_no_additional_properties(schema): @@ -93,11 +97,14 @@ def validate( schema = load_yaml(schema_file) if restrictive: schema = _enforce_no_additional_properties(schema) + reg = Registry(retrieve=_make_retrieve(restrictive=True)) + else: + reg = registry if defaults: - _jsonschema_validate_modified(data, schema, cls = DefaultValidatingDraft7Validator, registry=registry) + _jsonschema_validate_modified(data, schema, cls = DefaultValidatingDraft7Validator, registry=reg) else: - _jsonschema_validate_modified(data, schema, registry=registry) + _jsonschema_validate_modified(data, schema, registry=reg) return data