"""Tests for Search integration.""" import attr from pytest_unordered import unordered from homeassistant.components.search import DOMAIN, ItemType, Searcher from homeassistant.core import HomeAssistant from homeassistant.helpers import ( area_registry as ar, device_registry as dr, entity_registry as er, floor_registry as fr, label_registry as lr, ) from homeassistant.helpers.entity import EntityInfo from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.typing import WebSocketGenerator async def test_search( hass: HomeAssistant, area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, floor_registry: fr.FloorRegistry, label_registry: lr.LabelRegistry, hass_ws_client: WebSocketGenerator, ) -> None: """Test search.""" assert await async_setup_component(hass, DOMAIN, {}) # Labels label_energy = label_registry.async_create("Energy") label_christmas = label_registry.async_create("Christmas") label_other = label_registry.async_create("Other") # Floors first_floor = floor_registry.async_create("First Floor") second_floor = floor_registry.async_create("Second Floor") # Areas bedroom_area = area_registry.async_create( "Bedroom", floor_id=second_floor.floor_id, labels={label_other.label_id} ) kitchen_area = area_registry.async_create("Kitchen", floor_id=first_floor.floor_id) living_room_area = area_registry.async_create( "Living Room", floor_id=first_floor.floor_id ) # Config entries hue_config_entry = MockConfigEntry(domain="hue") hue_config_entry.add_to_hass(hass) wled_config_entry = MockConfigEntry(domain="wled") wled_config_entry.add_to_hass(hass) # Devices hue_device = device_registry.async_get_or_create( config_entry_id=hue_config_entry.entry_id, name="Light Strip", identifiers={("hue", "hue-1")}, ) device_registry.async_update_device(hue_device.id, area_id=kitchen_area.id) wled_device = device_registry.async_get_or_create( config_entry_id=wled_config_entry.entry_id, name="Light Strip", identifiers=({"wled", "wled-1"}), ) device_registry.async_update_device( wled_device.id, area_id=living_room_area.id, labels={label_christmas.label_id} ) # Entities hue_segment_1_entity = entity_registry.async_get_or_create( "light", "hue", "hue-1-seg-1", suggested_object_id="hue segment 1", config_entry=hue_config_entry, device_id=hue_device.id, ) entity_registry.async_update_entity( hue_segment_1_entity.entity_id, labels={label_energy.label_id} ) hue_segment_2_entity = entity_registry.async_get_or_create( "light", "hue", "hue-1-seg-2", suggested_object_id="hue segment 2", config_entry=hue_config_entry, device_id=hue_device.id, ) wled_segment_1_entity = entity_registry.async_get_or_create( "light", "wled", "wled-1-seg-1", suggested_object_id="wled segment 1", config_entry=wled_config_entry, device_id=wled_device.id, ) wled_segment_2_entity = entity_registry.async_get_or_create( "light", "wled", "wled-1-seg-2", suggested_object_id="wled segment 2", config_entry=wled_config_entry, device_id=wled_device.id, ) entity_registry.async_update_entity( wled_segment_2_entity.entity_id, area_id=bedroom_area.id ) # Config entry with a device providing a scene entity esphome_config_entry = MockConfigEntry(domain="esphome") esphome_config_entry.add_to_hass(hass) esphome_device = device_registry.async_get_or_create( config_entry_id=esphome_config_entry.entry_id, name="Node", identifiers={("esphome", "esphome-1")}, ) esphome_scene_entity = entity_registry.async_get_or_create( "scene", "esphome", "esphome-1-scene", suggested_object_id="esphome scene", config_entry=esphome_config_entry, device_id=esphome_device.id, ) scene_wled_hue_entity = entity_registry.async_get_or_create( "scene", "homeassistant", "wled_hue", suggested_object_id="scene_wled_hue", ) entity_registry.async_update_entity( scene_wled_hue_entity.entity_id, area_id=bedroom_area.id, labels={label_other.label_id}, ) # Persons can technically be assigned to areas person_paulus_entity = entity_registry.async_get_or_create( "person", "person", "abcd", suggested_object_id="paulus", ) entity_registry.async_update_entity( person_paulus_entity.entity_id, area_id=bedroom_area.id, labels={label_other.label_id}, ) # Device tracker of the person, provided by a config entry with a device mobile_app_config_entry = MockConfigEntry(domain="mobile_app") mobile_app_config_entry.add_to_hass(hass) mobile_app_device = device_registry.async_get_or_create( config_entry_id=mobile_app_config_entry.entry_id, name="Paulus iPhone", identifiers={("mobile_app", "phone-1")}, ) entity_registry.async_get_or_create( "device_tracker", "mobile_app", "phone-1-tracker", suggested_object_id="paulus_iphone", config_entry=mobile_app_config_entry, device_id=mobile_app_device.id, ) script_scene_entity = entity_registry.async_get_or_create( "script", "script", "scene", suggested_object_id="scene", ) entity_registry.async_update_entity( script_scene_entity.entity_id, area_id=bedroom_area.id, labels={label_other.label_id}, ) # Entity sources entity_sources = { "light.wled_platform_config_source": EntityInfo( domain="wled", ), "light.wled_config_entry_source": EntityInfo( config_entry=wled_config_entry.entry_id, domain="wled", ), } # Groups await async_setup_component( hass, "group", { "group": { "wled": { "name": "wled", "entities": [ wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, ], }, "hue": { "name": "hue", "entities": [ hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, ], }, "wled_hue": { "name": "wled and hue", "entities": [ wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, ], }, } }, ) # Persons assert await async_setup_component( hass, "person", { "person": [ { "id": "abcd", "name": "Paulus", "device_trackers": ["device_tracker.paulus_iphone"], } ] }, ) # Scenes await async_setup_component( hass, "scene", { "scene": [ { "name": "scene_wled_seg_1", "entities": {wled_segment_1_entity.entity_id: "on"}, }, { "name": "scene_hue_seg_1", "entities": {hue_segment_1_entity.entity_id: "on"}, }, { "id": "wled_hue", "name": "scene_wled_hue", "entities": { wled_segment_1_entity.entity_id: "on", wled_segment_2_entity.entity_id: "on", hue_segment_1_entity.entity_id: "on", hue_segment_2_entity.entity_id: "on", }, }, ] }, ) # Scene entities are added by a task, wait for it to finish await hass.async_block_till_done() # Automations assert await async_setup_component( hass, "automation", { "automation": [ { "id": "unique_id", "alias": "blueprint_automation_1", "triggers": {"platform": "template", "value_template": "true"}, "use_blueprint": { "path": "test_event_service.yaml", "input": { "trigger_event": "blueprint_event_1", "service_to_call": "test.automation_1", "a_number": 5, }, }, }, { "alias": "blueprint_automation_2", "triggers": {"platform": "template", "value_template": "true"}, "use_blueprint": { "path": "test_event_service.yaml", "input": { "trigger_event": "blueprint_event_2", "service_to_call": "test.automation_2", "a_number": 5, }, }, }, { "alias": "wled_entity", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "test.script", "data": {"entity_id": wled_segment_1_entity.entity_id}, }, ], }, { "alias": "wled_device", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "domain": "light", "device_id": wled_device.id, "entity_id": wled_segment_1_entity.entity_id, "type": "turn_on", }, ], }, { "alias": "floor", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "test.script", "target": {"floor_id": first_floor.floor_id}, }, ], }, { "alias": "area", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "test.script", "target": {"area_id": kitchen_area.id}, }, ], }, { "alias": "group", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "homeassistant.turn_on", "target": {"entity_id": "group.wled_hue"}, }, ], }, { "alias": "scene", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "scene": scene_wled_hue_entity.entity_id, }, ], }, { "alias": "script", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "script.turn_on", "data": {"entity_id": script_scene_entity.entity_id}, }, ], }, { "alias": "label", "trigger": {"platform": "template", "value_template": "true"}, "action": [ { "service": "script.turn_on", "target": {"label_id": label_christmas.label_id}, }, ], }, ] }, ) # Scripts assert await async_setup_component( hass, "script", { "script": { "blueprint_script_1": { "use_blueprint": { "path": "test_service.yaml", "input": { "service_to_call": "test.automation", }, } }, "blueprint_script_2": { "use_blueprint": { "path": "test_service.yaml", "input": { "service_to_call": "test.automation", }, } }, "wled": { "sequence": [ { "service": "test.script", "data": {"entity_id": wled_segment_1_entity.entity_id}, }, ] }, "hue": { "sequence": [ { "service": "test.script", "data": {"entity_id": hue_segment_1_entity.entity_id}, }, ] }, "script_with_templated_services": { "sequence": [ { "service": "test.script", "target": "{{ {'entity_id':'test.test1'} }}", }, { "service": "test.script", "data": "{{ {'entity_id':'test.test2'} }}", }, { "service": "test.script", "data_template": "{{ {'entity_id':'test.test3'} }}", }, ] }, "device": { "sequence": [ { "service": "test.script", "target": {"device_id": hue_device.id}, }, ], }, "floor": { "sequence": [ { "service": "test.script", "target": {"floor_id": first_floor.floor_id}, }, ], }, "area": { "sequence": [ { "service": "test.script", "target": {"area_id": kitchen_area.id}, }, ], }, "group": { "sequence": [ { "service": "test.script", "target": {"entity_id": "group.wled_hue"}, }, ], }, "scene": { "sequence": [ { "scene": scene_wled_hue_entity.entity_id, }, ], }, "label": { "sequence": [ { "service": "test.script", "target": {"label_id": label_other.label_id}, }, ], }, "nested": { "sequence": [ { "service": "script.turn_on", "data": {"entity_id": script_scene_entity.entity_id}, }, ], }, } }, ) def search(item_type: ItemType, item_id: str) -> dict[str, set[str]]: """Search.""" searcher = Searcher(hass, entity_sources) return searcher.async_search(item_type, item_id) # # Tests # assert not search(ItemType.AREA, "unknown") assert search(ItemType.AREA, bedroom_area.id) == { ItemType.AUTOMATION: {"automation.scene", "automation.script"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.ENTITY: { wled_segment_2_entity.entity_id, scene_wled_hue_entity.entity_id, script_scene_entity.entity_id, person_paulus_entity.entity_id, }, ItemType.FLOOR: {second_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.LABEL: {label_other.label_id}, ItemType.PERSON: {person_paulus_entity.entity_id}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {script_scene_entity.entity_id, "script.nested"}, } assert search(ItemType.AREA, living_room_area.id) == { ItemType.AUTOMATION: {"automation.wled_device", "automation.wled_entity"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.wled"}, } assert search(ItemType.AREA, kitchen_area.id) == { ItemType.AUTOMATION: {"automation.area"}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: { hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.area", "script.device", "script.hue"}, } assert not search(ItemType.AUTOMATION, "automation.unknown") assert search(ItemType.AUTOMATION, "automation.blueprint_automation_1") == { ItemType.AUTOMATION_BLUEPRINT: {"test_event_service.yaml"}, ItemType.ENTITY: {"light.kitchen"}, } assert search(ItemType.AUTOMATION, "automation.blueprint_automation_2") == { ItemType.AUTOMATION_BLUEPRINT: {"test_event_service.yaml"}, ItemType.ENTITY: {"light.kitchen"}, } assert search(ItemType.AUTOMATION, "automation.wled_entity") == { ItemType.AREA: {living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.AUTOMATION, "automation.wled_device") == { ItemType.AREA: {living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.AUTOMATION, "automation.floor") == { ItemType.FLOOR: {first_floor.floor_id}, } assert search(ItemType.AUTOMATION, "automation.area") == { ItemType.AREA: {kitchen_area.id}, ItemType.FLOOR: {first_floor.floor_id}, } assert search(ItemType.AUTOMATION, "automation.label") == { ItemType.LABEL: {label_christmas.label_id}, } assert search(ItemType.AUTOMATION, "automation.group") == { ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { "group.wled_hue", wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.GROUP: {"group.wled_hue"}, ItemType.INTEGRATION: {"hue", "wled"}, } assert search(ItemType.AUTOMATION, "automation.scene") == { ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, scene_wled_hue_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, } assert search(ItemType.AUTOMATION, "automation.script") == { ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, scene_wled_hue_entity.entity_id, script_scene_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {script_scene_entity.entity_id}, } assert not search(ItemType.AUTOMATION_BLUEPRINT, "unknown.yaml") assert search(ItemType.AUTOMATION_BLUEPRINT, "test_event_service.yaml") == { ItemType.AUTOMATION: { "automation.blueprint_automation_1", "automation.blueprint_automation_2", } } assert not search(ItemType.CONFIG_ENTRY, "unknown") assert search(ItemType.CONFIG_ENTRY, hue_config_entry.entry_id) == { ItemType.AREA: {kitchen_area.id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: { hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.INTEGRATION: {"hue"}, ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.device", "script.hue"}, } assert search(ItemType.CONFIG_ENTRY, wled_config_entry.entry_id) == { ItemType.AREA: {bedroom_area.id, living_room_area.id}, ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.INTEGRATION: {"wled"}, ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.wled"}, } assert not search(ItemType.DEVICE, "unknown") assert search(ItemType.DEVICE, wled_device.id) == { ItemType.AREA: {bedroom_area.id, living_room_area.id}, ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.INTEGRATION: {"wled"}, ItemType.LABEL: {label_christmas.label_id}, ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.wled"}, } assert search(ItemType.DEVICE, hue_device.id) == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.ENTITY: { hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.INTEGRATION: {"hue"}, ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.device", "script.hue"}, } assert search(ItemType.DEVICE, esphome_device.id) == { ItemType.CONFIG_ENTRY: {esphome_config_entry.entry_id}, ItemType.ENTITY: {esphome_scene_entity.entity_id}, ItemType.INTEGRATION: {"esphome"}, ItemType.SCENE: {esphome_scene_entity.entity_id}, } assert search(ItemType.CONFIG_ENTRY, esphome_config_entry.entry_id) == { ItemType.DEVICE: {esphome_device.id}, ItemType.ENTITY: {esphome_scene_entity.entity_id}, ItemType.INTEGRATION: {"esphome"}, ItemType.SCENE: {esphome_scene_entity.entity_id}, } assert not search(ItemType.ENTITY, "sensor.unknown") assert search(ItemType.ENTITY, wled_segment_1_entity.entity_id) == { ItemType.AREA: {living_room_area.id}, ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.INTEGRATION: {"wled"}, ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.wled"}, } assert search(ItemType.ENTITY, wled_segment_2_entity.entity_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.FLOOR: {second_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.INTEGRATION: {"wled"}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, } assert search(ItemType.ENTITY, hue_segment_1_entity.entity_id) == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.INTEGRATION: {"hue"}, ItemType.LABEL: {label_energy.label_id}, ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.hue"}, } assert search(ItemType.ENTITY, hue_segment_2_entity.entity_id) == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.INTEGRATION: {"hue"}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, } assert not search(ItemType.ENTITY, "automation.wled") assert search(ItemType.ENTITY, script_scene_entity.entity_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.AUTOMATION: {"automation.script"}, ItemType.FLOOR: {second_floor.floor_id}, ItemType.LABEL: {label_other.label_id}, ItemType.SCRIPT: {"script.nested"}, } assert search(ItemType.ENTITY, "group.wled_hue") == { ItemType.AUTOMATION: {"automation.group"}, ItemType.SCRIPT: {"script.group"}, } assert search(ItemType.ENTITY, person_paulus_entity.entity_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.FLOOR: {second_floor.floor_id}, ItemType.LABEL: {label_other.label_id}, } assert search(ItemType.ENTITY, scene_wled_hue_entity.entity_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.AUTOMATION: {"automation.scene"}, ItemType.FLOOR: {second_floor.floor_id}, ItemType.LABEL: {label_other.label_id}, ItemType.SCRIPT: {script_scene_entity.entity_id}, } assert search(ItemType.ENTITY, "device_tracker.paulus_iphone") == { ItemType.CONFIG_ENTRY: {mobile_app_config_entry.entry_id}, ItemType.DEVICE: {mobile_app_device.id}, ItemType.INTEGRATION: {"mobile_app"}, ItemType.PERSON: {person_paulus_entity.entity_id}, } assert search(ItemType.ENTITY, "light.wled_config_entry_source") == { ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.INTEGRATION: {"wled"}, } assert not search(ItemType.FLOOR, "unknown") assert search(ItemType.FLOOR, first_floor.floor_id) == { ItemType.AREA: {kitchen_area.id, living_room_area.id}, ItemType.AUTOMATION: { "automation.area", "automation.floor", "automation.wled_device", "automation.wled_entity", }, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id, wled_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id, wled_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.GROUP: {"group.hue", "group.wled", "group.wled_hue"}, ItemType.SCENE: { "scene.scene_hue_seg_1", "scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id, }, ItemType.SCRIPT: { "script.device", "script.area", "script.floor", "script.hue", "script.wled", }, } assert search(ItemType.FLOOR, second_floor.floor_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.AUTOMATION: {"automation.scene", "automation.script"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.ENTITY: { wled_segment_2_entity.entity_id, person_paulus_entity.entity_id, scene_wled_hue_entity.entity_id, script_scene_entity.entity_id, }, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.PERSON: {person_paulus_entity.entity_id}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {script_scene_entity.entity_id, "script.nested"}, } assert not search(ItemType.GROUP, "group.unknown") assert search(ItemType.GROUP, "group.wled") == { ItemType.AREA: {bedroom_area.id, living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.GROUP, "group.hue") == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: { hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"hue"}, } assert search(ItemType.GROUP, "group.wled_hue") == { ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, ItemType.AUTOMATION: {"automation.group"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.SCRIPT: {"script.group"}, } assert not search(ItemType.INTEGRATION, "unknown") assert search(ItemType.INTEGRATION, "wled") == { ItemType.AREA: {bedroom_area.id, living_room_area.id}, ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, "light.wled_platform_config_source", "light.wled_config_entry_source", }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.GROUP: {"group.wled", "group.wled_hue"}, ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.wled"}, } assert search(ItemType.INTEGRATION, "hue") == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: { hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, ItemType.GROUP: {"group.hue", "group.wled_hue"}, ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.device", "script.hue"}, } assert not search(ItemType.LABEL, "unknown") assert search(ItemType.LABEL, label_christmas.label_id) == { ItemType.AREA: {living_room_area.id}, ItemType.AUTOMATION: {"automation.label"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.LABEL, label_energy.label_id) == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: {hue_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"hue"}, } assert search(ItemType.LABEL, label_other.label_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.ENTITY: { scene_wled_hue_entity.entity_id, person_paulus_entity.entity_id, script_scene_entity.entity_id, }, ItemType.FLOOR: {second_floor.floor_id}, ItemType.PERSON: {person_paulus_entity.entity_id}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {"script.label", script_scene_entity.entity_id}, } assert not search(ItemType.PERSON, "person.unknown") assert search(ItemType.PERSON, person_paulus_entity.entity_id) == { ItemType.AREA: {bedroom_area.id}, ItemType.CONFIG_ENTRY: {mobile_app_config_entry.entry_id}, ItemType.DEVICE: {mobile_app_device.id}, ItemType.ENTITY: {"device_tracker.paulus_iphone"}, ItemType.FLOOR: {second_floor.floor_id}, ItemType.INTEGRATION: {"mobile_app"}, ItemType.LABEL: {label_other.label_id}, } assert not search(ItemType.SCENE, "scene.unknown") assert search(ItemType.SCENE, "scene.scene_wled_seg_1") == { ItemType.AREA: {living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.SCENE, "scene.scene_hue_seg_1") == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: {hue_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"hue"}, } assert search(ItemType.SCENE, scene_wled_hue_entity.entity_id) == { ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, ItemType.AUTOMATION: {"automation.scene"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.LABEL: {label_other.label_id}, ItemType.SCRIPT: {script_scene_entity.entity_id}, } assert not search(ItemType.SCRIPT, "script.unknown") assert search(ItemType.SCRIPT, "script.blueprint_script_1") == { ItemType.ENTITY: {"light.kitchen"}, ItemType.SCRIPT_BLUEPRINT: {"test_service.yaml"}, } assert search(ItemType.SCRIPT, "script.blueprint_script_2") == { ItemType.ENTITY: {"light.kitchen"}, ItemType.SCRIPT_BLUEPRINT: {"test_service.yaml"}, } assert search(ItemType.SCRIPT, "script.wled") == { ItemType.AREA: {living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"wled"}, } assert search(ItemType.SCRIPT, "script.hue") == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.ENTITY: {hue_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"hue"}, } assert search(ItemType.SCRIPT, "script.script_with_templated_services") == {} assert search(ItemType.SCRIPT, "script.device") == { ItemType.AREA: {kitchen_area.id}, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id}, ItemType.FLOOR: {first_floor.floor_id}, ItemType.INTEGRATION: {"hue"}, } assert search(ItemType.SCRIPT, "script.floor") == { ItemType.FLOOR: {first_floor.floor_id}, } assert search(ItemType.SCRIPT, "script.area") == { ItemType.AREA: {kitchen_area.id}, ItemType.FLOOR: {first_floor.floor_id}, } assert search(ItemType.SCRIPT, "script.label") == { ItemType.LABEL: {label_other.label_id}, } assert search(ItemType.SCRIPT, "script.group") == { ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { "group.wled_hue", wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.GROUP: {"group.wled_hue"}, ItemType.INTEGRATION: {"hue", "wled"}, } assert search(ItemType.SCRIPT, script_scene_entity.entity_id) == { ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, scene_wled_hue_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.LABEL: {label_other.label_id}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, } assert search(ItemType.SCRIPT, "script.nested") == { ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id, hue_device.id}, ItemType.ENTITY: { wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, scene_wled_hue_entity.entity_id, script_scene_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, ItemType.INTEGRATION: {"hue", "wled"}, ItemType.SCENE: {scene_wled_hue_entity.entity_id}, ItemType.SCRIPT: {script_scene_entity.entity_id}, } assert not search(ItemType.SCRIPT_BLUEPRINT, "unknown.yaml") assert search(ItemType.SCRIPT_BLUEPRINT, "test_service.yaml") == { ItemType.SCRIPT: {"script.blueprint_script_1", "script.blueprint_script_2"}, } # WebSocket client = await hass_ws_client(hass) await client.send_json( { "id": 1, "type": "search/related", "item_type": "device", "item_id": hue_device.id, } ) response = await client.receive_json() assert response["success"] assert response["result"] == { ItemType.AREA: [kitchen_area.id], ItemType.ENTITY: unordered( [ hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, ] ), ItemType.GROUP: unordered( [ "group.hue", "group.wled_hue", ] ), ItemType.CONFIG_ENTRY: [hue_config_entry.entry_id], ItemType.FLOOR: [first_floor.floor_id], ItemType.INTEGRATION: ["hue"], ItemType.SCENE: unordered( ["scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id] ), ItemType.SCRIPT: unordered(["script.device", "script.hue"]), } async def test_search_pre_migration_composite_device( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test search maps between a pre-migration composite device and its splits. When a composite device is split into one device per config entry, each split device records the id of the pre-migration composite. Automations and scripts created before the split still reference the composite id, so: - searching a split device must return them, but not automations or scripts referencing only a sibling split, and - searching such an automation or script must return the live split devices, not the virtual composite id. """ assert await async_setup_component(hass, DOMAIN, {}) entry_1 = MockConfigEntry(domain="test1") entry_1.add_to_hass(hass) entry_2 = MockConfigEntry(domain="test2") entry_2.add_to_hass(hass) device_1 = device_registry.async_get_or_create( config_entry_id=entry_1.entry_id, identifiers={("test1", "1")} ) device_2 = device_registry.async_get_or_create( config_entry_id=entry_2.entry_id, identifiers={("test2", "2")} ) # Simulate a migration split: both devices carry the pre-migration composite id composite_device_id = "composite00000000000000000000ab" device_registry._devices[device_1.id] = attr.evolve( device_1, composite_device_id=composite_device_id ) device_registry._devices[device_2.id] = attr.evolve( device_2, composite_device_id=composite_device_id ) def device_action(device_id: str) -> dict[str, dict[str, str]]: """Return a service call action targeting a device.""" return {"service": "test.script", "target": {"device_id": device_id}} assert await async_setup_component( hass, "automation", { "automation": [ { "alias": "composite", "trigger": {"platform": "template", "value_template": "true"}, "action": [device_action(composite_device_id)], }, { "alias": "split_1", "trigger": {"platform": "template", "value_template": "true"}, "action": [device_action(device_1.id)], }, { "alias": "split_2", "trigger": {"platform": "template", "value_template": "true"}, "action": [device_action(device_2.id)], }, ] }, ) assert await async_setup_component( hass, "script", { "script": { "composite": {"sequence": [device_action(composite_device_id)]}, "split_2": {"sequence": [device_action(device_2.id)]}, } }, ) def search(item_type: ItemType, item_id: str) -> dict[str, set[str]]: """Search.""" searcher = Searcher(hass, {}) return searcher.async_search(item_type, item_id) # Forward: searching a split device returns automations and scripts referencing # the pre-migration composite and the split itself, but not references to a # sibling split. assert search(ItemType.DEVICE, device_1.id) == { ItemType.AUTOMATION: {"automation.composite", "automation.split_1"}, ItemType.SCRIPT: {"script.composite"}, ItemType.CONFIG_ENTRY: {entry_1.entry_id}, ItemType.INTEGRATION: {"test1"}, } assert search(ItemType.DEVICE, device_2.id) == { ItemType.AUTOMATION: {"automation.composite", "automation.split_2"}, ItemType.SCRIPT: {"script.composite", "script.split_2"}, ItemType.CONFIG_ENTRY: {entry_2.entry_id}, ItemType.INTEGRATION: {"test2"}, } # Reverse: searching an automation or script that references the composite returns # the live split devices, not the virtual composite id. expected_reverse = { ItemType.DEVICE: {device_1.id, device_2.id}, ItemType.CONFIG_ENTRY: {entry_1.entry_id, entry_2.entry_id}, ItemType.INTEGRATION: {"test1", "test2"}, } assert search(ItemType.AUTOMATION, "automation.composite") == expected_reverse assert search(ItemType.SCRIPT, "script.composite") == expected_reverse async def test_search_label_on_child_device( hass: HomeAssistant, area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, floor_registry: fr.FloorRegistry, label_registry: lr.LabelRegistry, ) -> None: """Test searching a label that is carried by a child device. A child device carrying a label is surfaced by a label search just like a mains device (dr.async_entries_for_label includes child devices). Resolving up the child yields the area it inherits from its parent (and that area's floor), plus the child's config entry and integration. The parent device is also returned: resolve-up follows the first-class child -> parent edge, which here contributes the same area / config entry / integration. """ assert await async_setup_component(hass, DOMAIN, {}) label = label_registry.async_create("Outlet") ground_floor = floor_registry.async_create("Ground Floor") utility_area = area_registry.async_create("Utility", floor_id=ground_floor.floor_id) config_entry = MockConfigEntry(domain="test") config_entry.add_to_hass(hass) parent_device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("test", "strip")}, name="Power strip", ) device_registry.async_update_device(parent_device.id, area_id=utility_area.id) child_device = device_registry.async_get_or_create_child( config_entry_id=config_entry.entry_id, identifiers={("test", "strip-outlet-1")}, parent_device_id=parent_device.id, name="Outlet 1", ) device_registry.async_update_child_device(child_device.id, labels={label.label_id}) searcher = Searcher(hass, {}) assert searcher.async_search(ItemType.LABEL, label.label_id) == { ItemType.DEVICE: {child_device.id, parent_device.id}, ItemType.AREA: {utility_area.id}, ItemType.FLOOR: {ground_floor.floor_id}, ItemType.CONFIG_ENTRY: {config_entry.entry_id}, ItemType.INTEGRATION: {"test"}, } async def test_search_child_devices( hass: HomeAssistant, area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, floor_registry: fr.FloorRegistry, ) -> None: """Test search surfaces the parent <-> child device relations. A config entry search surfaces the entry's child devices, which dr.async_entries_for_config_entry omits. Searching a parent device surfaces its child devices and their entities. Searching a child device surfaces its parent device (resolve-up), but not the parent's own entities: the parent is resolved up, not fully searched, so unrelated sibling children are not pulled in. """ assert await async_setup_component(hass, DOMAIN, {}) ground_floor = floor_registry.async_create("Ground Floor") utility_area = area_registry.async_create("Utility", floor_id=ground_floor.floor_id) config_entry = MockConfigEntry(domain="test") config_entry.add_to_hass(hass) parent_device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("test", "strip")}, name="Power strip", ) device_registry.async_update_device(parent_device.id, area_id=utility_area.id) child_device = device_registry.async_get_or_create_child( config_entry_id=config_entry.entry_id, identifiers={("test", "strip-outlet-1")}, parent_device_id=parent_device.id, name="Outlet 1", ) parent_entity = entity_registry.async_get_or_create( "sensor", "test", "strip-power", config_entry=config_entry, device_id=parent_device.id, ) child_entity = entity_registry.async_get_or_create( "switch", "test", "outlet-1-switch", config_entry=config_entry, device_id=child_device.id, ) def search(item_type: ItemType, item_id: str) -> dict[str, set[str]]: """Search.""" searcher = Searcher(hass, {}) return searcher.async_search(item_type, item_id) # A config entry search surfaces both the mains device and its child device, # together with the entities of each. assert search(ItemType.CONFIG_ENTRY, config_entry.entry_id) == { ItemType.DEVICE: {parent_device.id, child_device.id}, ItemType.ENTITY: {parent_entity.entity_id, child_entity.entity_id}, ItemType.AREA: {utility_area.id}, ItemType.FLOOR: {ground_floor.floor_id}, ItemType.INTEGRATION: {"test"}, } # Searching the parent device surfaces its child device and the child's entity. assert search(ItemType.DEVICE, parent_device.id) == { ItemType.DEVICE: {child_device.id}, ItemType.ENTITY: {parent_entity.entity_id, child_entity.entity_id}, ItemType.AREA: {utility_area.id}, ItemType.FLOOR: {ground_floor.floor_id}, ItemType.CONFIG_ENTRY: {config_entry.entry_id}, ItemType.INTEGRATION: {"test"}, } # Searching the child device surfaces its parent device, but not the parent's # own entity: the parent is resolved up, not fully searched. assert search(ItemType.DEVICE, child_device.id) == { ItemType.DEVICE: {parent_device.id}, ItemType.ENTITY: {child_entity.entity_id}, ItemType.AREA: {utility_area.id}, ItemType.FLOOR: {ground_floor.floor_id}, ItemType.CONFIG_ENTRY: {config_entry.entry_id}, ItemType.INTEGRATION: {"test"}, }