Handle template errors on MQTT payload handling (#110180)
* Handle template errors on MQTT payload handling (alt) * Handle mqtt event en image template errors correctly
This commit is contained in:
parent
ee25f6b960
commit
09f1ec78a5
32 changed files with 859 additions and 22 deletions
|
@ -7,7 +7,6 @@ from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config as conf_util
|
from homeassistant import config as conf_util
|
||||||
|
@ -27,7 +26,6 @@ from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
ConfigValidationError,
|
ConfigValidationError,
|
||||||
ServiceValidationError,
|
ServiceValidationError,
|
||||||
TemplateError,
|
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import config_validation as cv, event as ev, template
|
from homeassistant.helpers import config_validation as cv, event as ev, template
|
||||||
|
@ -87,11 +85,13 @@ from .const import ( # noqa: F401
|
||||||
MQTT_DISCONNECTED,
|
MQTT_DISCONNECTED,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
RELOADABLE_PLATFORMS,
|
RELOADABLE_PLATFORMS,
|
||||||
|
TEMPLATE_ERRORS,
|
||||||
)
|
)
|
||||||
from .models import ( # noqa: F401
|
from .models import ( # noqa: F401
|
||||||
MqttCommandTemplate,
|
MqttCommandTemplate,
|
||||||
MqttData,
|
MqttData,
|
||||||
MqttValueTemplate,
|
MqttValueTemplate,
|
||||||
|
PayloadSentinel,
|
||||||
PublishPayloadType,
|
PublishPayloadType,
|
||||||
ReceiveMessage,
|
ReceiveMessage,
|
||||||
ReceivePayloadType,
|
ReceivePayloadType,
|
||||||
|
@ -325,7 +325,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
msg_topic_template, hass
|
msg_topic_template, hass
|
||||||
).async_render(parse_result=False)
|
).async_render(parse_result=False)
|
||||||
msg_topic = valid_publish_topic(rendered_topic)
|
msg_topic = valid_publish_topic(rendered_topic)
|
||||||
except (jinja2.TemplateError, TemplateError) as exc:
|
except TEMPLATE_ERRORS as exc:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
(
|
(
|
||||||
"Unable to publish: rendering topic template of %s "
|
"Unable to publish: rendering topic template of %s "
|
||||||
|
@ -352,7 +352,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
payload = MqttCommandTemplate(
|
payload = MqttCommandTemplate(
|
||||||
template.Template(payload_template), hass=hass
|
template.Template(payload_template), hass=hass
|
||||||
).async_render()
|
).async_render()
|
||||||
except (jinja2.TemplateError, TemplateError) as exc:
|
except TEMPLATE_ERRORS as exc:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
(
|
(
|
||||||
"Unable to publish to %s: rendering payload template of "
|
"Unable to publish to %s: rendering payload template of "
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
"""Constants used by multiple MQTT modules."""
|
"""Constants used by multiple MQTT modules."""
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
|
||||||
from homeassistant.const import CONF_PAYLOAD, Platform
|
from homeassistant.const import CONF_PAYLOAD, Platform
|
||||||
|
from homeassistant.exceptions import TemplateError
|
||||||
|
|
||||||
ATTR_DISCOVERY_HASH = "discovery_hash"
|
ATTR_DISCOVERY_HASH = "discovery_hash"
|
||||||
ATTR_DISCOVERY_PAYLOAD = "discovery_payload"
|
ATTR_DISCOVERY_PAYLOAD = "discovery_payload"
|
||||||
|
@ -194,3 +197,5 @@ RELOADABLE_PLATFORMS = [
|
||||||
Platform.VALVE,
|
Platform.VALVE,
|
||||||
Platform.WATER_HEATER,
|
Platform.WATER_HEATER,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TEMPLATE_ERRORS = (jinja2.TemplateError, TemplateError, TypeError, ValueError)
|
||||||
|
|
|
@ -29,6 +29,7 @@ from .const import (
|
||||||
CONF_STATE_TOPIC,
|
CONF_STATE_TOPIC,
|
||||||
PAYLOAD_EMPTY_JSON,
|
PAYLOAD_EMPTY_JSON,
|
||||||
PAYLOAD_NONE,
|
PAYLOAD_NONE,
|
||||||
|
TEMPLATE_ERRORS,
|
||||||
)
|
)
|
||||||
from .debug_info import log_messages
|
from .debug_info import log_messages
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
|
@ -131,7 +132,10 @@ class MqttEvent(MqttEntity, EventEntity):
|
||||||
return
|
return
|
||||||
event_attributes: dict[str, Any] = {}
|
event_attributes: dict[str, Any] = {}
|
||||||
event_type: str
|
event_type: str
|
||||||
payload = self._template(msg.payload, PayloadSentinel.DEFAULT)
|
try:
|
||||||
|
payload = self._template(msg.payload, PayloadSentinel.DEFAULT)
|
||||||
|
except TEMPLATE_ERRORS:
|
||||||
|
return
|
||||||
if (
|
if (
|
||||||
not payload
|
not payload
|
||||||
or payload is PayloadSentinel.DEFAULT
|
or payload is PayloadSentinel.DEFAULT
|
||||||
|
|
|
@ -24,7 +24,7 @@ from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import subscription
|
from . import subscription
|
||||||
from .config import MQTT_BASE_SCHEMA
|
from .config import MQTT_BASE_SCHEMA
|
||||||
from .const import CONF_ENCODING, CONF_QOS
|
from .const import CONF_ENCODING, CONF_QOS, TEMPLATE_ERRORS
|
||||||
from .debug_info import log_messages
|
from .debug_info import log_messages
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
MQTT_ENTITY_COMMON_SCHEMA,
|
MQTT_ENTITY_COMMON_SCHEMA,
|
||||||
|
@ -188,10 +188,11 @@ class MqttImage(MqttEntity, ImageEntity):
|
||||||
@log_messages(self.hass, self.entity_id)
|
@log_messages(self.hass, self.entity_id)
|
||||||
def image_from_url_request_received(msg: ReceiveMessage) -> None:
|
def image_from_url_request_received(msg: ReceiveMessage) -> None:
|
||||||
"""Handle new MQTT messages."""
|
"""Handle new MQTT messages."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = cv.url(self._url_template(msg.payload))
|
url = cv.url(self._url_template(msg.payload))
|
||||||
self._attr_image_url = url
|
self._attr_image_url = url
|
||||||
|
except TEMPLATE_ERRORS:
|
||||||
|
return
|
||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Invalid image URL '%s' received at topic %s",
|
"Invalid image URL '%s' received at topic %s",
|
||||||
|
|
|
@ -94,6 +94,7 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
MQTT_CONNECTED,
|
MQTT_CONNECTED,
|
||||||
MQTT_DISCONNECTED,
|
MQTT_DISCONNECTED,
|
||||||
|
TEMPLATE_ERRORS,
|
||||||
)
|
)
|
||||||
from .debug_info import log_message, log_messages
|
from .debug_info import log_message, log_messages
|
||||||
from .discovery import (
|
from .discovery import (
|
||||||
|
@ -110,7 +111,6 @@ from .models import (
|
||||||
MqttValueTemplate,
|
MqttValueTemplate,
|
||||||
PublishPayloadType,
|
PublishPayloadType,
|
||||||
ReceiveMessage,
|
ReceiveMessage,
|
||||||
ReceivePayloadType,
|
|
||||||
)
|
)
|
||||||
from .subscription import (
|
from .subscription import (
|
||||||
EntitySubscription,
|
EntitySubscription,
|
||||||
|
@ -480,7 +480,10 @@ def write_state_on_attr_change(
|
||||||
attribute: getattr(entity, attribute, UNDEFINED)
|
attribute: getattr(entity, attribute, UNDEFINED)
|
||||||
for attribute in attributes
|
for attribute in attributes
|
||||||
}
|
}
|
||||||
msg_callback(msg)
|
try:
|
||||||
|
msg_callback(msg)
|
||||||
|
except TEMPLATE_ERRORS:
|
||||||
|
return
|
||||||
if not _attrs_have_changed(tracked_attrs):
|
if not _attrs_have_changed(tracked_attrs):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -527,8 +530,9 @@ class MqttAttributes(Entity):
|
||||||
@log_messages(self.hass, self.entity_id)
|
@log_messages(self.hass, self.entity_id)
|
||||||
@write_state_on_attr_change(self, {"_attr_extra_state_attributes"})
|
@write_state_on_attr_change(self, {"_attr_extra_state_attributes"})
|
||||||
def attributes_message_received(msg: ReceiveMessage) -> None:
|
def attributes_message_received(msg: ReceiveMessage) -> None:
|
||||||
|
"""Update extra state attributes."""
|
||||||
|
payload = attr_tpl(msg.payload)
|
||||||
try:
|
try:
|
||||||
payload = attr_tpl(msg.payload)
|
|
||||||
json_dict = json_loads(payload) if isinstance(payload, str) else None
|
json_dict = json_loads(payload) if isinstance(payload, str) else None
|
||||||
if isinstance(json_dict, dict):
|
if isinstance(json_dict, dict):
|
||||||
filtered_dict = {
|
filtered_dict = {
|
||||||
|
@ -636,7 +640,6 @@ class MqttAvailability(Entity):
|
||||||
def availability_message_received(msg: ReceiveMessage) -> None:
|
def availability_message_received(msg: ReceiveMessage) -> None:
|
||||||
"""Handle a new received MQTT availability message."""
|
"""Handle a new received MQTT availability message."""
|
||||||
topic = msg.topic
|
topic = msg.topic
|
||||||
payload: ReceivePayloadType
|
|
||||||
payload = self._avail_topics[topic][CONF_AVAILABILITY_TEMPLATE](msg.payload)
|
payload = self._avail_topics[topic][CONF_AVAILABILITY_TEMPLATE](msg.payload)
|
||||||
if payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
|
if payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
|
||||||
self._available[topic] = True
|
self._available[topic] = True
|
||||||
|
|
|
@ -29,6 +29,8 @@ if TYPE_CHECKING:
|
||||||
from .discovery import MQTTDiscoveryPayload
|
from .discovery import MQTTDiscoveryPayload
|
||||||
from .tag import MQTTTagScanner
|
from .tag import MQTTTagScanner
|
||||||
|
|
||||||
|
from .const import TEMPLATE_ERRORS
|
||||||
|
|
||||||
|
|
||||||
class PayloadSentinel(StrEnum):
|
class PayloadSentinel(StrEnum):
|
||||||
"""Sentinel for `async_render_with_possible_json_value`."""
|
"""Sentinel for `async_render_with_possible_json_value`."""
|
||||||
|
@ -247,7 +249,7 @@ class MqttValueTemplate:
|
||||||
payload, variables=values
|
payload, variables=values
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except TEMPLATE_ERRORS as exc:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"%s: %s rendering template for entity '%s', template: '%s'",
|
"%s: %s rendering template for entity '%s', template: '%s'",
|
||||||
type(exc).__name__,
|
type(exc).__name__,
|
||||||
|
@ -255,7 +257,7 @@ class MqttValueTemplate:
|
||||||
self._entity.entity_id if self._entity else "n/a",
|
self._entity.entity_id if self._entity else "n/a",
|
||||||
self._value_template.template,
|
self._value_template.template,
|
||||||
)
|
)
|
||||||
raise exc
|
raise
|
||||||
return rendered_payload
|
return rendered_payload
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@ -274,18 +276,18 @@ class MqttValueTemplate:
|
||||||
payload, default, variables=values
|
payload, default, variables=values
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except TEMPLATE_ERRORS as exc:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"%s: %s rendering template for entity '%s', template: "
|
"%s: %s rendering template for entity '%s', template: "
|
||||||
"'%s', default value: %s and payload: %s",
|
"'%s', default value: %s and payload: %s",
|
||||||
type(ex).__name__,
|
type(exc).__name__,
|
||||||
ex,
|
exc,
|
||||||
self._entity.entity_id if self._entity else "n/a",
|
self._entity.entity_id if self._entity else "n/a",
|
||||||
self._value_template.template,
|
self._value_template.template,
|
||||||
default,
|
default,
|
||||||
payload,
|
payload,
|
||||||
)
|
)
|
||||||
raise ex
|
raise
|
||||||
return rendered_payload
|
return rendered_payload
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from . import subscription
|
from . import subscription
|
||||||
from .config import MQTT_BASE_SCHEMA
|
from .config import MQTT_BASE_SCHEMA
|
||||||
from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC
|
from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC, TEMPLATE_ERRORS
|
||||||
from .discovery import MQTTDiscoveryPayload
|
from .discovery import MQTTDiscoveryPayload
|
||||||
from .mixins import (
|
from .mixins import (
|
||||||
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||||
|
@ -136,7 +136,10 @@ class MQTTTagScanner(MqttDiscoveryDeviceUpdate):
|
||||||
"""Subscribe to MQTT topics."""
|
"""Subscribe to MQTT topics."""
|
||||||
|
|
||||||
async def tag_scanned(msg: ReceiveMessage) -> None:
|
async def tag_scanned(msg: ReceiveMessage) -> None:
|
||||||
tag_id = str(self._value_template(msg.payload, "")).strip()
|
try:
|
||||||
|
tag_id = str(self._value_template(msg.payload, "")).strip()
|
||||||
|
except TEMPLATE_ERRORS:
|
||||||
|
return
|
||||||
if not tag_id: # No output from template, ignore
|
if not tag_id: # No output from template, ignore
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -1354,3 +1354,32 @@ async def test_reload_after_invalid_config(
|
||||||
|
|
||||||
# Make sure the config is loaded now
|
# Make sure the config is loaded now
|
||||||
assert hass.states.get("alarm_control_panel.test") is not None
|
assert hass.states.get("alarm_control_panel.test") is not None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
alarm_control_panel.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1294,3 +1294,31 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
binary_sensor.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -29,6 +29,7 @@ from homeassistant.components.climate import (
|
||||||
from homeassistant.components.mqtt.climate import (
|
from homeassistant.components.mqtt.climate import (
|
||||||
DEFAULT_INITIAL_TEMPERATURE,
|
DEFAULT_INITIAL_TEMPERATURE,
|
||||||
MQTT_CLIMATE_ATTRIBUTES_BLOCKED,
|
MQTT_CLIMATE_ATTRIBUTES_BLOCKED,
|
||||||
|
VALUE_TEMPLATE_KEYS,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
|
from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -2514,3 +2515,36 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
climate.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
value_template.replace("_value", "_state").replace(
|
||||||
|
"_template", "_topic"
|
||||||
|
): "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template in VALUE_TEMPLATE_KEYS
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATE_KEYS,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -3550,3 +3550,43 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
VALUE_TEMPLATES = {
|
||||||
|
CONF_VALUE_TEMPLATE: CONF_STATE_TOPIC,
|
||||||
|
CONF_GET_POSITION_TEMPLATE: CONF_GET_POSITION_TOPIC,
|
||||||
|
CONF_TILT_STATUS_TEMPLATE: CONF_TILT_STATUS_TOPIC,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
cover.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"position_topic": "position-topic",
|
||||||
|
"tilt_command_topic": "tilt-topic",
|
||||||
|
topic: "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template, topic in VALUE_TEMPLATES.items()
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATES,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -673,3 +673,31 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
device_tracker.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -802,3 +802,31 @@ async def test_skipped_async_ha_write_state2(
|
||||||
async_fire_mqtt_message(hass, topic, payload2)
|
async_fire_mqtt_message(hass, topic, payload2)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(mock_async_ha_write_state.mock_calls) == 2
|
assert len(mock_async_ha_write_state.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
event.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -2317,3 +2317,50 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
VALUE_TEMPLATES = {
|
||||||
|
"state_value_template": "state_topic",
|
||||||
|
"direction_value_template": "direction_state_topic",
|
||||||
|
"oscillation_value_template": "oscillation_state_topic",
|
||||||
|
"percentage_value_template": "percentage_state_topic",
|
||||||
|
"preset_mode_value_template": "preset_mode_state_topic",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
fan.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"direction_command_topic": "direction-command-topic",
|
||||||
|
"oscillation_command_topic": "oscillation-command-topic",
|
||||||
|
"percentage_command_topic": "percentage-command-topic",
|
||||||
|
"preset_mode_command_topic": "preset-mode-command-topic",
|
||||||
|
"preset_modes": [
|
||||||
|
"auto",
|
||||||
|
],
|
||||||
|
topic: "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template, topic in VALUE_TEMPLATES.items()
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATES,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1613,3 +1613,47 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
VALUE_TEMPLATES = {
|
||||||
|
"state_value_template": "state_topic",
|
||||||
|
"action_template": "action_topic",
|
||||||
|
"mode_state_template": "mode_state_topic",
|
||||||
|
"current_humidity_template": "current_humidity_topic",
|
||||||
|
"target_humidity_state_template": "target_humidity_state_topic",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
humidifier.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"mode_command_topic": "preset-mode-command-topic",
|
||||||
|
"modes": [
|
||||||
|
"auto",
|
||||||
|
],
|
||||||
|
topic: "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template, topic in VALUE_TEMPLATES.items()
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATES,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -844,3 +844,31 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
image.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"url_topic": "test-topic",
|
||||||
|
"url_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -430,25 +430,27 @@ async def test_value_template_fails(
|
||||||
entity.hass = hass
|
entity.hass = hass
|
||||||
tpl = template.Template("{{ value_json.some_var * 2 }}")
|
tpl = template.Template("{{ value_json.some_var * 2 }}")
|
||||||
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass, entity=entity)
|
val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass, entity=entity)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError) as exc:
|
||||||
val_tpl.async_render_with_possible_json_value('{"some_var": null }')
|
val_tpl.async_render_with_possible_json_value('{"some_var": null }')
|
||||||
await hass.async_block_till_done()
|
assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'"
|
||||||
assert (
|
assert (
|
||||||
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
|
||||||
"rendering template for entity 'sensor.test', "
|
"rendering template for entity 'sensor.test', "
|
||||||
"template: '{{ value_json.some_var * 2 }}'"
|
"template: '{{ value_json.some_var * 2 }}'"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError) as exc:
|
||||||
val_tpl.async_render_with_possible_json_value(
|
val_tpl.async_render_with_possible_json_value(
|
||||||
'{"some_var": null }', default=100
|
'{"some_var": null }', default=100
|
||||||
)
|
)
|
||||||
|
assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'"
|
||||||
assert (
|
assert (
|
||||||
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' "
|
||||||
"rendering template for entity 'sensor.test', "
|
"rendering template for entity 'sensor.test', "
|
||||||
"template: '{{ value_json.some_var * 2 }}', default value: 100 and payload: "
|
"template: '{{ value_json.some_var * 2 }}', default value: 100 and payload: "
|
||||||
'{"some_var": null }'
|
'{"some_var": null }'
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def test_service_call_without_topic_does_not_publish(
|
async def test_service_call_without_topic_does_not_publish(
|
||||||
|
|
|
@ -924,3 +924,31 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
lawn_mower.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"activity_state_topic": "test-topic",
|
||||||
|
"activity_value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -186,6 +186,7 @@ from homeassistant.components.mqtt.light.schema_basic import (
|
||||||
CONF_RGBWW_COMMAND_TOPIC,
|
CONF_RGBWW_COMMAND_TOPIC,
|
||||||
CONF_XY_COMMAND_TOPIC,
|
CONF_XY_COMMAND_TOPIC,
|
||||||
MQTT_LIGHT_ATTRIBUTES_BLOCKED,
|
MQTT_LIGHT_ATTRIBUTES_BLOCKED,
|
||||||
|
VALUE_TEMPLATE_KEYS,
|
||||||
)
|
)
|
||||||
from homeassistant.components.mqtt.models import PublishPayloadType
|
from homeassistant.components.mqtt.models import PublishPayloadType
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -3691,3 +3692,36 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
light.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
value_template.replace(
|
||||||
|
"state_value_template", "state_topic"
|
||||||
|
).replace("_value_template", "_state_topic"): "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template in VALUE_TEMPLATE_KEYS
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATE_KEYS,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1444,3 +1444,73 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
VALUE_TEMPLATES_NO_RGB = (
|
||||||
|
"brightness_template",
|
||||||
|
"color_temp_template",
|
||||||
|
"effect_template",
|
||||||
|
"state_template",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
light.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template in VALUE_TEMPLATES_NO_RGB
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMPLATES_NO_RGB,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
light.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"red_template": "{{ value_json.r * 1 }}",
|
||||||
|
"green_template": "{{ value_json.g * 1 }}",
|
||||||
|
"blue_template": "{{ value_json.b * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_rgb_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"r": 255, "g": 255, "b": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1079,3 +1079,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
lock.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -396,3 +396,59 @@ async def test_name_attribute_is_set_or_not(
|
||||||
|
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) is None
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
sensor.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"availability_topic": "test-topic",
|
||||||
|
"availability_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
sensor.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"availability": {
|
||||||
|
"topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
sensor.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"state_topic": "state-topic",
|
||||||
|
"json_attributes_topic": "test-topic",
|
||||||
|
"json_attributes_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ids=[
|
||||||
|
"availability_template1",
|
||||||
|
"availability_template2",
|
||||||
|
"json_attributes_template",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1175,3 +1175,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
number.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -848,3 +848,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
select.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1488,3 +1488,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
sensor.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
"last_reset_value_template": "{{ value_json.some_var * 2 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1143,3 +1143,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
siren.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"state_value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -800,3 +800,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
switch.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -951,3 +951,25 @@ async def test_unload_entry(
|
||||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
tag_mock.assert_not_called()
|
tag_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mqtt_mock: MqttMockHAClient,
|
||||||
|
tag_mock: AsyncMock,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||||
|
config["value_template"] = "{{ value_json.some_var * 1 }}"
|
||||||
|
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "foobar/tag_scanned", '{"some_var": null }')
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -861,3 +861,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
text.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -782,3 +782,40 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
VALUE_TEMMPLATES = {
|
||||||
|
"value_template": "state_topic",
|
||||||
|
"latest_version_template": "latest_version_topic",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
update.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
state_topic: "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template, state_topic in VALUE_TEMMPLATES.items()
|
||||||
|
],
|
||||||
|
ids=VALUE_TEMMPLATES,
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1505,3 +1505,32 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
valve.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"value_template": "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
|
@ -1261,3 +1261,35 @@ async def test_skipped_async_ha_write_state(
|
||||||
"""Test a write state command is only called when there is change."""
|
"""Test a write state command is only called when there is change."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
await help_test_skipped_async_ha_write_state(hass, topic, payload1, payload2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hass_config",
|
||||||
|
[
|
||||||
|
help_custom_config(
|
||||||
|
water_heater.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"modes": ["auto"],
|
||||||
|
"mode_state_topic": "test-topic",
|
||||||
|
value_template: "{{ value_json.some_var * 1 }}",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for value_template in ["value_template", "mode_state_template"]
|
||||||
|
],
|
||||||
|
ids=["value_template", "mode_state_template"],
|
||||||
|
)
|
||||||
|
async def test_value_template_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the rendering of MQTT value template fails."""
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", '{"some_var": null }')
|
||||||
|
assert (
|
||||||
|
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue