1553 lines
46 KiB
Python
1553 lines
46 KiB
Python
"""Test the condition helper."""
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.exceptions import ConditionError, HomeAssistantError
|
|
from homeassistant.helpers import condition
|
|
from homeassistant.helpers.template import Template
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import dt
|
|
|
|
|
|
def assert_element(trace_element, expected_element, path):
|
|
"""Assert a trace element is as expected.
|
|
|
|
Note: Unused variable path is passed to get helpful errors from pytest.
|
|
"""
|
|
for result_key, result in expected_element.get("result", {}).items():
|
|
assert trace_element._result[result_key] == result
|
|
if "error_type" in expected_element:
|
|
assert isinstance(trace_element._error, expected_element["error_type"])
|
|
else:
|
|
assert trace_element._error is None
|
|
|
|
|
|
def assert_condition_trace(expected):
|
|
"""Assert a trace condition sequence is as expected."""
|
|
condition_trace = condition.condition_trace_get()
|
|
condition.condition_trace_clear()
|
|
expected_trace_keys = list(expected.keys())
|
|
assert list(condition_trace.keys()) == expected_trace_keys
|
|
for trace_key_index, key in enumerate(expected_trace_keys):
|
|
assert len(condition_trace[key]) == len(expected[key])
|
|
for index, element in enumerate(expected[key]):
|
|
path = f"[{trace_key_index}][{index}]"
|
|
assert_element(condition_trace[key][index], element, path)
|
|
|
|
|
|
async def test_invalid_condition(hass):
|
|
"""Test if invalid condition raises."""
|
|
with pytest.raises(HomeAssistantError):
|
|
await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "invalid",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
|
|
async def test_and_condition(hass):
|
|
"""Test the 'and' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "And Condition",
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 120)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 105)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"result": {"result": True}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": True}}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_and_condition_raises(hass):
|
|
"""Test the 'and' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "And Condition",
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature2",
|
|
"above": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
# All subconditions raise, the AND-condition should raise
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns True, the AND-condition
|
|
# should raise
|
|
hass.states.async_set("sensor.temperature2", 120)
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns False, the AND-condition
|
|
# should return False
|
|
hass.states.async_set("sensor.temperature2", 90)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_and_condition_with_template(hass):
|
|
"""Test the 'and' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"alias": "Template Condition",
|
|
"condition": "template",
|
|
"value_template": '{{ states.sensor.temperature.state == "100" }}',
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 120)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 105)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert test(hass)
|
|
|
|
|
|
async def test_or_condition(hass):
|
|
"""Test the 'or' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "Or Condition",
|
|
"condition": "or",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 120)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 105)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"result": {"result": True}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_or_condition_raises(hass):
|
|
"""Test the 'or' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "Or Condition",
|
|
"condition": "or",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature2",
|
|
"above": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
# All subconditions raise, the OR-condition should raise
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns False, the OR-condition
|
|
# should raise
|
|
hass.states.async_set("sensor.temperature2", 100)
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns True, the OR-condition
|
|
# should return True
|
|
hass.states.async_set("sensor.temperature2", 120)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_or_condition_with_template(hass):
|
|
"""Test the 'or' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "or",
|
|
"conditions": [
|
|
{'{{ states.sensor.temperature.state == "100" }}'},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 120)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 105)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert test(hass)
|
|
|
|
|
|
async def test_not_condition(hass):
|
|
"""Test the 'not' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "Not Condition",
|
|
"condition": "not",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 50,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 101)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
assert test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": True}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 49)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"result": {"result": False}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": False}}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"result": {"result": True}}],
|
|
"conditions/0/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_not_condition_raises(hass):
|
|
"""Test the 'and' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"alias": "Not Condition",
|
|
"condition": "not",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature2",
|
|
"below": 50,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
# All subconditions raise, the NOT-condition should raise
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"error_type": ConditionError}],
|
|
"conditions/1/entity_id/0": [{"error_type": ConditionError}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns False, the NOT-condition
|
|
# should raise
|
|
hass.states.async_set("sensor.temperature2", 90)
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"error_type": ConditionError}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": False}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": False}}],
|
|
}
|
|
)
|
|
|
|
# The first subconditions raises, the second returns True, the NOT-condition
|
|
# should return False
|
|
hass.states.async_set("sensor.temperature2", 40)
|
|
assert not test(hass)
|
|
assert_condition_trace(
|
|
{
|
|
"": [{"result": {"result": False}}],
|
|
"conditions/0": [{"error_type": ConditionError}],
|
|
"conditions/0/entity_id/0": [{"error_type": ConditionError}],
|
|
"conditions/1": [{"result": {"result": True}}],
|
|
"conditions/1/entity_id/0": [{"result": {"result": True}}],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_not_condition_with_template(hass):
|
|
"""Test the 'or' condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "not",
|
|
"conditions": [
|
|
{
|
|
"condition": "template",
|
|
"value_template": '{{ states.sensor.temperature.state == "100" }}',
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": 50,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 101)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 49)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_time_window(hass):
|
|
"""Test time condition windows."""
|
|
sixam = "06:00:00"
|
|
sixpm = "18:00:00"
|
|
|
|
test1 = await condition.async_from_config(
|
|
hass,
|
|
{"alias": "Time Cond", "condition": "time", "after": sixam, "before": sixpm},
|
|
)
|
|
test2 = await condition.async_from_config(
|
|
hass,
|
|
{"alias": "Time Cond", "condition": "time", "after": sixpm, "before": sixam},
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=3),
|
|
):
|
|
assert not test1(hass)
|
|
assert test2(hass)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=9),
|
|
):
|
|
assert test1(hass)
|
|
assert not test2(hass)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=15),
|
|
):
|
|
assert test1(hass)
|
|
assert not test2(hass)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=21),
|
|
):
|
|
assert not test1(hass)
|
|
assert test2(hass)
|
|
|
|
|
|
async def test_time_using_input_datetime(hass):
|
|
"""Test time conditions using input_datetime entities."""
|
|
await async_setup_component(
|
|
hass,
|
|
"input_datetime",
|
|
{
|
|
"input_datetime": {
|
|
"am": {"has_date": True, "has_time": True},
|
|
"pm": {"has_date": True, "has_time": True},
|
|
}
|
|
},
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
"input_datetime",
|
|
"set_datetime",
|
|
{
|
|
"entity_id": "input_datetime.am",
|
|
"datetime": str(
|
|
dt.now()
|
|
.replace(hour=6, minute=0, second=0, microsecond=0)
|
|
.replace(tzinfo=None)
|
|
),
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
"input_datetime",
|
|
"set_datetime",
|
|
{
|
|
"entity_id": "input_datetime.pm",
|
|
"datetime": str(
|
|
dt.now()
|
|
.replace(hour=18, minute=0, second=0, microsecond=0)
|
|
.replace(tzinfo=None)
|
|
),
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=3),
|
|
):
|
|
assert not condition.time(
|
|
hass, after="input_datetime.am", before="input_datetime.pm"
|
|
)
|
|
assert condition.time(
|
|
hass, after="input_datetime.pm", before="input_datetime.am"
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=9),
|
|
):
|
|
assert condition.time(
|
|
hass, after="input_datetime.am", before="input_datetime.pm"
|
|
)
|
|
assert not condition.time(
|
|
hass, after="input_datetime.pm", before="input_datetime.am"
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=15),
|
|
):
|
|
assert condition.time(
|
|
hass, after="input_datetime.am", before="input_datetime.pm"
|
|
)
|
|
assert not condition.time(
|
|
hass, after="input_datetime.pm", before="input_datetime.am"
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.helpers.condition.dt_util.now",
|
|
return_value=dt.now().replace(hour=21),
|
|
):
|
|
assert not condition.time(
|
|
hass, after="input_datetime.am", before="input_datetime.pm"
|
|
)
|
|
assert condition.time(
|
|
hass, after="input_datetime.pm", before="input_datetime.am"
|
|
)
|
|
|
|
with pytest.raises(ConditionError):
|
|
condition.time(hass, after="input_datetime.not_existing")
|
|
|
|
with pytest.raises(ConditionError):
|
|
condition.time(hass, before="input_datetime.not_existing")
|
|
|
|
|
|
async def test_state_raises(hass):
|
|
"""Test that state raises ConditionError on errors."""
|
|
# No entity
|
|
with pytest.raises(ConditionError, match="no entity"):
|
|
condition.state(hass, entity=None, req_state="missing")
|
|
|
|
# Unknown entities
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "state",
|
|
"entity_id": ["sensor.door_unknown", "sensor.window_unknown"],
|
|
"state": "open",
|
|
},
|
|
)
|
|
with pytest.raises(ConditionError, match="unknown entity.*door"):
|
|
test(hass)
|
|
with pytest.raises(ConditionError, match="unknown entity.*window"):
|
|
test(hass)
|
|
|
|
# Unknown attribute
|
|
with pytest.raises(ConditionError, match=r"attribute .* does not exist"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.door",
|
|
"attribute": "model",
|
|
"state": "acme",
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.door", "open")
|
|
test(hass)
|
|
|
|
# Unknown state entity
|
|
with pytest.raises(ConditionError, match="input_text.missing"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.door",
|
|
"state": "input_text.missing",
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.door", "open")
|
|
test(hass)
|
|
|
|
|
|
async def test_state_multiple_entities(hass):
|
|
"""Test with multiple entities in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": ["sensor.temperature_1", "sensor.temperature_2"],
|
|
"state": "100",
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 100)
|
|
hass.states.async_set("sensor.temperature_2", 100)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 101)
|
|
hass.states.async_set("sensor.temperature_2", 100)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 100)
|
|
hass.states.async_set("sensor.temperature_2", 101)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_multiple_states(hass):
|
|
"""Test with multiple states in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"alias": "State Condition",
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": ["100", "200"],
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 200)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 42)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_state_attribute(hass):
|
|
"""Test with state attribute in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"attribute": "attribute1",
|
|
"state": 200,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 200})
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": 200})
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": "200"})
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": 201})
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": None})
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_state_attribute_boolean(hass):
|
|
"""Test with boolean state attribute in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"attribute": "happening",
|
|
"state": False,
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"happening": 200})
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"happening": True})
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"no_happening": 201})
|
|
with pytest.raises(ConditionError):
|
|
test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"happening": False})
|
|
assert test(hass)
|
|
|
|
|
|
async def test_state_using_input_entities(hass):
|
|
"""Test state conditions using input_* entities."""
|
|
await async_setup_component(
|
|
hass,
|
|
"input_text",
|
|
{
|
|
"input_text": {
|
|
"hello": {"initial": "goodbye"},
|
|
}
|
|
},
|
|
)
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"input_select",
|
|
{
|
|
"input_select": {
|
|
"hello": {"options": ["cya", "goodbye", "welcome"], "initial": "cya"},
|
|
}
|
|
},
|
|
)
|
|
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.salut",
|
|
"state": [
|
|
"input_text.hello",
|
|
"input_select.hello",
|
|
"salut",
|
|
],
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.salut", "goodbye")
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.salut", "salut")
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.salut", "hello")
|
|
assert not test(hass)
|
|
|
|
await hass.services.async_call(
|
|
"input_text",
|
|
"set_value",
|
|
{
|
|
"entity_id": "input_text.hello",
|
|
"value": "hi",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.salut", "hi")
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.salut", "cya")
|
|
assert test(hass)
|
|
|
|
await hass.services.async_call(
|
|
"input_select",
|
|
"select_option",
|
|
{
|
|
"entity_id": "input_select.hello",
|
|
"option": "welcome",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.salut", "welcome")
|
|
assert test(hass)
|
|
|
|
|
|
async def test_numeric_state_known_non_matching(hass):
|
|
"""Test that numeric_state doesn't match on known non-matching states."""
|
|
hass.states.async_set("sensor.temperature", "unavailable")
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"above": 0,
|
|
},
|
|
)
|
|
|
|
# Unavailable state
|
|
assert not test(hass)
|
|
|
|
# Unknown state
|
|
hass.states.async_set("sensor.temperature", "unknown")
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_numeric_state_raises(hass):
|
|
"""Test that numeric_state raises ConditionError on errors."""
|
|
# Unknown entities
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": ["sensor.temperature_unknown", "sensor.humidity_unknown"],
|
|
"above": 0,
|
|
},
|
|
)
|
|
with pytest.raises(ConditionError, match="unknown entity.*temperature"):
|
|
test(hass)
|
|
with pytest.raises(ConditionError, match="unknown entity.*humidity"):
|
|
test(hass)
|
|
|
|
# Unknown attribute
|
|
with pytest.raises(ConditionError, match=r"attribute .* does not exist"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"attribute": "temperature",
|
|
"above": 0,
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
test(hass)
|
|
|
|
# Template error
|
|
with pytest.raises(ConditionError, match="ZeroDivisionError"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"value_template": "{{ 1 / 0 }}",
|
|
"above": 0,
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
test(hass)
|
|
|
|
# Bad number
|
|
with pytest.raises(ConditionError, match="cannot be processed as a number"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"above": 0,
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", "fifty")
|
|
test(hass)
|
|
|
|
# Below entity missing
|
|
with pytest.raises(ConditionError, match="'below' entity"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": "input_number.missing",
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
test(hass)
|
|
|
|
# Below entity not a number
|
|
with pytest.raises(
|
|
ConditionError,
|
|
match="'below'.*input_number.missing.*cannot be processed as a number",
|
|
):
|
|
hass.states.async_set("input_number.missing", "number")
|
|
test(hass)
|
|
|
|
# Above entity missing
|
|
with pytest.raises(ConditionError, match="'above' entity"):
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"above": "input_number.missing",
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 50)
|
|
test(hass)
|
|
|
|
# Above entity not a number
|
|
with pytest.raises(
|
|
ConditionError,
|
|
match="'above'.*input_number.missing.*cannot be processed as a number",
|
|
):
|
|
hass.states.async_set("input_number.missing", "number")
|
|
test(hass)
|
|
|
|
|
|
async def test_numeric_state_multiple_entities(hass):
|
|
"""Test with multiple entities in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"alias": "Numeric State Condition",
|
|
"condition": "numeric_state",
|
|
"entity_id": ["sensor.temperature_1", "sensor.temperature_2"],
|
|
"below": 50,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 49)
|
|
hass.states.async_set("sensor.temperature_2", 49)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 50)
|
|
hass.states.async_set("sensor.temperature_2", 49)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature_1", 49)
|
|
hass.states.async_set("sensor.temperature_2", 50)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_numeric_state_attribute(hass):
|
|
"""Test with numeric state attribute in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"attribute": "attribute1",
|
|
"below": 50,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 10})
|
|
with pytest.raises(ConditionError):
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": 49})
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": "49"})
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": 51})
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100, {"attribute1": None})
|
|
with pytest.raises(ConditionError):
|
|
assert test(hass)
|
|
|
|
|
|
async def test_numeric_state_using_input_number(hass):
|
|
"""Test numeric_state conditions using input_number entities."""
|
|
await async_setup_component(
|
|
hass,
|
|
"input_number",
|
|
{
|
|
"input_number": {
|
|
"low": {"min": 0, "max": 255, "initial": 10},
|
|
"high": {"min": 0, "max": 255, "initial": 100},
|
|
}
|
|
},
|
|
)
|
|
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature",
|
|
"below": "input_number.high",
|
|
"above": "input_number.low",
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set("sensor.temperature", 42)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 10)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("sensor.temperature", 100)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("input_number.high", "unknown")
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("input_number.high", "unavailable")
|
|
assert not test(hass)
|
|
|
|
await hass.services.async_call(
|
|
"input_number",
|
|
"set_value",
|
|
{
|
|
"entity_id": "input_number.high",
|
|
"value": 101,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set("input_number.low", "unknown")
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set("input_number.low", "unavailable")
|
|
assert not test(hass)
|
|
|
|
with pytest.raises(ConditionError):
|
|
condition.async_numeric_state(
|
|
hass, entity="sensor.temperature", below="input_number.not_exist"
|
|
)
|
|
with pytest.raises(ConditionError):
|
|
condition.async_numeric_state(
|
|
hass, entity="sensor.temperature", above="input_number.not_exist"
|
|
)
|
|
|
|
|
|
async def test_zone_raises(hass):
|
|
"""Test that zone raises ConditionError on errors."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "zone",
|
|
"entity_id": "device_tracker.cat",
|
|
"zone": "zone.home",
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="no zone"):
|
|
condition.zone(hass, zone_ent=None, entity="sensor.any")
|
|
|
|
with pytest.raises(ConditionError, match="unknown zone"):
|
|
test(hass)
|
|
|
|
hass.states.async_set(
|
|
"zone.home",
|
|
"zoning",
|
|
{"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10},
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="no entity"):
|
|
condition.zone(hass, zone_ent="zone.home", entity=None)
|
|
|
|
with pytest.raises(ConditionError, match="unknown entity"):
|
|
test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.cat",
|
|
"home",
|
|
{"friendly_name": "cat"},
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="latitude"):
|
|
test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.cat",
|
|
"home",
|
|
{"friendly_name": "cat", "latitude": 2.1},
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="longitude"):
|
|
test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.cat",
|
|
"home",
|
|
{"friendly_name": "cat", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
|
|
# All okay, now test multiple failed conditions
|
|
assert test(hass)
|
|
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "zone",
|
|
"entity_id": ["device_tracker.cat", "device_tracker.dog"],
|
|
"zone": ["zone.home", "zone.work"],
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="dog"):
|
|
test(hass)
|
|
|
|
with pytest.raises(ConditionError, match="work"):
|
|
test(hass)
|
|
|
|
hass.states.async_set(
|
|
"zone.work",
|
|
"zoning",
|
|
{"name": "work", "latitude": 20, "longitude": 10, "radius": 25000},
|
|
)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.dog",
|
|
"work",
|
|
{"friendly_name": "dog", "latitude": 20.1, "longitude": 10.1},
|
|
)
|
|
|
|
assert test(hass)
|
|
|
|
|
|
async def test_zone_multiple_entities(hass):
|
|
"""Test with multiple entities in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"alias": "Zone Condition",
|
|
"condition": "zone",
|
|
"entity_id": ["device_tracker.person_1", "device_tracker.person_2"],
|
|
"zone": "zone.home",
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set(
|
|
"zone.home",
|
|
"zoning",
|
|
{"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10},
|
|
)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person_1",
|
|
"home",
|
|
{"friendly_name": "person_1", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
hass.states.async_set(
|
|
"device_tracker.person_2",
|
|
"home",
|
|
{"friendly_name": "person_2", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person_1",
|
|
"home",
|
|
{"friendly_name": "person_1", "latitude": 20.1, "longitude": 10.1},
|
|
)
|
|
hass.states.async_set(
|
|
"device_tracker.person_2",
|
|
"home",
|
|
{"friendly_name": "person_2", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
assert not test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person_1",
|
|
"home",
|
|
{"friendly_name": "person_1", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
hass.states.async_set(
|
|
"device_tracker.person_2",
|
|
"home",
|
|
{"friendly_name": "person_2", "latitude": 20.1, "longitude": 10.1},
|
|
)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_multiple_zones(hass):
|
|
"""Test with multiple entities in condition."""
|
|
test = await condition.async_from_config(
|
|
hass,
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "zone",
|
|
"entity_id": "device_tracker.person",
|
|
"zone": ["zone.home", "zone.work"],
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
hass.states.async_set(
|
|
"zone.home",
|
|
"zoning",
|
|
{"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10},
|
|
)
|
|
hass.states.async_set(
|
|
"zone.work",
|
|
"zoning",
|
|
{"name": "work", "latitude": 20.1, "longitude": 10.1, "radius": 10},
|
|
)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person",
|
|
"home",
|
|
{"friendly_name": "person", "latitude": 2.1, "longitude": 1.1},
|
|
)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person",
|
|
"home",
|
|
{"friendly_name": "person", "latitude": 20.1, "longitude": 10.1},
|
|
)
|
|
assert test(hass)
|
|
|
|
hass.states.async_set(
|
|
"device_tracker.person",
|
|
"home",
|
|
{"friendly_name": "person", "latitude": 50.1, "longitude": 20.1},
|
|
)
|
|
assert not test(hass)
|
|
|
|
|
|
async def test_extract_entities():
|
|
"""Test extracting entities."""
|
|
assert condition.async_extract_entities(
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature_2",
|
|
"below": 110,
|
|
},
|
|
{
|
|
"condition": "not",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature_3",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature_4",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"condition": "or",
|
|
"conditions": [
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.temperature_5",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": "sensor.temperature_6",
|
|
"below": 110,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"condition": "state",
|
|
"entity_id": ["sensor.temperature_7", "sensor.temperature_8"],
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "numeric_state",
|
|
"entity_id": ["sensor.temperature_9", "sensor.temperature_10"],
|
|
"below": 110,
|
|
},
|
|
Template("{{ is_state('light.example', 'on') }}"),
|
|
],
|
|
}
|
|
) == {
|
|
"sensor.temperature",
|
|
"sensor.temperature_2",
|
|
"sensor.temperature_3",
|
|
"sensor.temperature_4",
|
|
"sensor.temperature_5",
|
|
"sensor.temperature_6",
|
|
"sensor.temperature_7",
|
|
"sensor.temperature_8",
|
|
"sensor.temperature_9",
|
|
"sensor.temperature_10",
|
|
}
|
|
|
|
|
|
async def test_extract_devices():
|
|
"""Test extracting devices."""
|
|
assert (
|
|
condition.async_extract_devices(
|
|
{
|
|
"condition": "and",
|
|
"conditions": [
|
|
{"condition": "device", "device_id": "abcd", "domain": "light"},
|
|
{"condition": "device", "device_id": "qwer", "domain": "switch"},
|
|
{
|
|
"condition": "state",
|
|
"entity_id": "sensor.not_a_device",
|
|
"state": "100",
|
|
},
|
|
{
|
|
"condition": "not",
|
|
"conditions": [
|
|
{
|
|
"condition": "device",
|
|
"device_id": "abcd_not",
|
|
"domain": "light",
|
|
},
|
|
{
|
|
"condition": "device",
|
|
"device_id": "qwer_not",
|
|
"domain": "switch",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"condition": "or",
|
|
"conditions": [
|
|
{
|
|
"condition": "device",
|
|
"device_id": "abcd_or",
|
|
"domain": "light",
|
|
},
|
|
{
|
|
"condition": "device",
|
|
"device_id": "qwer_or",
|
|
"domain": "switch",
|
|
},
|
|
],
|
|
},
|
|
Template("{{ is_state('light.example', 'on') }}"),
|
|
],
|
|
}
|
|
)
|
|
== {"abcd", "qwer", "abcd_not", "qwer_not", "abcd_or", "qwer_or"}
|
|
)
|
|
|
|
|
|
async def test_condition_template_error(hass):
|
|
"""Test invalid template."""
|
|
test = await condition.async_from_config(
|
|
hass, {"condition": "template", "value_template": "{{ undefined.state }}"}
|
|
)
|
|
|
|
with pytest.raises(ConditionError, match="template"):
|
|
test(hass)
|
|
|
|
|
|
async def test_condition_template_invalid_results(hass):
|
|
"""Test template condition render false with invalid results."""
|
|
test = await condition.async_from_config(
|
|
hass, {"condition": "template", "value_template": "{{ 'string' }}"}
|
|
)
|
|
assert not test(hass)
|
|
|
|
test = await condition.async_from_config(
|
|
hass, {"condition": "template", "value_template": "{{ 10.1 }}"}
|
|
)
|
|
assert not test(hass)
|
|
|
|
test = await condition.async_from_config(
|
|
hass, {"condition": "template", "value_template": "{{ 42 }}"}
|
|
)
|
|
assert not test(hass)
|
|
|
|
test = await condition.async_from_config(
|
|
hass, {"condition": "template", "value_template": "{{ [1, 2, 3] }}"}
|
|
)
|
|
assert not test(hass)
|