diff --git a/homeassistant/components/hue/device_trigger.py b/homeassistant/components/hue/device_trigger.py index 100467d77256..80282972b3a8 100644 --- a/homeassistant/components/hue/device_trigger.py +++ b/homeassistant/components/hue/device_trigger.py @@ -47,8 +47,9 @@ async def async_validate_trigger_config( ) is None: raise InvalidDeviceAutomationConfig(f"Device ID {device_id} is not valid") + entry_ids = _device_config_entry_ids(device_entry) for entry in entries: - if entry.entry_id not in device_entry.config_entries: + if entry.entry_id not in entry_ids: continue bridge = entry.runtime_data if bridge.api_version == 1: @@ -72,11 +73,12 @@ async def async_attach_trigger( ) is None: raise InvalidDeviceAutomationConfig(f"Device ID {device_id} is not valid") + entry_ids = _device_config_entry_ids(device_entry) entry: HueConfigEntry | None = next( ( entry for entry in hass.config_entries.async_entries(DOMAIN) - if entry.entry_id in device_entry.config_entries + if entry.entry_id in entry_ids ), None, ) @@ -112,8 +114,9 @@ async def async_get_triggers( # Iterate all config entries for this device # and work out the bridge version + entry_ids = _device_config_entry_ids(device_entry) for entry in entries: - if entry.entry_id not in device_entry.config_entries: + if entry.entry_id not in entry_ids: continue bridge = entry.runtime_data @@ -123,6 +126,17 @@ async def async_get_triggers( return [] +def _device_config_entry_ids(device_entry: dr.DeviceEntry) -> set[str]: + """Return the ids of the config entries the device belongs to. + + A restored composite has no single owning config entry; the union of the + split devices' config entries covers every owning domain. + """ + if device_entry.is_composite_device: + return device_entry.config_entries + return {device_entry.config_entry_id} + + async def _async_attach_bridge_trigger( entry: HueConfigEntry, device_entry: dr.DeviceEntry, diff --git a/tests/components/hue/test_device_trigger_v2.py b/tests/components/hue/test_device_trigger_v2.py index 834af956a20f..04dbdca61609 100644 --- a/tests/components/hue/test_device_trigger_v2.py +++ b/tests/components/hue/test_device_trigger_v2.py @@ -4,6 +4,7 @@ from typing import Any from unittest.mock import Mock, patch from aiohue.v2.models.button import ButtonEvent +import attr import pytest from pytest_unordered import unordered @@ -159,6 +160,79 @@ async def test_get_triggers( assert triggers == unordered(expected_triggers) +async def test_get_triggers_for_composite_device_id( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_bridge_v2: Mock, + v2_resources_test_data: JsonArrayType, + device_registry: dr.DeviceRegistry, +) -> None: + """Test we get the expected triggers for a pre-migration composite device id.""" + await mock_bridge_v2.api.load_test_data(v2_resources_test_data) + await setup_platform( + hass, mock_bridge_v2, [Platform.BINARY_SENSOR, Platform.SENSOR] + ) + hue_wall_switch_device = device_registry.async_get_device_by_identifier( + (hue.DOMAIN, WALL_SWITCH_DEVICE_ID), mock_bridge_v2.config_entry.entry_id + ) + other_entry = MockConfigEntry(domain="other") + other_entry.add_to_hass(hass) + other_device = device_registry.async_get_or_create( + config_entry_id=other_entry.entry_id, identifiers={("other", "1")} + ) + composite_id = "composite00000000000000000000ab" + # Simulate a migration split: both devices carry the pre-migration composite id + device_registry._devices[hue_wall_switch_device.id] = attr.evolve( + hue_wall_switch_device, composite_device_id=composite_id + ) + device_registry._devices[other_device.id] = attr.evolve( + other_device, composite_device_id=composite_id + ) + + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, composite_id + ) + + hue_bat_sensor = entity_registry.async_get( + "sensor.wall_switch_with_2_controls_battery" + ) + trigger_batt = { + "platform": "device", + "domain": "sensor", + "device_id": composite_id, + "type": "battery_level", + "entity_id": hue_bat_sensor.id, + "metadata": {"secondary": True}, + } + expected_triggers = [ + trigger_batt, + *( + { + "platform": "device", + "domain": hue.DOMAIN, + "device_id": composite_id, + "unique_id": resource_id, + "type": event_type.value, + "subtype": control_id, + "metadata": {}, + } + for event_type in ( + ButtonEvent.INITIAL_PRESS, + ButtonEvent.LONG_RELEASE, + ButtonEvent.REPEAT, + ButtonEvent.LONG_PRESS, + ButtonEvent.SHORT_RELEASE, + ) + for control_id, resource_id in ( + (1, "c658d3d8-a013-4b81-8ac6-78b248537e70"), + (2, "be1eb834-bdf5-4d26-8fba-7b1feaa83a9d"), + ) + ), + ] + + assert triggers == unordered(expected_triggers) + + async def test_get_triggers_for_removed_device( hass: HomeAssistant, mock_bridge_v2: Mock,