Skip to content

Commit 2ac15ab

Browse files
allenporterCopilot
andauthored
Ensure Roborock disconnects mqtt on unload/stop (home-assistant#158144)
Co-authored-by: Copilot <[email protected]>
1 parent d599bb9 commit 2ac15ab

File tree

3 files changed

+57
-41
lines changed

3 files changed

+57
-41
lines changed

homeassistant/components/roborock/__init__.py

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from roborock.map.map_parser import MapParserConfig
2121

2222
from homeassistant.const import CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
23-
from homeassistant.core import HomeAssistant
23+
from homeassistant.core import Event, HomeAssistant
2424
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
2525
from homeassistant.helpers import device_registry as dr
2626
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -99,10 +99,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
9999
translation_domain=DOMAIN,
100100
translation_key="home_data_fail",
101101
) from err
102+
103+
async def shutdown_roborock(_: Event | None = None) -> None:
104+
await asyncio.gather(device_manager.close(), cache.flush())
105+
106+
entry.async_on_unload(
107+
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_roborock)
108+
)
109+
entry.async_on_unload(shutdown_roborock)
110+
102111
devices = await device_manager.get_devices()
103112
_LOGGER.debug("Device manager found %d devices", len(devices))
104-
for device in devices:
105-
entry.async_on_unload(device.close)
106113

107114
coordinators = await asyncio.gather(
108115
*build_setup_functions(hass, entry, devices, user_data),
@@ -124,25 +131,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
124131
translation_domain=DOMAIN,
125132
translation_key="no_coordinators",
126133
)
127-
valid_coordinators = RoborockCoordinators(v1_coords, a01_coords)
128-
129-
async def on_stop(_: Any) -> None:
130-
_LOGGER.debug("Shutting down roborock")
131-
await asyncio.gather(
132-
*(
133-
coordinator.async_shutdown()
134-
for coordinator in valid_coordinators.values()
135-
),
136-
cache.flush(),
137-
)
138-
139-
entry.async_on_unload(
140-
hass.bus.async_listen_once(
141-
EVENT_HOMEASSISTANT_STOP,
142-
on_stop,
143-
)
144-
)
145-
entry.runtime_data = valid_coordinators
134+
entry.runtime_data = RoborockCoordinators(v1_coords, a01_coords)
146135

147136
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
148137

tests/components/roborock/conftest.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
ZeoState,
2626
)
2727
from roborock.devices.device import RoborockDevice
28+
from roborock.devices.device_manager import DeviceManager
2829
from roborock.devices.traits.v1 import PropertiesApi
2930
from roborock.devices.traits.v1.clean_summary import CleanSummaryTrait
3031
from roborock.devices.traits.v1.command import CommandTrait
@@ -134,18 +135,6 @@ async def close(self) -> None:
134135
"""Close the device."""
135136

136137

137-
class FakeDeviceManager:
138-
"""A fake device manager that returns a list of devices."""
139-
140-
def __init__(self, devices: list[RoborockDevice]) -> None:
141-
"""Initialize the fake device manager."""
142-
self._devices = devices
143-
144-
async def get_devices(self) -> list[RoborockDevice]:
145-
"""Return the list of devices."""
146-
return self._devices
147-
148-
149138
def make_mock_trait(
150139
trait_spec: type[V1TraitMixin] | None = None,
151140
dataclass_template: RoborockBase | None = None,
@@ -348,16 +337,26 @@ def fake_vacuum_command_fixture(
348337
return command_trait
349338

350339

340+
@pytest.fixture(name="device_manager")
341+
def device_manager_fixture(
342+
fake_devices: list[FakeDevice],
343+
) -> AsyncMock:
344+
"""Fixture to create a fake device manager."""
345+
device_manager = AsyncMock(spec=DeviceManager)
346+
device_manager.get_devices = AsyncMock(return_value=fake_devices)
347+
return device_manager
348+
349+
351350
@pytest.fixture(name="fake_create_device_manager", autouse=True)
352351
def fake_create_device_manager_fixture(
353-
fake_devices: list[FakeDevice],
354-
) -> Generator[Mock]:
352+
device_manager: AsyncMock,
353+
) -> None:
355354
"""Fixture to create a fake device manager."""
356355
with patch(
357356
"homeassistant.components.roborock.create_device_manager",
358357
) as mock_create_device_manager:
359-
mock_create_device_manager.return_value = FakeDeviceManager(fake_devices)
360-
yield mock_create_device_manager
358+
mock_create_device_manager.return_value = device_manager
359+
yield
361360

362361

363362
@pytest.fixture(name="config_entry_data")

tests/components/roborock/test_init.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pathlib
44
from typing import Any
5-
from unittest.mock import patch
5+
from unittest.mock import AsyncMock, patch
66

77
import pytest
88
from roborock import (
@@ -26,14 +26,42 @@
2626
from tests.typing import ClientSessionGenerator
2727

2828

29-
async def test_unload_entry(hass: HomeAssistant, setup_entry: MockConfigEntry) -> None:
30-
"""Test unloading roboorck integration."""
29+
async def test_unload_entry(
30+
hass: HomeAssistant,
31+
setup_entry: MockConfigEntry,
32+
device_manager: AsyncMock,
33+
) -> None:
34+
"""Test unloading roborock integration."""
3135
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
3236
assert setup_entry.state is ConfigEntryState.LOADED
37+
38+
assert device_manager.get_devices.called
39+
assert not device_manager.close.called
40+
41+
# Unload the config entry and verify that the device manager is closed
3342
assert await hass.config_entries.async_unload(setup_entry.entry_id)
3443
await hass.async_block_till_done()
3544
assert setup_entry.state is ConfigEntryState.NOT_LOADED
3645

46+
assert device_manager.close.called
47+
48+
49+
async def test_home_assistant_stop(
50+
hass: HomeAssistant,
51+
setup_entry: MockConfigEntry,
52+
device_manager: AsyncMock,
53+
) -> None:
54+
"""Test shutting down Home Assistant."""
55+
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
56+
assert setup_entry.state is ConfigEntryState.LOADED
57+
58+
assert not device_manager.close.called
59+
60+
# Perform Home Assistant stop and verify that device manager is closed
61+
await hass.async_stop()
62+
63+
assert device_manager.close.called
64+
3765

3866
async def test_reauth_started(
3967
hass: HomeAssistant, mock_roborock_entry: MockConfigEntry

0 commit comments

Comments
 (0)