core/tests/components/template/test_sensor.py

1263 lines
42 KiB
Python

"""The test for the Template sensor platform."""
from asyncio import Event
from datetime import timedelta
from unittest.mock import patch
import pytest
from homeassistant.bootstrap import async_from_config_dict
from homeassistant.components import sensor
from homeassistant.const import (
ATTR_ENTITY_PICTURE,
ATTR_ICON,
EVENT_COMPONENT_LOADED,
EVENT_HOMEASSISTANT_START,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import Context, CoreState, callback
from homeassistant.helpers import entity_registry
from homeassistant.helpers.template import Template
from homeassistant.setup import ATTR_COMPONENT, async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed
TEST_NAME = "sensor.test_template_sensor"
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "It {{ states.sensor.test_state.state }}."
}
},
},
},
],
)
async def test_template_legacy(hass, start_ha):
"""Test template."""
assert hass.states.get(TEST_NAME).state == "It ."
hass.states.async_set("sensor.test_state", "Works")
await hass.async_block_till_done()
assert hass.states.get(TEST_NAME).state == "It Works."
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"icon_template": "{% if states.sensor.test_state.state == "
"'Works' %}"
"mdi:check"
"{% endif %}",
}
},
},
},
],
)
async def test_icon_template(hass, start_ha):
"""Test icon template."""
assert hass.states.get(TEST_NAME).attributes.get("icon") == ""
hass.states.async_set("sensor.test_state", "Works")
await hass.async_block_till_done()
assert hass.states.get(TEST_NAME).attributes["icon"] == "mdi:check"
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"entity_picture_template": "{% if states.sensor.test_state.state == "
"'Works' %}"
"/local/sensor.png"
"{% endif %}",
}
},
},
},
],
)
async def test_entity_picture_template(hass, start_ha):
"""Test entity_picture template."""
assert hass.states.get(TEST_NAME).attributes.get("entity_picture") == ""
hass.states.async_set("sensor.test_state", "Works")
await hass.async_block_till_done()
assert (
hass.states.get(TEST_NAME).attributes["entity_picture"] == "/local/sensor.png"
)
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"attribute,config,expected",
[
(
"friendly_name",
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"friendly_name_template": "It {{ states.sensor.test_state.state }}.",
}
},
},
},
("It .", "It Works."),
),
(
"friendly_name",
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"friendly_name_template": "{{ 'It ' + states.sensor.test_state.state + '.'}}",
}
},
},
},
(None, "It Works."),
),
(
"friendly_name",
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.fourohfour.state }}",
"friendly_name_template": "It {{ states.sensor.test_state.state }}.",
}
},
},
},
("It .", "It Works."),
),
(
"test_attribute",
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"attribute_templates": {
"test_attribute": "It {{ states.sensor.test_state.state }}."
},
}
},
},
},
("It .", "It Works."),
),
],
)
async def test_friendly_name_template(hass, attribute, expected, start_ha):
"""Test friendly_name template with an unknown value_template."""
assert hass.states.get(TEST_NAME).attributes.get(attribute) == expected[0]
hass.states.async_set("sensor.test_state", "Works")
await hass.async_block_till_done()
assert hass.states.get(TEST_NAME).attributes[attribute] == expected[1]
@pytest.mark.parametrize("count,domain", [(0, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {"value_template": "{% if rubbish %}"}
},
},
},
{
"sensor": {
"platform": "template",
"sensors": {
"test INVALID sensor": {
"value_template": "{{ states.sensor.test_state.state }}"
}
},
},
},
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {"invalid"},
},
},
},
{
"sensor": {
"platform": "template",
},
},
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"not_value_template": "{{ states.sensor.test_state.state }}"
}
},
},
},
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"test": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"device_class": "foobarnotreal",
}
}
},
},
},
],
)
async def test_template_syntax_error(hass, start_ha):
"""Test setup with invalid device_class."""
assert hass.states.async_all("sensor") == []
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "It {{ states.sensor.test_state"
".attributes.missing }}."
}
},
},
},
],
)
async def test_template_attribute_missing(hass, start_ha):
"""Test missing attribute template."""
assert hass.states.get(TEST_NAME).state == STATE_UNAVAILABLE
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test1": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"device_class": "temperature",
},
"test2": {
"value_template": "{{ states.sensor.test_sensor.state }}"
},
},
},
},
],
)
async def test_setup_valid_device_class(hass, start_ha):
"""Test setup with valid device_class."""
assert hass.states.get("sensor.test1").attributes["device_class"] == "temperature"
assert "device_class" not in hass.states.get("sensor.test2").attributes
@pytest.mark.parametrize("load_registries", [False])
async def test_creating_sensor_loads_group(hass):
"""Test setting up template sensor loads group component first."""
order = []
after_dep_event = Event()
async def async_setup_group(hass, config):
# Make sure group takes longer to load, so that it won't
# be loaded first by chance
await after_dep_event.wait()
order.append("group")
return True
async def async_setup_template(
hass, config, async_add_entities, discovery_info=None
):
order.append("sensor.template")
return True
async def set_after_dep_event(event):
if event.data[ATTR_COMPONENT] == "sensor":
after_dep_event.set()
hass.bus.async_listen(EVENT_COMPONENT_LOADED, set_after_dep_event)
with patch(
"homeassistant.components.group.async_setup",
new=async_setup_group,
), patch(
"homeassistant.components.template.sensor.async_setup_platform",
new=async_setup_template,
):
await async_from_config_dict(
{"sensor": {"platform": "template", "sensors": {}}, "group": {}}, hass
)
await hass.async_block_till_done()
assert order == ["group", "sensor.template"]
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"availability_template": "{{ is_state('sensor.availability_sensor', 'on') }}",
}
},
},
},
],
)
async def test_available_template_with_entities(hass, start_ha):
"""Test availability tempalates with values from other entities."""
hass.states.async_set("sensor.availability_sensor", STATE_OFF)
# When template returns true..
hass.states.async_set("sensor.availability_sensor", STATE_ON)
await hass.async_block_till_done()
# Device State should not be unavailable
assert hass.states.get(TEST_NAME).state != STATE_UNAVAILABLE
# When Availability template returns false
hass.states.async_set("sensor.availability_sensor", STATE_OFF)
await hass.async_block_till_done()
# device state should be unavailable
assert hass.states.get(TEST_NAME).state == STATE_UNAVAILABLE
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"invalid_template": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"attribute_templates": {
"test_attribute": "{{ states.sensor.unknown.attributes.picture }}"
},
}
},
},
},
],
)
async def test_invalid_attribute_template(hass, caplog, start_ha, caplog_setup_text):
"""Test that errors are logged if rendering template fails."""
hass.states.async_set("sensor.test_sensor", "startup")
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
await hass.helpers.entity_component.async_update_entity("sensor.invalid_template")
assert "TemplateError" in caplog_setup_text
assert "test_attribute" in caplog.text
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"my_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"availability_template": "{{ x - 12 }}",
}
},
},
},
],
)
async def test_invalid_availability_template_keeps_component_available(
hass, start_ha, caplog_setup_text
):
"""Test that an invalid availability keeps the device available."""
assert hass.states.get("sensor.my_sensor").state != STATE_UNAVAILABLE
assert "UndefinedError: 'x' is undefined" in caplog_setup_text
async def test_no_template_match_all(hass, caplog):
"""Test that we allow static templates."""
hass.states.async_set("sensor.test_sensor", "startup")
hass.state = CoreState.not_running
await async_setup_component(
hass,
sensor.DOMAIN,
{
"sensor": {
"platform": "template",
"sensors": {
"invalid_state": {"value_template": "{{ 1 + 1 }}"},
"invalid_icon": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"icon_template": "{{ 1 + 1 }}",
},
"invalid_entity_picture": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"entity_picture_template": "{{ 1 + 1 }}",
},
"invalid_friendly_name": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"friendly_name_template": "{{ 1 + 1 }}",
},
"invalid_attribute": {
"value_template": "{{ states.sensor.test_sensor.state }}",
"attribute_templates": {"test_attribute": "{{ 1 + 1 }}"},
},
},
}
},
)
await hass.async_block_till_done()
assert hass.states.get("sensor.invalid_state").state == "unknown"
assert hass.states.get("sensor.invalid_icon").state == "unknown"
assert hass.states.get("sensor.invalid_entity_picture").state == "unknown"
assert hass.states.get("sensor.invalid_friendly_name").state == "unknown"
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 6
assert hass.states.get("sensor.invalid_state").state == "unknown"
assert hass.states.get("sensor.invalid_icon").state == "unknown"
assert hass.states.get("sensor.invalid_entity_picture").state == "unknown"
assert hass.states.get("sensor.invalid_friendly_name").state == "unknown"
assert hass.states.get("sensor.invalid_attribute").state == "unknown"
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert hass.states.get("sensor.invalid_state").state == "2"
assert hass.states.get("sensor.invalid_icon").state == "startup"
assert hass.states.get("sensor.invalid_entity_picture").state == "startup"
assert hass.states.get("sensor.invalid_friendly_name").state == "startup"
assert hass.states.get("sensor.invalid_attribute").state == "startup"
hass.states.async_set("sensor.test_sensor", "hello")
await hass.async_block_till_done()
assert hass.states.get("sensor.invalid_state").state == "2"
# Will now process because we have at least one valid template
assert hass.states.get("sensor.invalid_icon").state == "hello"
assert hass.states.get("sensor.invalid_entity_picture").state == "hello"
assert hass.states.get("sensor.invalid_friendly_name").state == "hello"
assert hass.states.get("sensor.invalid_attribute").state == "hello"
await hass.helpers.entity_component.async_update_entity("sensor.invalid_state")
await hass.helpers.entity_component.async_update_entity("sensor.invalid_icon")
await hass.helpers.entity_component.async_update_entity(
"sensor.invalid_entity_picture"
)
await hass.helpers.entity_component.async_update_entity(
"sensor.invalid_friendly_name"
)
await hass.helpers.entity_component.async_update_entity("sensor.invalid_attribute")
assert hass.states.get("sensor.invalid_state").state == "2"
assert hass.states.get("sensor.invalid_icon").state == "hello"
assert hass.states.get("sensor.invalid_entity_picture").state == "hello"
assert hass.states.get("sensor.invalid_friendly_name").state == "hello"
assert hass.states.get("sensor.invalid_attribute").state == "hello"
@pytest.mark.parametrize("count,domain", [(1, "template")])
@pytest.mark.parametrize(
"config",
[
{
"template": {
"unique_id": "group-id",
"sensor": {"name": "top-level", "unique_id": "sensor-id", "state": "5"},
},
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor_01": {
"unique_id": "not-so-unique-anymore",
"value_template": "{{ true }}",
},
"test_template_sensor_02": {
"unique_id": "not-so-unique-anymore",
"value_template": "{{ false }}",
},
},
},
},
],
)
async def test_unique_id(hass, start_ha):
"""Test unique_id option only creates one sensor per id."""
assert len(hass.states.async_all()) == 2
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert (
ent_reg.async_get_entity_id("sensor", "template", "group-id-sensor-id")
is not None
)
assert (
ent_reg.async_get_entity_id("sensor", "template", "not-so-unique-anymore")
is not None
)
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"solar_angle": {
"friendly_name": "Sun angle",
"unit_of_measurement": "degrees",
"value_template": "{{ state_attr('sun.sun', 'elevation') }}",
},
"sunrise": {
"value_template": "{{ state_attr('sun.sun', 'next_rising') }}"
},
},
}
},
],
)
async def test_sun_renders_once_per_sensor(hass, start_ha):
"""Test sun change renders the template only once per sensor."""
now = dt_util.utcnow()
hass.states.async_set(
"sun.sun", "above_horizon", {"elevation": 45.3, "next_rising": now}
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 3
assert hass.states.get("sensor.solar_angle").state == "45.3"
assert hass.states.get("sensor.sunrise").state == str(now)
async_render_calls = []
@callback
def _record_async_render(self, *args, **kwargs):
"""Catch async_render."""
async_render_calls.append(self.template)
return "mocked"
later = dt_util.utcnow()
with patch.object(Template, "async_render", _record_async_render):
hass.states.async_set("sun.sun", {"elevation": 50, "next_rising": later})
await hass.async_block_till_done()
assert hass.states.get("sensor.solar_angle").state == "mocked"
assert hass.states.get("sensor.sunrise").state == "mocked"
assert len(async_render_calls) == 2
assert set(async_render_calls) == {
"{{ state_attr('sun.sun', 'elevation') }}",
"{{ state_attr('sun.sun', 'next_rising') }}",
}
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test": {
"value_template": "{{ ((states.sensor.test.state or 0) | int) + 1 }}",
},
},
}
},
],
)
async def test_self_referencing_sensor_loop(hass, start_ha, caplog_setup_text):
"""Test a self referencing sensor does not loop forever."""
assert len(hass.states.async_all()) == 1
await hass.async_block_till_done()
await hass.async_block_till_done()
assert "Template loop detected" in caplog_setup_text
assert int(hass.states.get("sensor.test").state) == 2
await hass.async_block_till_done()
assert int(hass.states.get("sensor.test").state) == 2
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test": {
"value_template": "{{ ((states.sensor.test.state or 0) | int) + 1 }}",
"icon_template": "{% if ((states.sensor.test.state or 0) | int) >= 1 %}mdi:greater{% else %}mdi:less{% endif %}",
},
},
}
},
],
)
async def test_self_referencing_sensor_with_icon_loop(
hass, start_ha, caplog_setup_text
):
"""Test a self referencing sensor loops forever with a valid self referencing icon."""
assert len(hass.states.async_all()) == 1
await hass.async_block_till_done()
await hass.async_block_till_done()
assert "Template loop detected" in caplog_setup_text
state = hass.states.get("sensor.test")
assert int(state.state) == 3
assert state.attributes[ATTR_ICON] == "mdi:greater"
await hass.async_block_till_done()
state = hass.states.get("sensor.test")
assert int(state.state) == 3
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test": {
"value_template": "{{ ((states.sensor.test.state or 0) | int) + 1 }}",
"icon_template": "{% if ((states.sensor.test.state or 0) | int) > 3 %}mdi:greater{% else %}mdi:less{% endif %}",
"entity_picture_template": "{% if ((states.sensor.test.state or 0) | int) >= 1 %}bigpic{% else %}smallpic{% endif %}",
},
},
}
},
],
)
async def test_self_referencing_sensor_with_icon_and_picture_entity_loop(
hass, start_ha, caplog_setup_text
):
"""Test a self referencing sensor loop forevers with a valid self referencing icon."""
assert len(hass.states.async_all()) == 1
await hass.async_block_till_done()
await hass.async_block_till_done()
assert "Template loop detected" in caplog_setup_text
state = hass.states.get("sensor.test")
assert int(state.state) == 4
assert state.attributes[ATTR_ICON] == "mdi:less"
assert state.attributes[ATTR_ENTITY_PICTURE] == "bigpic"
await hass.async_block_till_done()
assert int(state.state) == 4
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test": {
"value_template": "{{ 1 }}",
"entity_picture_template": "{{ ((states.sensor.test.attributes['entity_picture'] or 0) | int) + 1 }}",
},
},
}
},
],
)
async def test_self_referencing_entity_picture_loop(hass, start_ha, caplog_setup_text):
"""Test a self referencing sensor does not loop forever with a looping self referencing entity picture."""
assert len(hass.states.async_all()) == 1
next_time = dt_util.utcnow() + timedelta(seconds=1.2)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert "Template loop detected" in caplog_setup_text
state = hass.states.get("sensor.test")
assert int(state.state) == 1
assert state.attributes[ATTR_ENTITY_PICTURE] == 2
await hass.async_block_till_done()
assert int(state.state) == 1
async def test_self_referencing_icon_with_no_loop(hass, caplog):
"""Test a self referencing icon that does not loop."""
hass.states.async_set("sensor.heartworm_high_80", 10)
hass.states.async_set("sensor.heartworm_low_57", 10)
hass.states.async_set("sensor.heartworm_avg_64", 10)
hass.states.async_set("sensor.heartworm_avg_57", 10)
value_template_str = """{% if (states.sensor.heartworm_high_80.state|int >= 10) and (states.sensor.heartworm_low_57.state|int >= 10) %}
extreme
{% elif (states.sensor.heartworm_avg_64.state|int >= 30) %}
high
{% elif (states.sensor.heartworm_avg_64.state|int >= 14) %}
moderate
{% elif (states.sensor.heartworm_avg_64.state|int >= 5) %}
slight
{% elif (states.sensor.heartworm_avg_57.state|int >= 5) %}
marginal
{% elif (states.sensor.heartworm_avg_57.state|int < 5) %}
none
{% endif %}"""
icon_template_str = """{% if is_state('sensor.heartworm_risk',"extreme") %}
mdi:hazard-lights
{% elif is_state('sensor.heartworm_risk',"high") %}
mdi:triangle-outline
{% elif is_state('sensor.heartworm_risk',"moderate") %}
mdi:alert-circle-outline
{% elif is_state('sensor.heartworm_risk',"slight") %}
mdi:exclamation
{% elif is_state('sensor.heartworm_risk',"marginal") %}
mdi:heart
{% elif is_state('sensor.heartworm_risk',"none") %}
mdi:snowflake
{% endif %}"""
await async_setup_component(
hass,
sensor.DOMAIN,
{
"sensor": {
"platform": "template",
"sensors": {
"heartworm_risk": {
"value_template": value_template_str,
"icon_template": icon_template_str,
},
},
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5
hass.states.async_set("sensor.heartworm_high_80", 10)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert "Template loop detected" not in caplog.text
state = hass.states.get("sensor.heartworm_risk")
assert state.state == "extreme"
assert state.attributes[ATTR_ICON] == "mdi:hazard-lights"
await hass.async_block_till_done()
assert state.state == "extreme"
assert state.attributes[ATTR_ICON] == "mdi:hazard-lights"
assert "Template loop detected" not in caplog.text
@pytest.mark.parametrize("count,domain", [(1, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"friendly_name_template": "{{ states.sensor.test_state.state }}",
}
},
}
},
],
)
async def test_duplicate_templates(hass, start_ha):
"""Test template entity where the value and friendly name as the same template."""
hass.states.async_set("sensor.test_state", "Abc")
await hass.async_block_till_done()
state = hass.states.get(TEST_NAME)
assert state.attributes["friendly_name"] == "Abc"
assert state.state == "Abc"
hass.states.async_set("sensor.test_state", "Def")
await hass.async_block_till_done()
state = hass.states.get(TEST_NAME)
assert state.attributes["friendly_name"] == "Def"
assert state.state == "Def"
@pytest.mark.parametrize("count,domain", [(2, "template")])
@pytest.mark.parametrize(
"config",
[
{
"template": [
{"invalid": "config"},
# Config after invalid should still be set up
{
"unique_id": "listening-test-event",
"trigger": {"platform": "event", "event_type": "test_event"},
"sensors": {
"hello": {
"friendly_name": "Hello Name",
"unique_id": "hello_name-id",
"device_class": "battery",
"unit_of_measurement": "%",
"value_template": "{{ trigger.event.data.beer }}",
"entity_picture_template": "{{ '/local/dogs.png' }}",
"icon_template": "{{ 'mdi:pirate' }}",
"attribute_templates": {
"plus_one": "{{ trigger.event.data.beer + 1 }}"
},
},
},
"sensor": [
{
"name": "via list",
"unique_id": "via_list-id",
"device_class": "battery",
"unit_of_measurement": "%",
"availability": "{{ True }}",
"state": "{{ trigger.event.data.beer + 1 }}",
"picture": "{{ '/local/dogs.png' }}",
"icon": "{{ 'mdi:pirate' }}",
"attributes": {
"plus_one": "{{ trigger.event.data.beer + 1 }}"
},
"state_class": "measurement",
}
],
},
{
"trigger": [],
"sensors": {
"bare_minimum": {
"value_template": "{{ trigger.event.data.beer }}"
},
},
},
],
},
],
)
async def test_trigger_entity(hass, start_ha):
"""Test trigger entity works."""
state = hass.states.get("sensor.hello_name")
assert state is not None
assert state.state == STATE_UNKNOWN
state = hass.states.get("sensor.bare_minimum")
assert state is not None
assert state.state == STATE_UNKNOWN
context = Context()
hass.bus.async_fire("test_event", {"beer": 2}, context=context)
await hass.async_block_till_done()
state = hass.states.get("sensor.hello_name")
assert state.state == "2"
assert state.attributes.get("device_class") == "battery"
assert state.attributes.get("icon") == "mdi:pirate"
assert state.attributes.get("entity_picture") == "/local/dogs.png"
assert state.attributes.get("plus_one") == 3
assert state.attributes.get("unit_of_measurement") == "%"
assert state.context is context
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 2
assert (
ent_reg.entities["sensor.hello_name"].unique_id
== "listening-test-event-hello_name-id"
)
assert (
ent_reg.entities["sensor.via_list"].unique_id
== "listening-test-event-via_list-id"
)
state = hass.states.get("sensor.via_list")
assert state.state == "3"
assert state.attributes.get("device_class") == "battery"
assert state.attributes.get("icon") == "mdi:pirate"
assert state.attributes.get("entity_picture") == "/local/dogs.png"
assert state.attributes.get("plus_one") == 3
assert state.attributes.get("unit_of_measurement") == "%"
assert state.attributes.get("state_class") == "measurement"
assert state.context is context
@pytest.mark.parametrize("count,domain", [(1, "template")])
@pytest.mark.parametrize(
"config",
[
{
"template": {
"trigger": {"platform": "event", "event_type": "test_event"},
"sensors": {
"hello": {
"unique_id": "no-base-id",
"friendly_name": "Hello",
"value_template": "{{ non_existing + 1 }}",
}
},
},
},
],
)
async def test_trigger_entity_render_error(hass, start_ha):
"""Test trigger entity handles render error."""
state = hass.states.get("sensor.hello")
assert state is not None
assert state.state == STATE_UNKNOWN
context = Context()
hass.bus.async_fire("test_event", {"beer": 2}, context=context)
await hass.async_block_till_done()
state = hass.states.get("sensor.hello")
assert state.state == STATE_UNAVAILABLE
ent_reg = entity_registry.async_get(hass)
assert len(ent_reg.entities) == 1
assert ent_reg.entities["sensor.hello"].unique_id == "no-base-id"
@pytest.mark.parametrize("count,domain", [(0, sensor.DOMAIN)])
@pytest.mark.parametrize(
"config",
[
{
"sensor": {
"platform": "template",
"trigger": {"platform": "event", "event_type": "test_event"},
"sensors": {
"test_template_sensor": {
"value_template": "{{ states.sensor.test_state.state }}",
"friendly_name_template": "{{ states.sensor.test_state.state }}",
}
},
}
},
],
)
async def test_trigger_not_allowed_platform_config(hass, start_ha, caplog_setup_text):
"""Test we throw a helpful warning if a trigger is configured in platform config."""
state = hass.states.get(TEST_NAME)
assert state is None
assert (
"You can only add triggers to template entities if they are defined under `template:`."
in caplog_setup_text
)
@pytest.mark.parametrize("count,domain", [(1, "template")])
@pytest.mark.parametrize(
"config",
[
{
"template": {
"sensor": {
"name": "top-level",
"device_class": "battery",
"state_class": "measurement",
"state": "5",
"unit_of_measurement": "%",
},
},
},
],
)
async def test_config_top_level(hass, start_ha):
"""Test unique_id option only creates one sensor per id."""
assert len(hass.states.async_all()) == 1
state = hass.states.get("sensor.top_level")
assert state is not None
assert state.state == "5"
assert state.attributes["device_class"] == "battery"
assert state.attributes["state_class"] == "measurement"
async def test_trigger_entity_available(hass):
"""Test trigger entity availability works."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"trigger": {"platform": "event", "event_type": "test_event"},
"sensor": [
{
"name": "Maybe Available",
"availability": "{{ trigger and trigger.event.data.beer == 2 }}",
"state": "{{ trigger.event.data.beer }}",
},
],
},
],
},
)
await hass.async_block_till_done()
# Sensors are unknown if never triggered
state = hass.states.get("sensor.maybe_available")
assert state is not None
assert state.state == STATE_UNKNOWN
hass.bus.async_fire("test_event", {"beer": 2})
await hass.async_block_till_done()
state = hass.states.get("sensor.maybe_available")
assert state.state == "2"
hass.bus.async_fire("test_event", {"beer": 1})
await hass.async_block_till_done()
state = hass.states.get("sensor.maybe_available")
assert state.state == "unavailable"
async def test_trigger_entity_device_class_parsing_works(hass):
"""Test trigger entity device class parsing works."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"trigger": {"platform": "event", "event_type": "test_event"},
"sensor": [
{
"name": "Date entity",
"state": "{{ now().date() }}",
"device_class": "date",
},
{
"name": "Timestamp entity",
"state": "{{ now() }}",
"device_class": "timestamp",
},
],
},
],
},
)
await hass.async_block_till_done()
now = dt_util.now()
with patch("homeassistant.util.dt.now", return_value=now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
date_state = hass.states.get("sensor.date_entity")
assert date_state is not None
assert date_state.state == now.date().isoformat()
ts_state = hass.states.get("sensor.timestamp_entity")
assert ts_state is not None
assert ts_state.state == now.isoformat(timespec="seconds")
async def test_trigger_entity_device_class_errors_works(hass):
"""Test trigger entity device class errors works."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"trigger": {"platform": "event", "event_type": "test_event"},
"sensor": [
{
"name": "Date entity",
"state": "invalid",
"device_class": "date",
},
{
"name": "Timestamp entity",
"state": "invalid",
"device_class": "timestamp",
},
],
},
],
},
)
await hass.async_block_till_done()
now = dt_util.now()
with patch("homeassistant.util.dt.now", return_value=now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
date_state = hass.states.get("sensor.date_entity")
assert date_state is not None
assert date_state.state == STATE_UNKNOWN
ts_state = hass.states.get("sensor.timestamp_entity")
assert ts_state is not None
assert ts_state.state == STATE_UNKNOWN
async def test_entity_device_class_parsing_works(hass):
"""Test entity device class parsing works."""
now = dt_util.now()
with patch("homeassistant.util.dt.now", return_value=now):
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"sensor": [
{
"name": "Date entity",
"state": "{{ now().date() }}",
"device_class": "date",
},
{
"name": "Timestamp entity",
"state": "{{ now() }}",
"device_class": "timestamp",
},
],
},
],
},
)
await hass.async_block_till_done()
date_state = hass.states.get("sensor.date_entity")
assert date_state is not None
assert date_state.state == now.date().isoformat()
ts_state = hass.states.get("sensor.timestamp_entity")
assert ts_state is not None
assert ts_state.state == now.isoformat(timespec="seconds")
async def test_entity_device_class_errors_works(hass):
"""Test entity device class errors works."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"sensor": [
{
"name": "Date entity",
"state": "invalid",
"device_class": "date",
},
{
"name": "Timestamp entity",
"state": "invalid",
"device_class": "timestamp",
},
],
},
],
},
)
await hass.async_block_till_done()
now = dt_util.now()
with patch("homeassistant.util.dt.now", return_value=now):
hass.bus.async_fire("test_event")
await hass.async_block_till_done()
date_state = hass.states.get("sensor.date_entity")
assert date_state is not None
assert date_state.state == STATE_UNKNOWN
ts_state = hass.states.get("sensor.timestamp_entity")
assert ts_state is not None
assert ts_state.state == STATE_UNKNOWN