diff --git a/supervisor/addons/validate.py b/supervisor/addons/validate.py index 03f2747bd..e022897b4 100644 --- a/supervisor/addons/validate.py +++ b/supervisor/addons/validate.py @@ -424,6 +424,11 @@ def _nested_validate_list(coresys, typ, data_list, key): """Validate nested items.""" options = [] + # Make sure it is a list + if not isinstance(data_list, list): + raise vol.Invalid(f"Invalid list for {key}") + + # Process list for element in data_list: # Nested? if isinstance(typ, dict): @@ -439,6 +444,11 @@ def _nested_validate_dict(coresys, typ, data_dict, key): """Validate nested items.""" options = {} + # Make sure it is a dict + if not isinstance(data_dict, dict): + raise vol.Invalid(f"Invalid dict for {key}") + + # Process dict for c_key, c_value in data_dict.items(): # Ignore unknown options / remove from list if c_key not in typ: diff --git a/tests/addons/test_options_schema.py b/tests/addons/test_options_schema.py new file mode 100644 index 000000000..f44983adc --- /dev/null +++ b/tests/addons/test_options_schema.py @@ -0,0 +1,65 @@ +"""Test add-ons schema to UI schema convertion.""" + +import pytest +import voluptuous as vol + +from supervisor.addons.validate import validate_options + + +def test_simple_schema(coresys): + """Test with simple schema.""" + assert validate_options( + coresys, + {"name": "str", "password": "password", "fires": "bool", "alias": "str?"}, + )({"name": "Pascal", "password": "1234", "fires": True, "alias": "test"}) + + assert validate_options( + coresys, + {"name": "str", "password": "password", "fires": "bool", "alias": "str?"}, + )({"name": "Pascal", "password": "1234", "fires": True}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, + {"name": "str", "password": "password", "fires": "bool", "alias": "str?"}, + )({"name": "Pascal", "password": "1234", "fires": "hah"}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, + {"name": "str", "password": "password", "fires": "bool", "alias": "str?"}, + )({"name": "Pascal", "fires": True}) + + +def test_complex_schema_list(coresys): + """Test with complex list schema.""" + assert validate_options( + coresys, {"name": "str", "password": "password", "extend": ["str"]}, + )({"name": "Pascal", "password": "1234", "extend": ["test", "blu"]}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, {"name": "str", "password": "password", "extend": ["str"]}, + )({"name": "Pascal", "password": "1234", "extend": ["test", 1]}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, {"name": "str", "password": "password", "extend": ["str"]}, + )({"name": "Pascal", "password": "1234", "extend": "test"}) + + +def test_complex_schema_dict(coresys): + """Test with complex dict schema.""" + assert validate_options( + coresys, {"name": "str", "password": "password", "extend": {"test": "int"}}, + )({"name": "Pascal", "password": "1234", "extend": {"test": 1}}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, {"name": "str", "password": "password", "extend": {"test": "int"}}, + )({"name": "Pascal", "password": "1234", "extend": {"wrong": 1}}) + + with pytest.raises(vol.error.Invalid): + validate_options( + coresys, {"name": "str", "password": "password", "extend": ["str"]}, + )({"name": "Pascal", "password": "1234", "extend": "test"})