1
mirror of https://github.com/home-assistant/core synced 2024-09-15 17:29:45 +02:00
ha-core/tests/helpers/test_selector.py
Erik Montnemery 84a812ad05
Allow setting number selector step size to 'any' (#78265)
* Allow setting number selector step size to 'any'

* Improve test coverage
2022-09-15 08:29:46 +02:00

712 lines
20 KiB
Python

"""Test selectors."""
from enum import Enum
import pytest
import voluptuous as vol
from homeassistant.helpers import selector
from homeassistant.util import yaml
FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411"
@pytest.mark.parametrize(
"schema",
(
{"device": None},
{"entity": None},
),
)
def test_valid_base_schema(schema):
"""Test base schema validation."""
selector.validate_selector(schema)
@pytest.mark.parametrize(
"schema",
(
None,
"not_a_dict",
{},
{"non_existing": {}},
# Two keys
{"device": {}, "entity": {}},
),
)
def test_invalid_base_schema(schema):
"""Test base schema validation."""
with pytest.raises(vol.Invalid):
selector.validate_selector(schema)
def _test_selector(
selector_type, schema, valid_selections, invalid_selections, converter=None
):
"""Help test a selector."""
def default_converter(x):
return x
if converter is None:
converter = default_converter
# Validate selector configuration
config = {selector_type: schema}
selector.validate_selector(config)
selector_instance = selector.selector(config)
# We do not allow enums in the config, as they cannot serialize
assert not any(isinstance(val, Enum) for val in selector_instance.config.values())
# Use selector in schema and validate
vol_schema = vol.Schema({"selection": selector_instance})
for selection in valid_selections:
assert vol_schema({"selection": selection}) == {
"selection": converter(selection)
}
for selection in invalid_selections:
with pytest.raises(vol.Invalid):
vol_schema({"selection": selection})
# Serialize selector
selector_instance = selector.selector({selector_type: schema})
assert (
selector.selector(selector_instance.serialize()["selector"]).config
== selector_instance.config
)
# Test serialized selector can be dumped to YAML
yaml.dump(selector_instance.serialize())
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(None, ("abc123",), (None,)),
({}, ("abc123",), (None,)),
({"integration": "zha"}, ("abc123",), (None,)),
({"manufacturer": "mock-manuf"}, ("abc123",), (None,)),
({"model": "mock-model"}, ("abc123",), (None,)),
({"manufacturer": "mock-manuf", "model": "mock-model"}, ("abc123",), (None,)),
(
{"integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model"},
("abc123",),
(None,),
),
({"entity": {"device_class": "motion"}}, ("abc123",), (None,)),
(
{
"integration": "zha",
"manufacturer": "mock-manuf",
"model": "mock-model",
"entity": {"domain": "binary_sensor", "device_class": "motion"},
},
("abc123",),
(None,),
),
(
{"multiple": True},
(["abc123", "def456"],),
("abc123", None, ["abc123", None]),
),
),
)
def test_device_selector_schema(schema, valid_selections, invalid_selections):
"""Test device selector."""
_test_selector("device", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
({}, ("sensor.abc123", FAKE_UUID), (None, "abc123")),
({"integration": "zha"}, ("sensor.abc123", FAKE_UUID), (None, "abc123")),
({"domain": "light"}, ("light.abc123", FAKE_UUID), (None, "sensor.abc123")),
(
{"domain": ["light", "sensor"]},
("light.abc123", "sensor.abc123", FAKE_UUID),
(None, "dog.abc123"),
),
({"device_class": "motion"}, ("sensor.abc123", FAKE_UUID), (None, "abc123")),
(
{"integration": "zha", "domain": "light"},
("light.abc123", FAKE_UUID),
(None, "sensor.abc123"),
),
(
{"integration": "zha", "domain": "binary_sensor", "device_class": "motion"},
("binary_sensor.abc123", FAKE_UUID),
(None, "sensor.abc123"),
),
(
{"multiple": True, "domain": "sensor"},
(["sensor.abc123", "sensor.def456"], ["sensor.abc123", FAKE_UUID]),
(
"sensor.abc123",
FAKE_UUID,
None,
"abc123",
["sensor.abc123", "light.def456"],
),
),
(
{
"include_entities": ["sensor.abc123", "sensor.def456", "sensor.ghi789"],
"exclude_entities": ["sensor.ghi789", "sensor.jkl123"],
},
("sensor.abc123", FAKE_UUID),
("sensor.ghi789", "sensor.jkl123"),
),
(
{
"multiple": True,
"include_entities": ["sensor.abc123", "sensor.def456", "sensor.ghi789"],
"exclude_entities": ["sensor.ghi789", "sensor.jkl123"],
},
(["sensor.abc123", "sensor.def456"], ["sensor.abc123", FAKE_UUID]),
(
["sensor.abc123", "sensor.jkl123"],
["sensor.abc123", "sensor.ghi789"],
),
),
),
)
def test_entity_selector_schema(schema, valid_selections, invalid_selections):
"""Test entity selector."""
_test_selector("entity", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
({}, ("abc123",), (None,)),
({"entity": {}}, ("abc123",), (None,)),
({"entity": {"domain": "light"}}, ("abc123",), (None,)),
(
{"entity": {"domain": "binary_sensor", "device_class": "motion"}},
("abc123",),
(None,),
),
(
{
"entity": {
"domain": "binary_sensor",
"device_class": "motion",
"integration": "demo",
}
},
("abc123",),
(None,),
),
(
{"device": {"integration": "demo", "model": "mock-model"}},
("abc123",),
(None,),
),
(
{
"entity": {"domain": "binary_sensor", "device_class": "motion"},
"device": {"integration": "demo", "model": "mock-model"},
},
("abc123",),
(None,),
),
(
{"multiple": True},
((["abc123", "def456"],)),
(None, "abc123", ["abc123", None]),
),
),
)
def test_area_selector_schema(schema, valid_selections, invalid_selections):
"""Test area selector."""
_test_selector("area", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{"min": 10, "max": 50},
(
10,
50,
),
(9, 51),
),
({"min": -100, "max": 100, "step": 5}, (), ()),
({"min": -20, "max": -10, "mode": "box"}, (), ()),
(
{"min": 0, "max": 100, "unit_of_measurement": "seconds", "mode": "slider"},
(),
(),
),
({"min": 10, "max": 1000, "mode": "slider", "step": 0.5}, (), ()),
({"mode": "box"}, (10,), ()),
({"mode": "box", "step": "any"}, (), ()),
),
)
def test_number_selector_schema(schema, valid_selections, invalid_selections):
"""Test number selector."""
_test_selector("number", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema",
(
{}, # Must have mandatory fields
{"mode": "slider"}, # Must have min+max in slider mode
{
"mode": "slider",
"min": 0,
"max": 1,
"step": "any", # Can't combine slider with step any
},
),
)
def test_number_selector_schema_error(schema):
"""Test number selector."""
with pytest.raises(vol.Invalid):
selector.validate_selector({"number": schema})
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, ("abc123",), (None,)),),
)
def test_addon_selector_schema(schema, valid_selections, invalid_selections):
"""Test add-on selector."""
_test_selector("addon", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, (1, "one", None), ()),), # Everything can be coarced to bool
)
def test_boolean_selector_schema(schema, valid_selections, invalid_selections):
"""Test boolean selector."""
_test_selector(
"boolean",
schema,
valid_selections,
invalid_selections,
bool,
)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
("6b68b250388cbe0d620c92dd3acc93ec", "76f2e8f9a6491a1b580b3a8967c27ddd"),
(None, True, 1),
),
(
{"integration": "adguard"},
("6b68b250388cbe0d620c92dd3acc93ec", "76f2e8f9a6491a1b580b3a8967c27ddd"),
(None, True, 1),
),
),
)
def test_config_entry_selector_schema(schema, valid_selections, invalid_selections):
"""Test boolean selector."""
_test_selector("config_entry", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, ("00:00:00",), ("blah", None)),),
)
def test_time_selector_schema(schema, valid_selections, invalid_selections):
"""Test time selector."""
_test_selector("time", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{"entity_id": "sensor.abc"},
("on", "armed"),
(None, True, 1),
),
),
)
def test_state_selector_schema(schema, valid_selections, invalid_selections):
"""Test state selector."""
_test_selector("state", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
({}, ({"entity_id": ["sensor.abc123"]},), ("abc123", None)),
({"entity": {}}, (), ()),
({"entity": {"domain": "light"}}, (), ()),
({"entity": {"domain": "binary_sensor", "device_class": "motion"}}, (), ()),
(
{
"entity": {
"domain": "binary_sensor",
"device_class": "motion",
"integration": "demo",
}
},
(),
(),
),
({"device": {"integration": "demo", "model": "mock-model"}}, (), ()),
(
{
"entity": {"domain": "binary_sensor", "device_class": "motion"},
"device": {"integration": "demo", "model": "mock-model"},
},
(),
(),
),
),
)
def test_target_selector_schema(schema, valid_selections, invalid_selections):
"""Test target selector."""
_test_selector("target", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, ("abc123",), ()),),
)
def test_action_selector_schema(schema, valid_selections, invalid_selections):
"""Test action sequence selector."""
_test_selector("action", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, ("abc123",), ()),),
)
def test_object_selector_schema(schema, valid_selections, invalid_selections):
"""Test object selector."""
_test_selector("object", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
({}, ("abc123",), (None,)),
({"multiline": True}, (), ()),
({"multiline": False, "type": "email"}, (), ()),
),
)
def test_text_selector_schema(schema, valid_selections, invalid_selections):
"""Test text selector."""
_test_selector("text", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{"options": ["red", "green", "blue"]},
("red", "green", "blue"),
("cat", 0, None, ["red"]),
),
(
{
"options": [
{"value": "red", "label": "Ruby Red"},
{"value": "green", "label": "Emerald Green"},
]
},
("red", "green"),
("cat", 0, None, ["red"]),
),
(
{"options": ["red", "green", "blue"], "multiple": True},
(["red"], ["green", "blue"], []),
("cat", 0, None, "red"),
),
(
{
"options": ["red", "green", "blue"],
"multiple": True,
"custom_value": True,
},
(["red"], ["green", "blue"], ["red", "cat"], []),
("cat", 0, None, "red"),
),
(
{"options": ["red", "green", "blue"], "custom_value": True},
("red", "green", "blue", "cat"),
(0, None, ["red"]),
),
(
{"options": [], "custom_value": True},
("red", "cat"),
(0, None, ["red"]),
),
(
{"options": [], "custom_value": True, "multiple": True, "mode": "list"},
(["red"], ["green", "blue"], []),
(0, None, "red"),
),
),
)
def test_select_selector_schema(schema, valid_selections, invalid_selections):
"""Test select selector."""
_test_selector("select", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema",
(
{}, # Must have options
{"options": {"hello": "World"}}, # Options must be a list
# Options must be strings or value / label pairs
{"options": [{"hello": "World"}]},
# Options must all be of the same type
{"options": ["red", {"value": "green", "label": "Emerald Green"}]},
),
)
def test_select_selector_schema_error(schema):
"""Test select selector."""
with pytest.raises(vol.Invalid):
selector.validate_selector({"select": schema})
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{"entity_id": "sensor.abc"},
("friendly_name", "device_class"),
(None,),
),
(
{"entity_id": "sensor.abc", "hide_attributes": ["friendly_name"]},
("device_class", "state_class"),
(None,),
),
),
)
def test_attribute_selector_schema(schema, valid_selections, invalid_selections):
"""Test attribute selector."""
_test_selector("attribute", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
(
{"seconds": 10},
{"days": 10}, # Days is allowed also if `enable_day` is not set
),
(None, {}),
),
(
{"enable_day": True},
({"seconds": 10}, {"days": 10}),
(None, {}),
),
),
)
def test_duration_selector_schema(schema, valid_selections, invalid_selections):
"""Test duration selector."""
_test_selector("duration", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
("mdi:abc",),
(None,),
),
),
)
def test_icon_selector_schema(schema, valid_selections, invalid_selections):
"""Test icon selector."""
_test_selector("icon", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
("abc",),
(None,),
),
),
)
def test_theme_selector_schema(schema, valid_selections, invalid_selections):
"""Test theme selector."""
_test_selector("theme", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
(
{
"entity_id": "sensor.abc",
"media_content_id": "abc",
"media_content_type": "def",
},
{
"entity_id": "sensor.abc",
"media_content_id": "abc",
"media_content_type": "def",
"metadata": {},
},
),
(None, "abc", {}),
),
),
)
def test_media_selector_schema(schema, valid_selections, invalid_selections):
"""Test media selector."""
def drop_metadata(data):
"""Drop metadata key from the input."""
data.pop("metadata", None)
return data
_test_selector(
"media",
schema,
valid_selections,
invalid_selections,
drop_metadata,
)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
(
{
"latitude": 1.0,
"longitude": 2.0,
},
{
"latitude": 1.0,
"longitude": 2.0,
"radius": 3.0,
},
),
(
None,
"abc",
{},
{"latitude": 1.0},
{"longitude": 1.0},
{"latitude": 1.0, "longitude": "1.0"},
),
),
),
)
def test_location_selector_schema(schema, valid_selections, invalid_selections):
"""Test location selector."""
_test_selector("location", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
([0, 0, 0], [255, 255, 255], [0.0, 0.0, 0.0], [255.0, 255.0, 255.0]),
(None, "abc", [0, 0, "nil"], (255, 255, 255)),
),
),
)
def test_rgb_color_selector_schema(schema, valid_selections, invalid_selections):
"""Test color_rgb selector."""
_test_selector("color_rgb", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
(100, 100.0),
(None, "abc", [100]),
),
(
{"min_mireds": 100, "max_mireds": 200},
(100, 200),
(99, 201),
),
),
)
def test_color_tempselector_schema(schema, valid_selections, invalid_selections):
"""Test color_temp selector."""
_test_selector("color_temp", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
("2022-03-24",),
(None, "abc", "00:00", "2022-03-24 00:00", "2022-03-32"),
),
),
)
def test_date_selector_schema(schema, valid_selections, invalid_selections):
"""Test date selector."""
_test_selector("date", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{},
("2022-03-24 00:00", "2022-03-24"),
(None, "abc", "00:00", "2022-03-24 24:01"),
),
),
)
def test_datetime_selector_schema(schema, valid_selections, invalid_selections):
"""Test datetime selector."""
_test_selector("datetime", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(({}, ("abc123", "{{ now() }}"), (None, "{{ incomplete }", "{% if True %}Hi!")),),
)
def test_template_selector_schema(schema, valid_selections, invalid_selections):
"""Test template selector."""
_test_selector("template", schema, valid_selections, invalid_selections)
@pytest.mark.parametrize(
"schema,valid_selections,invalid_selections",
(
(
{"accept": "image/*"},
("0182a1b99dbc5ae24aecd90c346605fa",),
(None, "not-a-uuid", "abcd", 1),
),
),
)
def test_file_selector_schema(schema, valid_selections, invalid_selections):
"""Test file selector."""
_test_selector("file", schema, valid_selections, invalid_selections)