Minor cleanup and test coverage improvement for MQTT (#55265)
This commit is contained in:
parent
eae828a15a
commit
4d7e3cde5a
2 changed files with 137 additions and 22 deletions
|
@ -38,7 +38,7 @@ from homeassistant.core import (
|
||||||
ServiceCall,
|
ServiceCall,
|
||||||
callback,
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
from homeassistant.exceptions import HomeAssistantError, TemplateError, Unauthorized
|
||||||
from homeassistant.helpers import config_validation as cv, event, template
|
from homeassistant.helpers import config_validation as cv, event, template
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
||||||
from homeassistant.helpers.typing import ConfigType, ServiceDataType
|
from homeassistant.helpers.typing import ConfigType, ServiceDataType
|
||||||
|
@ -153,16 +153,6 @@ MQTT_WILL_BIRTH_SCHEMA = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def embedded_broker_deprecated(value):
|
|
||||||
"""Warn user that embedded MQTT broker is deprecated."""
|
|
||||||
_LOGGER.warning(
|
|
||||||
"The embedded MQTT broker has been deprecated and will stop working"
|
|
||||||
"after June 5th, 2019. Use an external broker instead. For"
|
|
||||||
"instructions, see https://www.home-assistant.io/docs/mqtt/broker"
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.All(
|
DOMAIN: vol.All(
|
||||||
|
@ -495,7 +485,7 @@ async def async_setup_entry(hass, entry):
|
||||||
payload = template.Template(payload_template, hass).async_render(
|
payload = template.Template(payload_template, hass).async_render(
|
||||||
parse_result=False
|
parse_result=False
|
||||||
)
|
)
|
||||||
except template.jinja2.TemplateError as exc:
|
except (template.jinja2.TemplateError, TemplateError) as exc:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Unable to publish to %s: rendering payload template of "
|
"Unable to publish to %s: rendering payload template of "
|
||||||
"%s failed because %s",
|
"%s failed because %s",
|
||||||
|
|
|
@ -12,13 +12,13 @@ from homeassistant.components import mqtt, websocket_api
|
||||||
from homeassistant.components.mqtt import debug_info
|
from homeassistant.components.mqtt import debug_info
|
||||||
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
|
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DOMAIN,
|
|
||||||
ATTR_SERVICE,
|
|
||||||
EVENT_CALL_SERVICE,
|
EVENT_CALL_SERVICE,
|
||||||
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import CoreState, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
@ -97,21 +97,35 @@ async def test_publish_calls_service(hass, mqtt_mock, calls, record_calls):
|
||||||
hass.bus.async_listen_once(EVENT_CALL_SERVICE, record_calls)
|
hass.bus.async_listen_once(EVENT_CALL_SERVICE, record_calls)
|
||||||
|
|
||||||
mqtt.async_publish(hass, "test-topic", "test-payload")
|
mqtt.async_publish(hass, "test-topic", "test-payload")
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
assert calls[0][0].data["service_data"][mqtt.ATTR_TOPIC] == "test-topic"
|
assert calls[0][0].data["service_data"][mqtt.ATTR_TOPIC] == "test-topic"
|
||||||
assert calls[0][0].data["service_data"][mqtt.ATTR_PAYLOAD] == "test-payload"
|
assert calls[0][0].data["service_data"][mqtt.ATTR_PAYLOAD] == "test-payload"
|
||||||
|
assert mqtt.ATTR_QOS not in calls[0][0].data["service_data"]
|
||||||
|
assert mqtt.ATTR_RETAIN not in calls[0][0].data["service_data"]
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(EVENT_CALL_SERVICE, record_calls)
|
||||||
|
|
||||||
|
mqtt.async_publish(hass, "test-topic", "test-payload", 2, True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(calls) == 2
|
||||||
|
assert calls[1][0].data["service_data"][mqtt.ATTR_TOPIC] == "test-topic"
|
||||||
|
assert calls[1][0].data["service_data"][mqtt.ATTR_PAYLOAD] == "test-payload"
|
||||||
|
assert calls[1][0].data["service_data"][mqtt.ATTR_QOS] == 2
|
||||||
|
assert calls[1][0].data["service_data"][mqtt.ATTR_RETAIN] is True
|
||||||
|
|
||||||
|
|
||||||
async def test_service_call_without_topic_does_not_publish(hass, mqtt_mock):
|
async def test_service_call_without_topic_does_not_publish(hass, mqtt_mock):
|
||||||
"""Test the service call if topic is missing."""
|
"""Test the service call if topic is missing."""
|
||||||
hass.bus.fire(
|
with pytest.raises(vol.Invalid):
|
||||||
EVENT_CALL_SERVICE,
|
await hass.services.async_call(
|
||||||
{ATTR_DOMAIN: mqtt.DOMAIN, ATTR_SERVICE: mqtt.SERVICE_PUBLISH},
|
mqtt.DOMAIN,
|
||||||
)
|
mqtt.SERVICE_PUBLISH,
|
||||||
await hass.async_block_till_done()
|
{},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
assert not mqtt_mock.async_publish.called
|
assert not mqtt_mock.async_publish.called
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,10 +134,37 @@ async def test_service_call_with_template_payload_renders_template(hass, mqtt_mo
|
||||||
|
|
||||||
If 'payload_template' is provided and 'payload' is not, then render it.
|
If 'payload_template' is provided and 'payload' is not, then render it.
|
||||||
"""
|
"""
|
||||||
mqtt.async_publish_template(hass, "test/topic", "{{ 1+1 }}")
|
mqtt.publish_template(hass, "test/topic", "{{ 1+1 }}")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mqtt_mock.async_publish.called
|
assert mqtt_mock.async_publish.called
|
||||||
assert mqtt_mock.async_publish.call_args[0][1] == "2"
|
assert mqtt_mock.async_publish.call_args[0][1] == "2"
|
||||||
|
mqtt_mock.reset_mock()
|
||||||
|
|
||||||
|
mqtt.async_publish_template(hass, "test/topic", "{{ 2+2 }}")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mqtt_mock.async_publish.called
|
||||||
|
assert mqtt_mock.async_publish.call_args[0][1] == "4"
|
||||||
|
mqtt_mock.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
mqtt.DOMAIN,
|
||||||
|
mqtt.SERVICE_PUBLISH,
|
||||||
|
{mqtt.ATTR_TOPIC: "test/topic", mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 4+4 }}"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert mqtt_mock.async_publish.called
|
||||||
|
assert mqtt_mock.async_publish.call_args[0][1] == "8"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_call_with_bad_template(hass, mqtt_mock):
|
||||||
|
"""Test the service call with a bad template does not publish."""
|
||||||
|
await hass.services.async_call(
|
||||||
|
mqtt.DOMAIN,
|
||||||
|
mqtt.SERVICE_PUBLISH,
|
||||||
|
{mqtt.ATTR_TOPIC: "test/topic", mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 1 | bad }}"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert not mqtt_mock.async_publish.called
|
||||||
|
|
||||||
|
|
||||||
async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock):
|
async def test_service_call_with_payload_doesnt_render_template(hass, mqtt_mock):
|
||||||
|
@ -340,6 +381,34 @@ async def test_subscribe_topic(hass, mqtt_mock, calls, record_calls):
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_subscribe_topic_non_async(hass, mqtt_mock, calls, record_calls):
|
||||||
|
"""Test the subscription of a topic using the non-async function."""
|
||||||
|
unsub = await hass.async_add_executor_job(
|
||||||
|
mqtt.subscribe, hass, "test-topic", record_calls
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", "test-payload")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0][0].topic == "test-topic"
|
||||||
|
assert calls[0][0].payload == "test-payload"
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(unsub)
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "test-topic", "test-payload")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_subscribe_bad_topic(hass, mqtt_mock, calls, record_calls):
|
||||||
|
"""Test the subscription of a topic."""
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await mqtt.async_subscribe(hass, 55, record_calls)
|
||||||
|
|
||||||
|
|
||||||
async def test_subscribe_deprecated(hass, mqtt_mock):
|
async def test_subscribe_deprecated(hass, mqtt_mock):
|
||||||
"""Test the subscription of a topic using deprecated callback signature."""
|
"""Test the subscription of a topic using deprecated callback signature."""
|
||||||
calls = []
|
calls = []
|
||||||
|
@ -833,6 +902,62 @@ async def test_no_birth_message(hass, mqtt_client_mock, mqtt_mock):
|
||||||
mqtt_client_mock.publish.assert_not_called()
|
mqtt_client_mock.publish.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mqtt_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
mqtt.CONF_BROKER: "mock-broker",
|
||||||
|
mqtt.CONF_BIRTH_MESSAGE: {
|
||||||
|
mqtt.ATTR_TOPIC: "homeassistant/status",
|
||||||
|
mqtt.ATTR_PAYLOAD: "online",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config):
|
||||||
|
"""Test sending birth message does not happen until Home Assistant starts."""
|
||||||
|
hass.state = CoreState.starting
|
||||||
|
birth = asyncio.Event()
|
||||||
|
|
||||||
|
result = await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: mqtt_config})
|
||||||
|
assert result
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Workaround: asynctest==0.13 fails on @functools.lru_cache
|
||||||
|
spec = dir(hass.data["mqtt"])
|
||||||
|
spec.remove("_matching_subscriptions")
|
||||||
|
|
||||||
|
mqtt_component_mock = MagicMock(
|
||||||
|
return_value=hass.data["mqtt"],
|
||||||
|
spec_set=spec,
|
||||||
|
wraps=hass.data["mqtt"],
|
||||||
|
)
|
||||||
|
mqtt_component_mock._mqttc = mqtt_client_mock
|
||||||
|
|
||||||
|
hass.data["mqtt"] = mqtt_component_mock
|
||||||
|
mqtt_mock = hass.data["mqtt"]
|
||||||
|
mqtt_mock.reset_mock()
|
||||||
|
|
||||||
|
async def wait_birth(topic, payload, qos):
|
||||||
|
"""Handle birth message."""
|
||||||
|
birth.set()
|
||||||
|
|
||||||
|
with patch("homeassistant.components.mqtt.DISCOVERY_COOLDOWN", 0.1):
|
||||||
|
await mqtt.async_subscribe(hass, "homeassistant/status", wait_birth)
|
||||||
|
mqtt_mock._mqtt_on_connect(None, None, 0, 0)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
with pytest.raises(asyncio.TimeoutError):
|
||||||
|
await asyncio.wait_for(birth.wait(), 0.2)
|
||||||
|
assert not mqtt_client_mock.publish.called
|
||||||
|
assert not birth.is_set()
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await birth.wait()
|
||||||
|
mqtt_client_mock.publish.assert_called_with(
|
||||||
|
"homeassistant/status", "online", 0, False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mqtt_config",
|
"mqtt_config",
|
||||||
[
|
[
|
||||||
|
|
Loading…
Add table
Reference in a new issue