mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 08:06:00 +01:00
Add get_conditions_for_target websocket command (#157344)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
@@ -9,6 +9,9 @@ from typing import Any, Self
|
||||
from homeassistant.const import CONF_TARGET
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import target as target_helpers
|
||||
from homeassistant.helpers.condition import (
|
||||
async_get_all_descriptions as async_get_all_condition_descriptions,
|
||||
)
|
||||
from homeassistant.helpers.entity import (
|
||||
entity_sources,
|
||||
get_device_class,
|
||||
@@ -199,6 +202,16 @@ async def async_get_triggers_for_target(
|
||||
)
|
||||
|
||||
|
||||
async def async_get_conditions_for_target(
|
||||
hass: HomeAssistant, target_selector: ConfigType, expand_group: bool
|
||||
) -> set[str]:
|
||||
"""Get conditions for a target."""
|
||||
descriptions = await async_get_all_condition_descriptions(hass)
|
||||
return _async_get_automation_components_for_target(
|
||||
hass, target_selector, expand_group, descriptions
|
||||
)
|
||||
|
||||
|
||||
async def async_get_services_for_target(
|
||||
hass: HomeAssistant, target_selector: ConfigType, expand_group: bool
|
||||
) -> set[str]:
|
||||
|
||||
@@ -87,7 +87,11 @@ from homeassistant.setup import (
|
||||
from homeassistant.util.json import format_unserializable_data
|
||||
|
||||
from . import const, decorators, messages
|
||||
from .automation import async_get_services_for_target, async_get_triggers_for_target
|
||||
from .automation import (
|
||||
async_get_conditions_for_target,
|
||||
async_get_services_for_target,
|
||||
async_get_triggers_for_target,
|
||||
)
|
||||
from .connection import ActiveConnection
|
||||
from .messages import construct_event_message, construct_result_message
|
||||
|
||||
@@ -109,6 +113,7 @@ def async_register_commands(
|
||||
async_reg(hass, handle_execute_script)
|
||||
async_reg(hass, handle_extract_from_target)
|
||||
async_reg(hass, handle_fire_event)
|
||||
async_reg(hass, handle_get_conditions_for_target)
|
||||
async_reg(hass, handle_get_config)
|
||||
async_reg(hass, handle_get_services)
|
||||
async_reg(hass, handle_get_services_for_target)
|
||||
@@ -903,6 +908,29 @@ async def handle_get_triggers_for_target(
|
||||
connection.send_result(msg["id"], triggers)
|
||||
|
||||
|
||||
@decorators.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "get_conditions_for_target",
|
||||
vol.Required("target"): cv.TARGET_FIELDS,
|
||||
vol.Optional("expand_group", default=True): bool,
|
||||
}
|
||||
)
|
||||
@decorators.async_response
|
||||
async def handle_get_conditions_for_target(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Handle get conditions for target command.
|
||||
|
||||
This command returns all conditions that can be used with any entities that are currently
|
||||
part of a target.
|
||||
"""
|
||||
conditions = await async_get_conditions_for_target(
|
||||
hass, msg["target"], msg["expand_group"]
|
||||
)
|
||||
|
||||
connection.send_result(msg["id"], conditions)
|
||||
|
||||
|
||||
@decorators.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "get_services_for_target",
|
||||
|
||||
@@ -3659,49 +3659,57 @@ async def test_extract_from_target_validation_error(
|
||||
|
||||
@pytest.mark.usefixtures("enable_experimental_triggers_conditions", "target_entities")
|
||||
@patch("annotatedyaml.loader.load_yaml")
|
||||
@patch.object(Integration, "has_triggers", return_value=True)
|
||||
@pytest.mark.parametrize("automation_component", ["trigger", "condition"])
|
||||
async def test_get_triggers_for_target(
|
||||
mock_has_triggers: Mock,
|
||||
mock_load_yaml: Mock,
|
||||
hass: HomeAssistant,
|
||||
websocket_client: MockHAClientWebSocket,
|
||||
automation_component: str,
|
||||
) -> None:
|
||||
"""Test get_triggers_for_target command with mixed target types."""
|
||||
"""Test get_triggers_for_target/get_conditions_for_target command with mixed target types."""
|
||||
|
||||
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type]:
|
||||
async def async_get_triggers_conditions(hass: HomeAssistant) -> dict[str, type]:
|
||||
return {
|
||||
"turned_on": Mock,
|
||||
"non_target_trigger": Mock,
|
||||
"non_target": Mock,
|
||||
}
|
||||
|
||||
mock_platform(hass, "light.trigger", Mock(async_get_triggers=async_get_triggers))
|
||||
mock_platform(hass, "switch.trigger", Mock(async_get_triggers=async_get_triggers))
|
||||
mock_platform(hass, "sensor.trigger", Mock(async_get_triggers=async_get_triggers))
|
||||
common_mock = Mock(
|
||||
**{f"async_get_{automation_component}s": async_get_triggers_conditions}
|
||||
)
|
||||
|
||||
mock_platform(hass, f"light.{automation_component}", common_mock)
|
||||
mock_platform(hass, f"switch.{automation_component}", common_mock)
|
||||
mock_platform(hass, f"sensor.{automation_component}", common_mock)
|
||||
mock_platform(
|
||||
hass,
|
||||
"component1.trigger",
|
||||
f"component1.{automation_component}",
|
||||
Mock(
|
||||
async_get_triggers=AsyncMock(
|
||||
return_value={
|
||||
"_": Mock,
|
||||
"light_message": Mock,
|
||||
"light_flash": Mock,
|
||||
"light_dance": Mock,
|
||||
}
|
||||
)
|
||||
**{
|
||||
f"async_get_{automation_component}s": AsyncMock(
|
||||
return_value={
|
||||
"_": Mock,
|
||||
"light_message": Mock,
|
||||
"light_flash": Mock,
|
||||
"light_dance": Mock,
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
mock_platform(
|
||||
hass,
|
||||
"component2.trigger",
|
||||
f"component2.{automation_component}",
|
||||
Mock(
|
||||
async_get_triggers=AsyncMock(
|
||||
return_value={"match_all": Mock, "other_integration_lights": Mock}
|
||||
)
|
||||
**{
|
||||
f"async_get_{automation_component}s": AsyncMock(
|
||||
return_value={"match_all": Mock, "other_integration_lights": Mock}
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def get_common_trigger_descriptions(domain: str):
|
||||
def get_common_descriptions(domain: str):
|
||||
return f"""
|
||||
turned_on:
|
||||
target:
|
||||
@@ -3717,7 +3725,7 @@ async def test_get_triggers_for_target(
|
||||
- first
|
||||
- last
|
||||
- any
|
||||
non_target_trigger:
|
||||
non_target:
|
||||
fields:
|
||||
behavior:
|
||||
required: true
|
||||
@@ -3730,7 +3738,7 @@ async def test_get_triggers_for_target(
|
||||
- any
|
||||
"""
|
||||
|
||||
component1_trigger_descriptions = """
|
||||
component1_descriptions = """
|
||||
_:
|
||||
target:
|
||||
entity:
|
||||
@@ -3761,7 +3769,7 @@ async def test_get_triggers_for_target(
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
"""
|
||||
|
||||
component2_trigger_descriptions = """
|
||||
component2_descriptions = """
|
||||
match_all:
|
||||
target:
|
||||
|
||||
@@ -3776,145 +3784,148 @@ async def test_get_triggers_for_target(
|
||||
"""
|
||||
|
||||
def _load_yaml(fname, secrets=None):
|
||||
if fname.endswith("component1/triggers.yaml"):
|
||||
trigger_descriptions = component1_trigger_descriptions
|
||||
elif fname.endswith("component2/triggers.yaml"):
|
||||
trigger_descriptions = component2_trigger_descriptions
|
||||
if fname.endswith(f"component1/{automation_component}s.yaml"):
|
||||
descriptions = component1_descriptions
|
||||
elif fname.endswith(f"component2/{automation_component}s.yaml"):
|
||||
descriptions = component2_descriptions
|
||||
else:
|
||||
trigger_descriptions = get_common_trigger_descriptions(fname.split("/")[-2])
|
||||
with io.StringIO(trigger_descriptions) as file:
|
||||
descriptions = get_common_descriptions(fname.split("/")[-2])
|
||||
with io.StringIO(descriptions) as file:
|
||||
return parse_yaml(file)
|
||||
|
||||
mock_load_yaml.side_effect = _load_yaml
|
||||
assert await async_setup_component(hass, "light", {})
|
||||
assert await async_setup_component(hass, "switch", {})
|
||||
assert await async_setup_component(hass, "sensor", {})
|
||||
assert await async_setup_component(hass, "component1", {})
|
||||
assert await async_setup_component(hass, "component2", {})
|
||||
await hass.async_block_till_done()
|
||||
with patch.object(Integration, f"has_{automation_component}s", return_value=True):
|
||||
assert await async_setup_component(hass, "light", {})
|
||||
assert await async_setup_component(hass, "switch", {})
|
||||
assert await async_setup_component(hass, "sensor", {})
|
||||
assert await async_setup_component(hass, "component1", {})
|
||||
assert await async_setup_component(hass, "component2", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def assert_triggers(target: dict[str, list[str]], expected: list[str]) -> Any:
|
||||
"""Call the command and assert expected triggers."""
|
||||
await websocket_client.send_json_auto_id(
|
||||
{"type": "get_triggers_for_target", "target": target}
|
||||
async def assert_command(
|
||||
target: dict[str, list[str]], expected: list[str]
|
||||
) -> Any:
|
||||
"""Call the command and assert expected triggers/conditions."""
|
||||
await websocket_client.send_json_auto_id(
|
||||
{"type": f"get_{automation_component}s_for_target", "target": target}
|
||||
)
|
||||
msg = await websocket_client.receive_json()
|
||||
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert msg["success"]
|
||||
assert sorted(msg["result"]) == sorted(expected)
|
||||
|
||||
# Test entity target - unknown entity
|
||||
await assert_command({"entity_id": ["light.unknown_entity"]}, [])
|
||||
|
||||
# Test entity target - entity not in registry
|
||||
await assert_command(
|
||||
{"entity_id": ["light.not_registry"]},
|
||||
[
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
msg = await websocket_client.receive_json()
|
||||
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert msg["success"]
|
||||
assert sorted(msg["result"]) == sorted(expected)
|
||||
# Test entity targets
|
||||
await assert_command(
|
||||
{"entity_id": ["light.component1_light", "switch.component1_switch"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_command(
|
||||
{"entity_id": ["light.component1_flash_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_command(
|
||||
{"entity_id": ["light.component1_effect_flash_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_command(
|
||||
{"entity_id": ["light.component1_flash_transition_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_dance",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test entity target - unknown entity
|
||||
await assert_triggers({"entity_id": ["light.unknown_entity"]}, [])
|
||||
# Test device target - multiple devices
|
||||
await assert_command(
|
||||
{"device_id": ["device1", "device2"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"sensor.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test entity target - entity not in registry
|
||||
await assert_triggers(
|
||||
{"entity_id": ["light.not_registry"]},
|
||||
[
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
# Test area target - multiple areas
|
||||
await assert_command(
|
||||
{"area_id": ["kitchen", "living_room"]},
|
||||
[
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test entity targets
|
||||
await assert_triggers(
|
||||
{"entity_id": ["light.component1_light", "switch.component1_switch"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_triggers(
|
||||
{"entity_id": ["light.component1_flash_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_triggers(
|
||||
{"entity_id": ["light.component1_effect_flash_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
await assert_triggers(
|
||||
{"entity_id": ["light.component1_flash_transition_light"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_dance",
|
||||
"component1.light_flash",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"light.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test device target - multiple devices
|
||||
await assert_triggers(
|
||||
{"device_id": ["device1", "device2"]},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"sensor.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test area target - multiple areas
|
||||
await assert_triggers(
|
||||
{"area_id": ["kitchen", "living_room"]},
|
||||
[
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
# Test label target - multiple labels
|
||||
await assert_triggers(
|
||||
{"label_id": ["label_1", "label_2"]},
|
||||
[
|
||||
"light.turned_on",
|
||||
"component1",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
# Test mixed target types
|
||||
await assert_triggers(
|
||||
{
|
||||
"entity_id": ["light.test1"],
|
||||
"device_id": ["device2"],
|
||||
"area_id": ["kitchen"],
|
||||
"label_id": ["label_1"],
|
||||
},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"sensor.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
# Test label target - multiple labels
|
||||
await assert_command(
|
||||
{"label_id": ["label_1", "label_2"]},
|
||||
[
|
||||
"light.turned_on",
|
||||
"component1",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
# Test mixed target types
|
||||
await assert_command(
|
||||
{
|
||||
"entity_id": ["light.test1"],
|
||||
"device_id": ["device2"],
|
||||
"area_id": ["kitchen"],
|
||||
"label_id": ["label_1"],
|
||||
},
|
||||
[
|
||||
"component1",
|
||||
"component1.light_message",
|
||||
"component2.match_all",
|
||||
"component2.other_integration_lights",
|
||||
"light.turned_on",
|
||||
"sensor.turned_on",
|
||||
"switch.turned_on",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("target_entities")
|
||||
|
||||
Reference in New Issue
Block a user