From 492c6dd068547ca7d89f1ea71bf61bd56d29d883 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Jun 2025 13:18:45 +0200 Subject: [PATCH] cover --- homeassistant/components/esphome/manager.py | 5 +- tests/components/esphome/test_manager.py | 150 ++++++++++++++++++++ 2 files changed, 151 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index 84c9b36f838..5f57e402d49 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -823,9 +823,6 @@ def _async_setup_device_registry( areas_by_id = {area.area_id: area for area in device_info.areas} # Create/update sub devices that should exist for sub_device in device_info.devices: - # Create a unique identifier for this sub device - sub_device_identifier = f"{device_info.mac_address}_{sub_device.device_id}" - # Determine the area for this sub device sub_device_suggested_area: str | None = None if sub_device.area_id and sub_device.area_id in areas_by_id: @@ -833,7 +830,7 @@ def _async_setup_device_registry( sub_device_entry = device_registry.async_get_or_create( config_entry_id=entry.entry_id, - identifiers={(DOMAIN, sub_device_identifier)}, + identifiers={(DOMAIN, f"{device_info.mac_address}_{sub_device.device_id}")}, name=sub_device.name, manufacturer=manufacturer, model=model, diff --git a/tests/components/esphome/test_manager.py b/tests/components/esphome/test_manager.py index dfadf6ad6d7..7839077ce42 100644 --- a/tests/components/esphome/test_manager.py +++ b/tests/components/esphome/test_manager.py @@ -1500,3 +1500,153 @@ async def test_assist_in_progress_issue_deleted( ) is None ) + + +async def test_sub_device_creation( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Test sub devices are created in device registry.""" + device_registry = dr.async_get(hass) + + # Define areas + areas = [ + {"area_id": 1, "name": "Living Room"}, + {"area_id": 2, "name": "Bedroom"}, + {"area_id": 3, "name": "Kitchen"}, + ] + + # Define sub devices + sub_devices = [ + {"device_id": 11111111, "name": "Motion Sensor", "area_id": 1}, + {"device_id": 22222222, "name": "Light Switch", "area_id": 1}, + {"device_id": 33333333, "name": "Temperature Sensor", "area_id": 2}, + ] + + device_info = { + "areas": areas, + "devices": sub_devices, + "area": {"area_id": 0, "name": "Main Hub"}, + } + + device = await mock_esphome_device( + mock_client=mock_client, + device_info=device_info, + ) + + # Check main device is created + main_device = device_registry.async_get_device( + connections={(dr.CONNECTION_NETWORK_MAC, device.entry.unique_id)} + ) + assert main_device is not None + assert main_device.suggested_area == "Main Hub" + + # Check sub devices are created + sub_device_1 = device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_11111111")} + ) + assert sub_device_1 is not None + assert sub_device_1.name == "Motion Sensor" + assert sub_device_1.suggested_area == "Living Room" + assert sub_device_1.via_device_id == main_device.id + + sub_device_2 = device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_22222222")} + ) + assert sub_device_2 is not None + assert sub_device_2.name == "Light Switch" + assert sub_device_2.suggested_area == "Living Room" + assert sub_device_2.via_device_id == main_device.id + + sub_device_3 = device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_33333333")} + ) + assert sub_device_3 is not None + assert sub_device_3.name == "Temperature Sensor" + assert sub_device_3.suggested_area == "Bedroom" + assert sub_device_3.via_device_id == main_device.id + + +async def test_sub_device_cleanup( + hass: HomeAssistant, + mock_client: APIClient, + mock_esphome_device: MockESPHomeDeviceType, +) -> None: + """Test sub devices are removed when they no longer exist.""" + device_registry = dr.async_get(hass) + + # Initial sub devices + sub_devices_initial = [ + {"device_id": 11111111, "name": "Device 1", "area_id": 0}, + {"device_id": 22222222, "name": "Device 2", "area_id": 0}, + {"device_id": 33333333, "name": "Device 3", "area_id": 0}, + ] + + device_info = { + "devices": sub_devices_initial, + } + + device = await mock_esphome_device( + mock_client=mock_client, + device_info=device_info, + ) + + # Verify all sub devices exist + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_11111111")} + ) + is not None + ) + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_22222222")} + ) + is not None + ) + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_33333333")} + ) + is not None + ) + + # Now update with fewer sub devices (device 2 removed) + sub_devices_updated = [ + {"device_id": 11111111, "name": "Device 1", "area_id": 0}, + {"device_id": 33333333, "name": "Device 3", "area_id": 0}, + ] + + # Update device info + device.device_info = DeviceInfo( + name="test", + friendly_name="Test", + esphome_version="1.0.0", + mac_address="11:22:33:44:55:AA", + devices=sub_devices_updated, + ) + + # Simulate reconnection which triggers device registry update + await device.mock_connect() + await hass.async_block_till_done() + + # Verify device 2 was removed + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_11111111")} + ) + is not None + ) + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_22222222")} + ) + is None + ) # Should be removed + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, f"{device.entry.unique_id}_33333333")} + ) + is not None + )