Files
core/tests/components/search/test_init.py
T
2026-08-13 13:53:15 +02:00

1362 lines
52 KiB
Python

"""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"},
}