diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 570ec5983fe..e8df7e1072d 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -21,6 +21,7 @@ from homeassistant.exceptions import ( ConfigEntryNotReady, HomeAssistantError, ) +from homeassistant.helpers import device_registry as dr from .activity import ActivityStream from .const import DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS @@ -283,12 +284,15 @@ class AugustData(AugustSubscriberMixin): device.device_id, ) - def _get_device_name(self, device_id): + def get_device(self, device_id: str) -> Doorbell | Lock | None: + """Get a device by id.""" + return self._locks_by_id.get(device_id) or self._doorbells_by_id.get(device_id) + + def _get_device_name(self, device_id: str) -> str | None: """Return doorbell or lock name as August has it stored.""" - if device_id in self._locks_by_id: - return self._locks_by_id[device_id].device_name - if device_id in self._doorbells_by_id: - return self._doorbells_by_id[device_id].device_name + if device := self.get_device(device_id): + return device.device_name + return None async def async_lock(self, device_id): """Lock the device.""" @@ -403,3 +407,15 @@ def _restore_live_attrs(lock_detail, attrs): """Restore the non-cache attributes after a cached update.""" for attr, value in attrs.items(): setattr(lock_detail, attr, value) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove august config entry from a device if its no longer present.""" + data: AugustData = hass.data[DOMAIN][config_entry.entry_id] + return not any( + identifier + for identifier in device_entry.identifiers + if identifier[0] == DOMAIN and data.get_device(identifier[1]) + ) diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index e419488becc..c93e6429f1c 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -1,7 +1,10 @@ """Mocks for the august component.""" +from __future__ import annotations + import json import os import time +from typing import Any, Iterable from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from yalexs.activity import ( @@ -26,7 +29,9 @@ from yalexs.lock import Lock, LockDetail from yalexs.pubnub_async import AugustPubNub from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -76,9 +81,13 @@ async def _mock_setup_august( async def _create_august_with_devices( - hass, devices, api_call_side_effects=None, activities=None, pubnub=None -): - entry, api_instance = await _create_august_api_with_devices( + hass: HomeAssistant, + devices: Iterable[LockDetail | DoorbellDetail], + api_call_side_effects: dict[str, Any] | None = None, + activities: list[Any] | None = None, + pubnub: AugustPubNub | None = None, +) -> ConfigEntry: + entry, _ = await _create_august_api_with_devices( hass, devices, api_call_side_effects, activities, pubnub ) return entry diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index 320461ca6e9..56113832d23 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -17,6 +17,9 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity_registry import EntityRegistry +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.components.august.mocks import ( @@ -318,3 +321,46 @@ async def test_load_unload(hass): await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done() + + +async def remove_device(ws_client, device_id, config_entry_id): + """Remove config entry from a device.""" + await ws_client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": config_entry_id, + "device_id": device_id, + } + ) + response = await ws_client.receive_json() + return response["success"] + + +async def test_device_remove_devices(hass, hass_ws_client): + """Test we can only remove a device that no longer exists.""" + assert await async_setup_component(hass, "config", {}) + august_operative_lock = await _mock_operative_august_lock_detail(hass) + config_entry = await _create_august_with_devices(hass, [august_operative_lock]) + registry: EntityRegistry = er.async_get(hass) + entity = registry.entities["lock.a6697750d607098bae8d6baa11ef8063_name"] + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get(entity.device_id) + assert ( + await remove_device( + await hass_ws_client(hass), device_entry.id, config_entry.entry_id + ) + is False + ) + + dead_device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, "remove-device-id")}, + ) + assert ( + await remove_device( + await hass_ws_client(hass), dead_device_entry.id, config_entry.entry_id + ) + is True + )