mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 01:46:11 -04:00
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:
co-authored by
J. Nick Koston
parent
adc472862b
commit
0bcda9fe9c
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user