1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-28 04:33:49 +01:00

Fix Shelly orphaned entity removal logic to handle sub-devices (#152195)

This commit is contained in:
Shay Levy
2025-09-13 12:11:47 +03:00
committed by GitHub
parent d9a757c7e6
commit 3955391cda
4 changed files with 72 additions and 17 deletions

View File

@@ -682,20 +682,20 @@ def async_remove_orphaned_entities(
): ):
return return
device_id = devices[0].id for device in devices:
entities = er.async_entries_for_device(entity_reg, device_id, True) entities = er.async_entries_for_device(entity_reg, device.id, True)
for entity in entities: for entity in entities:
if not entity.entity_id.startswith(platform): if not entity.entity_id.startswith(platform):
continue continue
if key_suffix is not None and key_suffix not in entity.unique_id: if key_suffix is not None and key_suffix not in entity.unique_id:
continue continue
# we are looking for the component ID, e.g. boolean:201, em1data:1 # we are looking for the component ID, e.g. boolean:201, em1data:1
if not (match := COMPONENT_ID_PATTERN.search(entity.unique_id)): if not (match := COMPONENT_ID_PATTERN.search(entity.unique_id)):
continue continue
key = match.group() key = match.group()
if key not in keys: if key not in keys:
orphaned_entities.append(entity.unique_id.split("-", 1)[1]) orphaned_entities.append(entity.unique_id.split("-", 1)[1])
if orphaned_entities: if orphaned_entities:
async_remove_shelly_rpc_entities(hass, platform, mac, orphaned_entities) async_remove_shelly_rpc_entities(hass, platform, mac, orphaned_entities)

View File

@@ -156,6 +156,17 @@ def register_device(
) )
def register_sub_device(
device_registry: DeviceRegistry, config_entry: ConfigEntry, unique_id: str
) -> DeviceEntry:
"""Register Shelly sub-device."""
return device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, f"{MOCK_MAC}-{unique_id}")},
via_device=(DOMAIN, format_mac(MOCK_MAC)),
)
async def snapshot_device_entities( async def snapshot_device_entities(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,

View File

@@ -21,6 +21,7 @@ from . import (
mutate_rpc_device_status, mutate_rpc_device_status,
register_device, register_device,
register_entity, register_entity,
register_sub_device,
) )
from tests.common import mock_restore_cache from tests.common import mock_restore_cache
@@ -475,8 +476,10 @@ async def test_rpc_remove_virtual_binary_sensor_when_orphaned(
) -> None: ) -> None:
"""Check whether the virtual binary sensor will be removed if it has been removed from the device configuration.""" """Check whether the virtual binary sensor will be removed if it has been removed from the device configuration."""
config_entry = await init_integration(hass, 3, skip_setup=True) config_entry = await init_integration(hass, 3, skip_setup=True)
# create orphaned entity on main device
device_entry = register_device(device_registry, config_entry) device_entry = register_device(device_registry, config_entry)
entity_id = register_entity( entity_id1 = register_entity(
hass, hass,
BINARY_SENSOR_DOMAIN, BINARY_SENSOR_DOMAIN,
"test_name_boolean_200", "test_name_boolean_200",
@@ -485,10 +488,29 @@ async def test_rpc_remove_virtual_binary_sensor_when_orphaned(
device_id=device_entry.id, device_id=device_entry.id,
) )
# create orphaned entity on sub device
sub_device_entry = register_sub_device(
device_registry,
config_entry,
"boolean:201-boolean",
)
entity_id2 = register_entity(
hass,
BINARY_SENSOR_DOMAIN,
"boolean_201",
"boolean:201-boolean",
config_entry,
device_id=sub_device_entry.id,
)
assert entity_registry.async_get(entity_id1) is not None
assert entity_registry.async_get(entity_id2) is not None
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entity_registry.async_get(entity_id) is None assert entity_registry.async_get(entity_id1) is None
assert entity_registry.async_get(entity_id2) is None
async def test_blu_trv_binary_sensor_entity( async def test_blu_trv_binary_sensor_entity(

View File

@@ -36,6 +36,7 @@ from . import (
inject_rpc_device_event, inject_rpc_device_event,
register_device, register_device,
register_entity, register_entity,
register_sub_device,
) )
from tests.common import async_fire_time_changed, mock_restore_cache from tests.common import async_fire_time_changed, mock_restore_cache
@@ -720,8 +721,10 @@ async def test_rpc_remove_virtual_switch_when_orphaned(
) -> None: ) -> None:
"""Check whether the virtual switch will be removed if it has been removed from the device configuration.""" """Check whether the virtual switch will be removed if it has been removed from the device configuration."""
config_entry = await init_integration(hass, 3, skip_setup=True) config_entry = await init_integration(hass, 3, skip_setup=True)
# create orphaned entity on main device
device_entry = register_device(device_registry, config_entry) device_entry = register_device(device_registry, config_entry)
entity_id = register_entity( entity_id1 = register_entity(
hass, hass,
SWITCH_DOMAIN, SWITCH_DOMAIN,
"test_name_boolean_200", "test_name_boolean_200",
@@ -730,10 +733,29 @@ async def test_rpc_remove_virtual_switch_when_orphaned(
device_id=device_entry.id, device_id=device_entry.id,
) )
# create orphaned entity on sub device
sub_device_entry = register_sub_device(
device_registry,
config_entry,
"boolean:201-boolean",
)
entity_id2 = register_entity(
hass,
SWITCH_DOMAIN,
"boolean_201",
"boolean:201-boolean",
config_entry,
device_id=sub_device_entry.id,
)
assert entity_registry.async_get(entity_id1) is not None
assert entity_registry.async_get(entity_id2) is not None
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entity_registry.async_get(entity_id) is None assert entity_registry.async_get(entity_id1) is None
assert entity_registry.async_get(entity_id2) is None
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")