From d26c7a0536983847832352f58a3e877b95dab4e4 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 28 Nov 2024 08:27:24 +0100 Subject: [PATCH] Log warning if via_device reference not exists when creating or updating a device registry entry (#131746) --- homeassistant/helpers/device_registry.py | 11 +++++- tests/helpers/test_device_registry.py | 50 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 0e56adc7377..5dfd5d9e8a9 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -38,6 +38,7 @@ from .deprecation import ( check_if_deprecated_constant, dir_with_deprecated_constants, ) +from .frame import ReportBehavior, report_usage from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes, json_fragment from .registry import BaseRegistry, BaseRegistryItems, RegistryIndexType from .singleton import singleton @@ -821,7 +822,15 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]): name = default_name if via_device is not None and via_device is not UNDEFINED: - via = self.async_get_device(identifiers={via_device}) + if (via := self.async_get_device(identifiers={via_device})) is None: + report_usage( + "calls `device_registry.async_get_or_create` referencing a " + f"non existing `via_device` {via_device}, " + f"with device info: {device_info}", + core_behavior=ReportBehavior.LOG, + breaks_in_ha_version="2025.12.0", + ) + via_device_id: str | UndefinedType = via.id if via else UNDEFINED else: via_device_id = UNDEFINED diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 837400d502d..2335b1b93bd 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1482,7 +1482,9 @@ async def test_removing_area_id( async def test_specifying_via_device_create( - hass: HomeAssistant, device_registry: dr.DeviceRegistry + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + caplog: pytest.LogCaptureFixture, ) -> None: """Test specifying a via_device and removal of the hub device.""" config_entry_1 = MockConfigEntry() @@ -1513,9 +1515,32 @@ async def test_specifying_via_device_create( light = device_registry.async_get_device(identifiers={("hue", "456")}) assert light.via_device_id is None + # A device with a non existing via_device reference should create + light_via_nonexisting_parent_device = device_registry.async_get_or_create( + config_entry_id=config_entry_2.entry_id, + connections=set(), + identifiers={("hue", "789")}, + manufacturer="manufacturer", + model="light", + via_device=("hue", "non_existing_123"), + ) + assert { + "calls `device_registry.async_get_or_create` " + "referencing a non existing `via_device` " + '("hue","non_existing_123")' in caplog.text + } + assert light_via_nonexisting_parent_device is not None + assert light_via_nonexisting_parent_device.via_device_id is None + nonexisting_parent_device = device_registry.async_get_device( + identifiers={("hue", "non_existing_123")} + ) + assert nonexisting_parent_device is None + async def test_specifying_via_device_update( - hass: HomeAssistant, device_registry: dr.DeviceRegistry + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + caplog: pytest.LogCaptureFixture, ) -> None: """Test specifying a via_device and updating.""" config_entry_1 = MockConfigEntry() @@ -1529,6 +1554,7 @@ async def test_specifying_via_device_update( identifiers={("hue", "456")}, manufacturer="manufacturer", model="light", + name="Light", via_device=("hue", "0123"), ) @@ -1552,6 +1578,26 @@ async def test_specifying_via_device_update( ) assert light.via_device_id == via.id + assert light.name == "Light" + + # Try updating with a non existing via device + light = device_registry.async_get_or_create( + config_entry_id=config_entry_2.entry_id, + connections=set(), + identifiers={("hue", "456")}, + manufacturer="manufacturer", + model="light", + name="New light", + via_device=("hue", "non_existing_abc"), + ) + assert { + "calls `device_registry.async_get_or_create` " + "referencing a non existing `via_device` " + '("hue","non_existing_123")' in caplog.text + } + # Assert the name was updated correctly + assert light.via_device_id == via.id + assert light.name == "New light" async def test_loading_saving_data(