From e0614953a23de9ef9becb85ff71c699cbee4f0bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 May 2022 09:47:14 -1000 Subject: [PATCH] Add support for async_remove_config_entry_device to homekit_controller (#72630) --- .../components/homekit_controller/__init__.py | 18 +++++++- tests/components/homekit_controller/common.py | 14 +++++++ .../homekit_controller/test_init.py | 42 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 6b538658b23..6909b226556 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -15,9 +15,10 @@ from aiohomekit.model.characteristics import ( from aiohomekit.model.services import Service, ServicesTypes from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ATTR_IDENTIFIERS, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.typing import ConfigType @@ -261,3 +262,18 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: "HomeKit again", entry.title, ) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove homekit_controller config entry from a device.""" + hkid = config_entry.data["AccessoryPairingID"] + connection: HKDevice = hass.data[KNOWN_DEVICES][hkid] + return not device_entry.identifiers.intersection( + identifier + for accessory in connection.entity_map.accessories + for identifier in connection.device_info_for_accessory(accessory)[ + ATTR_IDENTIFIERS + ] + ) diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index 8f59fae8639..749bd4b0f07 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -372,3 +372,17 @@ async def assert_devices_and_entities_created( # Root device must not have a via, otherwise its not the device assert root_device.via_device_id is None + + +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"] diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index 03694e7186a..820b89e587d 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -8,9 +8,17 @@ from aiohomekit.model.services import ServicesTypes from homeassistant.components.homekit_controller.const import ENTITY_MAP from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant +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 .common import Helper, remove_device from tests.components.homekit_controller.common import setup_test_component +ALIVE_DEVICE_NAME = "Light Bulb" +ALIVE_DEEVICE_ENTITY_ID = "light.testdevice" + def create_motion_sensor_service(accessory): """Define motion characteristics as per page 225 of HAP spec.""" @@ -47,3 +55,37 @@ async def test_async_remove_entry(hass: HomeAssistant): assert len(controller.pairings) == 0 assert hkid not in hass.data[ENTITY_MAP].storage_data + + +def create_alive_service(accessory): + """Create a service to validate we can only remove dead devices.""" + service = accessory.add_service(ServicesTypes.LIGHTBULB, name=ALIVE_DEVICE_NAME) + service.add_char(CharacteristicsTypes.ON) + return service + + +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", {}) + helper: Helper = await setup_test_component(hass, create_alive_service) + config_entry = helper.config_entry + entry_id = config_entry.entry_id + + registry: EntityRegistry = er.async_get(hass) + entity = registry.entities[ALIVE_DEEVICE_ENTITY_ID] + device_registry = dr.async_get(hass) + + live_device_entry = device_registry.async_get(entity.device_id) + assert ( + await remove_device(await hass_ws_client(hass), live_device_entry.id, entry_id) + is False + ) + + dead_device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={("homekit_controller:accessory-id", "E9:88:E7:B8:B4:40:aid:1")}, + ) + assert ( + await remove_device(await hass_ws_client(hass), dead_device_entry.id, entry_id) + is True + )