mirror of
https://github.com/home-assistant/core.git
synced 2025-11-08 18:39:30 +00:00
Fix template entity preview when templates error (#154029)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
@@ -344,16 +344,23 @@ class TemplateEntity(AbstractTemplateEntity):
|
||||
)
|
||||
return
|
||||
|
||||
errors = []
|
||||
for update in updates:
|
||||
for template_attr in self._template_attrs[update.template]:
|
||||
template_attr.handle_result(
|
||||
event, update.template, update.last_result, update.result
|
||||
)
|
||||
if isinstance(update.result, TemplateError):
|
||||
errors.append(update.result)
|
||||
|
||||
if not self._preview_callback:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
if errors:
|
||||
self._preview_callback(None, None, None, str(errors[-1]))
|
||||
return
|
||||
|
||||
try:
|
||||
calculated_state = self._async_calculate_state()
|
||||
validate_state(calculated_state.state)
|
||||
@@ -451,13 +458,19 @@ class TemplateEntity(AbstractTemplateEntity):
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Render a preview."""
|
||||
|
||||
def log_template_error(level: int, msg: str) -> None:
|
||||
preview_callback(None, None, None, msg)
|
||||
def suppress_preview_errors(level: int, msg: str) -> None:
|
||||
"""Suppress redundant template render errors.
|
||||
|
||||
Preview entities render templates at least 3 times before the preview entity
|
||||
is created. If template contains an error, each render will produce an error.
|
||||
Instead of overwhelming the client with errors, suppress them and raise
|
||||
a single error through the self._handle_results method.
|
||||
"""
|
||||
|
||||
self._preview_callback = preview_callback
|
||||
self._async_setup_templates()
|
||||
try:
|
||||
self._async_template_startup(None, log_template_error)
|
||||
self._async_template_startup(None, suppress_preview_errors)
|
||||
except Exception as err: # noqa: BLE001
|
||||
preview_callback(None, None, None, str(err))
|
||||
return self._call_on_remove_callbacks
|
||||
|
||||
@@ -8,7 +8,8 @@ from pytest_unordered import unordered
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.template import DOMAIN, async_setup_entry
|
||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@@ -872,10 +873,10 @@ async def test_options(
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one'), default='') + float(states('sensor.two'), default='') }}",
|
||||
"{{ float(states('sensor.one'), 0.0) + float(states('sensor.two'), 0.0) }}",
|
||||
{},
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
["", STATE_UNKNOWN, "50.0"],
|
||||
["0.0", "30.0", "50.0"],
|
||||
[{}, {}],
|
||||
[["one", "two"], ["one", "two"]],
|
||||
),
|
||||
@@ -1124,7 +1125,7 @@ async def test_config_flow_preview_bad_input(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
["unavailable", "50.0"],
|
||||
["50.0"],
|
||||
[
|
||||
(
|
||||
"ValueError: Template error: float got invalid input 'unknown' "
|
||||
@@ -1181,10 +1182,6 @@ async def test_config_flow_preview_template_startup_error(
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"] == {"error": error_event}
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[0]
|
||||
|
||||
for input_entity in input_entities:
|
||||
hass.states.async_set(
|
||||
f"{template_type}.{input_entity}", input_states[input_entity], {}
|
||||
@@ -1192,7 +1189,7 @@ async def test_config_flow_preview_template_startup_error(
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[1]
|
||||
assert msg["event"]["state"] == template_states[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -1208,8 +1205,8 @@ async def test_config_flow_preview_template_startup_error(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) > 30 and undefined_function() }}",
|
||||
[{"one": "30.0", "two": "20.0"}, {"one": "35.0", "two": "20.0"}],
|
||||
["False", "unavailable"],
|
||||
["'undefined_function' is undefined"],
|
||||
["False"],
|
||||
["UndefinedError: 'undefined_function' is undefined"],
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1273,10 +1270,6 @@ async def test_config_flow_preview_template_error(
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"] == {"error": error_event}
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
@@ -1749,3 +1742,89 @@ async def test_options_flow_change_device(
|
||||
**state_template,
|
||||
**extra_options,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("step_id", "user_input", "expected_error"),
|
||||
[
|
||||
(
|
||||
"light",
|
||||
{
|
||||
"name": "",
|
||||
"state": "{{ state() }}",
|
||||
"level": "{{ statex() }}",
|
||||
"turn_on": [],
|
||||
"turn_off": [],
|
||||
"set_level": [],
|
||||
},
|
||||
"UndefinedError: 'statex' is undefined",
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
{
|
||||
"name": "",
|
||||
"state": "{{ state() }}",
|
||||
},
|
||||
"UndefinedError: 'state' is undefined",
|
||||
),
|
||||
(
|
||||
"light",
|
||||
{
|
||||
"name": "",
|
||||
"state": "{{ state() }}",
|
||||
"level": "{{ states('sensor.abc') }}",
|
||||
"turn_on": [],
|
||||
"turn_off": [],
|
||||
"set_level": [],
|
||||
},
|
||||
"UndefinedError: 'state' is undefined",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_preview_error(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
step_id: str,
|
||||
user_input: dict,
|
||||
expected_error: str,
|
||||
) -> None:
|
||||
"""Test preview will error if any template errors."""
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": step_id},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == step_id
|
||||
assert result["errors"] is None
|
||||
assert result["preview"] == "template"
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "template/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": user_input,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] is None
|
||||
|
||||
# Test expected error
|
||||
msg = await client.receive_json()
|
||||
assert "error" in msg["event"]
|
||||
assert msg["event"]["error"] == expected_error
|
||||
|
||||
# Test No preview is created
|
||||
with pytest.raises(TimeoutError):
|
||||
await client.receive_json(timeout=0.01)
|
||||
|
||||
Reference in New Issue
Block a user