From 83d4f8eedcbb0df0b057a56a1da83d66bcd6a969 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 24 Dec 2025 11:00:36 -0800 Subject: [PATCH] Fix Roborock repair issue behavior (#159718) --- .../components/roborock/coordinator.py | 5 +- tests/components/roborock/test_init.py | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index fe070e10321..025a5c37186 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -122,6 +122,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState]): # Tracks the last successful update to control when we report failure # to the base class. This is reset on successful data update. self._last_update_success_time: datetime | None = None + self._has_connected_locally: bool = False @cached_property def dock_device_info(self) -> DeviceInfo: @@ -191,7 +192,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState]): async def _verify_api(self) -> None: """Verify that the api is reachable.""" if self._device.is_connected: - if self._device.is_local_connected: + self._has_connected_locally |= self._device.is_local_connected + if self._has_connected_locally: async_delete_issue( self.hass, DOMAIN, f"cloud_api_used_{self.duid_slug}" ) @@ -234,6 +236,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceState]): async def _async_update_data(self) -> DeviceState: """Update data via library.""" + await self._verify_api() try: # Update device props and standard api information await self._update_device_prop() diff --git a/tests/components/roborock/test_init.py b/tests/components/roborock/test_init.py index 034d8b3c1f9..8aad5fde10e 100644 --- a/tests/components/roborock/test_init.py +++ b/tests/components/roborock/test_init.py @@ -415,6 +415,63 @@ async def test_cloud_api_repair( assert len(issue_registry.issues) == 0 +@pytest.mark.parametrize("platforms", [[Platform.SENSOR]]) +async def test_cloud_api_repair_cleared_on_update( + hass: HomeAssistant, + mock_roborock_entry: MockConfigEntry, + fake_vacuum: FakeDevice, + freezer: FrozenDateTimeFactory, +) -> None: + """Test that a repair is created then cleared if the device is reachable locally again.""" + + # Fake that the device is only reachable via cloud + fake_vacuum.is_connected = True + fake_vacuum.is_local_connected = False + + # Load the integration and verify that a repair issue is created + await async_setup_component(hass, HA_DOMAIN, {}) + await hass.config_entries.async_setup(mock_roborock_entry.entry_id) + await hass.async_block_till_done() + assert mock_roborock_entry.state is ConfigEntryState.LOADED + + issue_registry = ir.async_get(hass) + assert len(issue_registry.issues) == 1 + + # Fake that the device is reachable locally again. + fake_vacuum.is_local_connected = True + + # Refresh the coordinator using an arbitrary sensor, which should + # clear the repair issue. + sensor_entity_id = "sensor.roborock_s7_maxv_battery" + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: sensor_entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + # Verify that the repair issue is cleared + issue_registry = ir.async_get(hass) + assert len(issue_registry.issues) == 0 + + # Fake the device is cloud only again. Refreshing the coordinator + # should not recreate the repair issue. + fake_vacuum.is_local_connected = False + + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: sensor_entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + # Verify that the repair issue still does not exist + issue_registry = ir.async_get(hass) + assert len(issue_registry.issues) == 0 + + @pytest.mark.parametrize("platforms", [[Platform.SENSOR]]) async def test_zeo_device_fails_setup( hass: HomeAssistant,