diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index d199d2c5a2c..8cb41ebcbe1 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -18,7 +18,7 @@ from homeassistant.const import ( ) from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.device_registry import DeviceEntry, DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -103,6 +103,24 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok +async def async_remove_config_entry_device( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> bool: + """Remove Fritzbox config entry from a device.""" + coordinator: FritzboxDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + CONF_COORDINATOR + ] + + for identifier in device.identifiers: + if identifier[0] == DOMAIN and ( + identifier[1] in coordinator.data.devices + or identifier[1] in coordinator.data.templates + ): + return False + + return True + + class FritzBoxEntity(CoordinatorEntity[FritzboxDataUpdateCoordinator], ABC): """Basis FritzBox entity.""" diff --git a/tests/components/fritzbox/test_init.py b/tests/components/fritzbox/test_init.py index dd5a8127185..b07b8225c3e 100644 --- a/tests/components/fritzbox/test_init.py +++ b/tests/components/fritzbox/test_init.py @@ -21,12 +21,14 @@ from homeassistant.const import ( UnitOfTemperature, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.setup import async_setup_component from . import FritzDeviceSwitchMock, setup_config_entry from .const import CONF_FAKE_AIN, CONF_FAKE_NAME, MOCK_CONFIG from tests.common import MockConfigEntry +from tests.typing import WebSocketGenerator async def test_setup(hass: HomeAssistant, fritz: Mock) -> None: @@ -250,6 +252,68 @@ async def test_unload_remove(hass: HomeAssistant, fritz: Mock) -> None: assert state is None +async def test_remove_device( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + hass_ws_client: WebSocketGenerator, + fritz: Mock, +) -> None: + """Test removing of a device.""" + assert await async_setup_component(hass, "config", {}) + assert await setup_config_entry( + hass, + MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], + f"{FB_DOMAIN}.{CONF_FAKE_NAME}", + FritzDeviceSwitchMock(), + fritz, + ) + await hass.async_block_till_done() + + entries = hass.config_entries.async_entries() + assert len(entries) == 1 + + entry = entries[0] + assert entry.supports_remove_device + + entity = entity_registry.async_get("switch.fake_name") + good_device = device_registry.async_get(entity.device_id) + + orphan_device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(FB_DOMAIN, "0000 000000")}, + ) + + # try to delete good_device + ws_client = await hass_ws_client(hass) + await ws_client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry.entry_id, + "device_id": good_device.id, + } + ) + response = await ws_client.receive_json() + assert not response["success"] + assert response["error"]["code"] == "unknown_error" + await hass.async_block_till_done() + + # try to delete orphan_device + ws_client = await hass_ws_client(hass) + await ws_client.send_json( + { + "id": 5, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": entry.entry_id, + "device_id": orphan_device.id, + } + ) + response = await ws_client.receive_json() + assert response["success"] + await hass.async_block_till_done() + + async def test_raise_config_entry_not_ready_when_offline(hass: HomeAssistant) -> None: """Config entry state is SETUP_RETRY when fritzbox is offline.""" entry = MockConfigEntry(