Make sure MQTT client is available when starting depending platforms (#91164)

* Make sure MQTT is available starting mqtt_json

* Wait for mqtt client

* Sync client connect

* Simplify

* Addiitional tests async_wait_for_mqtt_client

* Improve comment waiting for mqtt

* Improve docstr

* Do not wait unless the MQTT client is in setup

* Handle entry errors during setup

* More comments - do not clear event

* Add snips and mqtt_room

* Add manual_mqtt

* Update homeassistant/components/mqtt/__init__.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Use a fixture, improve tests

* Simplify

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Jan Bouwhuis
2023-04-20 08:07:35 +02:00
committed by GitHub
co-authored by J. Nick Koston
parent adc472862b
commit 0bcda9fe9c
11 changed files with 346 additions and 34 deletions
@@ -1506,3 +1506,24 @@ async def test_state_changes_are_published_to_mqtt(
mqtt_mock.async_publish.assert_called_once_with(
"alarm/state", STATE_ALARM_DISARMED, 0, True
)
async def test_no_mqtt(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None:
"""Test publishing of MQTT messages when state changes."""
assert await async_setup_component(
hass,
alarm_control_panel.DOMAIN,
{
alarm_control_panel.DOMAIN: {
"platform": "manual_mqtt",
"name": "test",
"state_topic": "alarm/state",
"command_topic": "alarm/command",
}
},
)
await hass.async_block_till_done()
entity_id = "alarm_control_panel.test"
assert hass.states.get(entity_id) is None
assert "MQTT integration is not available" in caplog.text
+166 -1
View File
@@ -1,12 +1,17 @@
"""Test MQTT utils."""
from collections.abc import Callable
from random import getrandbits
from unittest.mock import patch
import pytest
from homeassistant.components import mqtt
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
from homeassistant.core import CoreState, HomeAssistant
from tests.common import MockConfigEntry
from tests.typing import MqttMockHAClient, MqttMockPahoClient
@pytest.fixture(autouse=True)
@@ -48,3 +53,163 @@ async def test_reading_non_exitisting_certificate_file() -> None:
assert (
mqtt.util.migrate_certificate_file_to_content("/home/file_not_exists") is None
)
@patch("homeassistant.components.mqtt.PLATFORMS", [])
async def test_waiting_for_client_not_loaded(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
) -> None:
"""Test waiting for client while mqtt entry is not yet loaded."""
hass.state = CoreState.starting
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={"broker": "test-broker"},
state=ConfigEntryState.NOT_LOADED,
)
entry.add_to_hass(hass)
unsubs: list[Callable[[], None]] = []
async def _async_just_in_time_subscribe() -> Callable[[], None]:
nonlocal unsub
assert await mqtt.async_wait_for_mqtt_client(hass)
# Awaiting a second time should work too and return True
assert await mqtt.async_wait_for_mqtt_client(hass)
unsubs.append(await mqtt.async_subscribe(hass, "test_topic", lambda msg: None))
# Simulate some integration waiting for the client to become available
hass.async_add_job(_async_just_in_time_subscribe)
hass.async_add_job(_async_just_in_time_subscribe)
hass.async_add_job(_async_just_in_time_subscribe)
hass.async_add_job(_async_just_in_time_subscribe)
assert entry.state == ConfigEntryState.NOT_LOADED
assert await hass.config_entries.async_setup(entry.entry_id)
assert len(unsubs) == 4
for unsub in unsubs:
unsub()
@patch("homeassistant.components.mqtt.PLATFORMS", [])
async def test_waiting_for_client_loaded(
hass: HomeAssistant,
mqtt_mock: MqttMockHAClient,
) -> None:
"""Test waiting for client where mqtt entry is loaded."""
unsub: Callable[[], None] | None = None
async def _async_just_in_time_subscribe() -> Callable[[], None]:
nonlocal unsub
assert await mqtt.async_wait_for_mqtt_client(hass)
unsub = await mqtt.async_subscribe(hass, "test_topic", lambda msg: None)
entry = hass.config_entries.async_entries(mqtt.DATA_MQTT)[0]
assert entry.state == ConfigEntryState.LOADED
await _async_just_in_time_subscribe()
assert unsub is not None
unsub()
async def test_waiting_for_client_entry_fails(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
) -> None:
"""Test waiting for client where mqtt entry is failing."""
hass.state = CoreState.starting
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={"broker": "test-broker"},
state=ConfigEntryState.NOT_LOADED,
)
entry.add_to_hass(hass)
async def _async_just_in_time_subscribe() -> Callable[[], None]:
assert not await mqtt.async_wait_for_mqtt_client(hass)
hass.async_add_job(_async_just_in_time_subscribe)
assert entry.state == ConfigEntryState.NOT_LOADED
with patch(
"homeassistant.components.mqtt.async_setup_entry",
side_effect=Exception,
):
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == ConfigEntryState.SETUP_ERROR
async def test_waiting_for_client_setup_fails(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
) -> None:
"""Test waiting for client where mqtt entry is failing during setup."""
hass.state = CoreState.starting
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={"broker": "test-broker"},
state=ConfigEntryState.NOT_LOADED,
)
entry.add_to_hass(hass)
async def _async_just_in_time_subscribe() -> Callable[[], None]:
assert not await mqtt.async_wait_for_mqtt_client(hass)
hass.async_add_job(_async_just_in_time_subscribe)
assert entry.state == ConfigEntryState.NOT_LOADED
# Simulate MQTT setup fails before the client would become available
mqtt_client_mock.connect.side_effect = Exception
assert not await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == ConfigEntryState.SETUP_ERROR
@patch("homeassistant.components.mqtt.util.AVAILABILITY_TIMEOUT", 0.01)
async def test_waiting_for_client_timeout(
hass: HomeAssistant,
) -> None:
"""Test waiting for client with timeout."""
hass.state = CoreState.starting
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={"broker": "test-broker"},
state=ConfigEntryState.NOT_LOADED,
)
entry.add_to_hass(hass)
assert entry.state == ConfigEntryState.NOT_LOADED
# returns False after timeout
assert not await mqtt.async_wait_for_mqtt_client(hass)
async def test_waiting_for_client_with_disabled_entry(
hass: HomeAssistant,
) -> None:
"""Test waiting for client with timeout."""
hass.state = CoreState.starting
await hass.async_block_till_done()
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={"broker": "test-broker"},
state=ConfigEntryState.NOT_LOADED,
)
entry.add_to_hass(hass)
# Disable MQTT config entry
await hass.config_entries.async_set_disabled_by(
entry.entry_id, ConfigEntryDisabler.USER
)
assert entry.state == ConfigEntryState.NOT_LOADED
# returns False because entry is disabled
assert not await mqtt.async_wait_for_mqtt_client(hass)
@@ -11,6 +11,8 @@ from homeassistant.components.device_tracker.legacy import (
DOMAIN as DT_DOMAIN,
YAML_DEVICES,
)
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.config_entries import ConfigEntryDisabler
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
@@ -39,6 +41,28 @@ async def setup_comp(
os.remove(yaml_devices)
async def test_setup_fails_without_mqtt_being_setup(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Ensure mqtt is started when we setup the component."""
# Simulate MQTT is was removed
mqtt_entry = hass.config_entries.async_entries(MQTT_DOMAIN)[0]
await hass.config_entries.async_unload(mqtt_entry.entry_id)
await hass.config_entries.async_set_disabled_by(
mqtt_entry.entry_id, ConfigEntryDisabler.USER
)
dev_id = "zanzito"
topic = "location/zanzito"
await async_setup_component(
hass,
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
assert "MQTT integration is not available" in caplog.text
async def test_ensure_device_tracker_platform_validation(hass: HomeAssistant) -> None:
"""Test if platform validation was done."""
+24
View File
@@ -3,6 +3,8 @@ import datetime
import json
from unittest.mock import patch
import pytest
from homeassistant.components.mqtt import CONF_QOS, CONF_STATE_TOPIC, DEFAULT_QOS
import homeassistant.components.sensor as sensor
from homeassistant.const import (
@@ -56,6 +58,28 @@ async def assert_distance(hass, distance):
assert state.attributes.get("distance") == distance
async def test_no_mqtt(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None:
"""Test no mqtt available."""
assert await async_setup_component(
hass,
sensor.DOMAIN,
{
sensor.DOMAIN: {
CONF_PLATFORM: "mqtt_room",
CONF_NAME: NAME,
CONF_DEVICE_ID: DEVICE_ID,
CONF_STATE_TOPIC: "room_presence",
CONF_QOS: DEFAULT_QOS,
CONF_TIMEOUT: 5,
}
},
)
await hass.async_block_till_done()
state = hass.states.get(SENSOR_STATE)
assert state is None
assert "MQTT integration is not available" in caplog.text
async def test_room_update(hass: HomeAssistant, mqtt_mock: MqttMockHAClient) -> None:
"""Test the updating between rooms."""
assert await async_setup_component(