mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Add stale device removal support to UniFi Access (#166792)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,7 @@ from unifi_access_api.models.websocket import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -194,6 +195,9 @@ class UnifiAccessCoordinator(DataUpdateCoordinator[UnifiAccessData]):
|
||||
|
||||
supports_lock_rules = bool(door_lock_rules) or bool(unconfirmed_lock_rule_doors)
|
||||
|
||||
current_ids = {door.id for door in doors} | {self.config_entry.entry_id}
|
||||
self._remove_stale_devices(current_ids)
|
||||
|
||||
return UnifiAccessData(
|
||||
doors={door.id: door for door in doors},
|
||||
emergency=emergency,
|
||||
@@ -221,6 +225,23 @@ class UnifiAccessCoordinator(DataUpdateCoordinator[UnifiAccessData]):
|
||||
except ApiNotFoundError:
|
||||
return None
|
||||
|
||||
@callback
|
||||
def _remove_stale_devices(self, current_ids: set[str]) -> None:
|
||||
"""Remove devices for doors that no longer exist on the hub."""
|
||||
device_registry = dr.async_get(self.hass)
|
||||
for device in dr.async_entries_for_config_entry(
|
||||
device_registry, self.config_entry.entry_id
|
||||
):
|
||||
if any(
|
||||
identifier[0] == DOMAIN and identifier[1] in current_ids
|
||||
for identifier in device.identifiers
|
||||
):
|
||||
continue
|
||||
device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
|
||||
def _on_ws_connect(self) -> None:
|
||||
"""Handle WebSocket connection established."""
|
||||
_LOGGER.debug("WebSocket connected to UniFi Access")
|
||||
|
||||
@@ -64,7 +64,7 @@ rules:
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: Integration raises ConfigEntryAuthFailed and relies on Home Assistant core to surface reauth/repair issues, no custom repairs are defined.
|
||||
stale-devices: todo
|
||||
stale-devices: done
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
|
||||
@@ -23,8 +23,10 @@ from unifi_access_api.models.websocket import (
|
||||
WebsocketMessage,
|
||||
)
|
||||
|
||||
from homeassistant.components.unifi_access.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -353,3 +355,57 @@ async def test_ws_location_update_thumbnail_only_no_state(
|
||||
# Door state unchanged, thumbnail updated
|
||||
assert hass.states.get(FRONT_DOOR_BINARY_SENSOR).state == state_before
|
||||
assert hass.states.get(FRONT_DOOR_IMAGE).state != image_state_before
|
||||
|
||||
|
||||
async def test_stale_device_removed_on_refresh(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test that stale devices are automatically removed on data refresh."""
|
||||
# Verify both doors exist after initial setup
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-001")})
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-002")})
|
||||
|
||||
# Simulate door-002 being removed from the hub
|
||||
mock_client.get_doors.return_value = [
|
||||
door for door in mock_client.get_doors.return_value if door.id != "door-002"
|
||||
]
|
||||
|
||||
# Trigger natural refresh via WebSocket reconnect
|
||||
on_disconnect = mock_client.start_websocket.call_args[1]["on_disconnect"]
|
||||
on_connect = mock_client.start_websocket.call_args[1]["on_connect"]
|
||||
on_disconnect()
|
||||
await hass.async_block_till_done()
|
||||
on_connect()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# door-001 still exists, door-002 was removed
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-001")})
|
||||
assert not device_registry.async_get_device(identifiers={(DOMAIN, "door-002")})
|
||||
|
||||
|
||||
async def test_stale_device_removed_on_startup(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test stale devices present before setup are removed on initial refresh."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
# Create a stale door device that no longer exists on the hub
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=mock_config_entry.entry_id,
|
||||
identifiers={(DOMAIN, "door-003")},
|
||||
)
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-003")})
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Valid doors from the hub should exist, stale device should be removed
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-001")})
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "door-002")})
|
||||
assert not device_registry.async_get_device(identifiers={(DOMAIN, "door-003")})
|
||||
|
||||
Reference in New Issue
Block a user