mirror of
https://github.com/home-assistant/core.git
synced 2025-08-31 18:31:35 +02:00
Fix target key; add other platform entities on tests
This commit is contained in:
@@ -1,10 +1,16 @@
|
|||||||
"""Provides conditions for lights."""
|
"""Provides conditions for lights."""
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final, override
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_CONDITION, CONF_STATE, STATE_OFF, STATE_ON
|
from homeassistant.const import (
|
||||||
|
CONF_CONDITION,
|
||||||
|
CONF_STATE,
|
||||||
|
CONF_TARGET,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant, split_entity_id
|
from homeassistant.core import HomeAssistant, split_entity_id
|
||||||
from homeassistant.helpers import config_validation as cv, target
|
from homeassistant.helpers import config_validation as cv, target
|
||||||
from homeassistant.helpers.condition import (
|
from homeassistant.helpers.condition import (
|
||||||
@@ -21,18 +27,17 @@ BEHAVIOR_ONE: Final = "one"
|
|||||||
BEHAVIOR_ANY: Final = "any"
|
BEHAVIOR_ANY: Final = "any"
|
||||||
BEHAVIOR_ALL: Final = "all"
|
BEHAVIOR_ALL: Final = "all"
|
||||||
|
|
||||||
STATE_CONDITION_TYPE = f"{DOMAIN}.state"
|
STATE_CONDITION_TYPE = "state"
|
||||||
STATE_CONDITION_SCHEMA = vol.All(
|
STATE_CONDITION_SCHEMA = vol.All(
|
||||||
{
|
{
|
||||||
**cv.CONDITION_BASE_SCHEMA,
|
**cv.CONDITION_BASE_SCHEMA,
|
||||||
vol.Required(CONF_CONDITION): STATE_CONDITION_TYPE,
|
vol.Required(CONF_CONDITION): f"{DOMAIN}.{STATE_CONDITION_TYPE}",
|
||||||
vol.Required(CONF_STATE): vol.In([STATE_ON, STATE_OFF]),
|
vol.Required(CONF_STATE): vol.In([STATE_ON, STATE_OFF]),
|
||||||
vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In(
|
vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In(
|
||||||
[BEHAVIOR_ONE, BEHAVIOR_ANY, BEHAVIOR_ALL]
|
[BEHAVIOR_ONE, BEHAVIOR_ANY, BEHAVIOR_ALL]
|
||||||
),
|
),
|
||||||
**cv.ENTITY_SERVICE_FIELDS,
|
vol.Required(CONF_TARGET): cv.ENTITY_SERVICE_FIELDS,
|
||||||
},
|
},
|
||||||
cv.has_at_least_one_key(*cv.ENTITY_SERVICE_FIELDS),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -44,14 +49,16 @@ class StateCondition(Condition):
|
|||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
async def async_validate_condition_config(
|
async def async_validate_config(
|
||||||
cls, hass: HomeAssistant, config: ConfigType
|
cls, hass: HomeAssistant, config: ConfigType
|
||||||
) -> ConfigType:
|
) -> ConfigType:
|
||||||
"""Validate config."""
|
"""Validate config."""
|
||||||
return STATE_CONDITION_SCHEMA(config) # type: ignore[no-any-return]
|
return STATE_CONDITION_SCHEMA(config) # type: ignore[no-any-return]
|
||||||
|
|
||||||
async def async_condition_from_config(self) -> ConditionCheckerType:
|
@override
|
||||||
|
async def async_get_checker(self) -> ConditionCheckerType:
|
||||||
"""Wrap action method with zone based condition."""
|
"""Wrap action method with zone based condition."""
|
||||||
state = self._config[CONF_STATE]
|
state = self._config[CONF_STATE]
|
||||||
behavior = self._config.get(ATTR_BEHAVIOR)
|
behavior = self._config.get(ATTR_BEHAVIOR)
|
||||||
@@ -95,10 +102,12 @@ class StateCondition(Condition):
|
|||||||
elif behavior == BEHAVIOR_ONE:
|
elif behavior == BEHAVIOR_ONE:
|
||||||
matcher = check_one_match_state
|
matcher = check_one_match_state
|
||||||
|
|
||||||
|
target_config = self._config.get(CONF_TARGET, {})
|
||||||
|
|
||||||
@trace_condition_function
|
@trace_condition_function
|
||||||
def test_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
def test_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
||||||
"""Test state condition."""
|
"""Test state condition."""
|
||||||
selector_data = target.TargetSelectorData(self._config)
|
selector_data = target.TargetSelectorData(target_config)
|
||||||
targeted_entities = target.async_extract_referenced_entity_ids(
|
targeted_entities = target.async_extract_referenced_entity_ids(
|
||||||
hass, selector_data, expand_group=False
|
hass, selector_data, expand_group=False
|
||||||
)
|
)
|
||||||
|
@@ -7,6 +7,7 @@ from homeassistant.const import (
|
|||||||
ATTR_LABEL_ID,
|
ATTR_LABEL_ID,
|
||||||
CONF_CONDITION,
|
CONF_CONDITION,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
|
CONF_TARGET,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
@@ -24,8 +25,8 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def label_lights(hass: HomeAssistant) -> None:
|
async def label_entities(hass: HomeAssistant) -> None:
|
||||||
"""Create multiple light entities associated with labels."""
|
"""Create multiple entities associated with labels."""
|
||||||
await async_setup_component(hass, "light", {})
|
await async_setup_component(hass, "light", {})
|
||||||
|
|
||||||
config_entry = MockConfigEntry(domain="test_labels")
|
config_entry = MockConfigEntry(domain="test_labels")
|
||||||
@@ -45,6 +46,16 @@ async def label_lights(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
entity_reg.async_update_entity(light_entity.entity_id, labels={label.label_id})
|
entity_reg.async_update_entity(light_entity.entity_id, labels={label.label_id})
|
||||||
|
|
||||||
|
# Also create switches to test that they don't impact the conditions
|
||||||
|
for i in range(2):
|
||||||
|
switch_entity = entity_reg.async_get_or_create(
|
||||||
|
domain="switch",
|
||||||
|
platform="test",
|
||||||
|
unique_id=f"label_switch_{i}",
|
||||||
|
suggested_object_id=f"label_switch_{i}",
|
||||||
|
)
|
||||||
|
entity_reg.async_update_entity(switch_entity.entity_id, labels={label.label_id})
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"light.label_light_0",
|
"light.label_light_0",
|
||||||
"light.label_light_1",
|
"light.label_light_1",
|
||||||
@@ -67,14 +78,18 @@ async def has_calls_after_trigger(
|
|||||||
async def test_light_state_condition_behavior_one(
|
async def test_light_state_condition_behavior_one(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
service_calls: list[ServiceCall],
|
service_calls: list[ServiceCall],
|
||||||
label_lights: list[str],
|
label_entities: list[str],
|
||||||
condition_state: str,
|
condition_state: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the light state condition with the 'one' behavior."""
|
"""Test the light state condition with the 'one' behavior."""
|
||||||
await async_setup_component(hass, "light", {})
|
await async_setup_component(hass, "light", {})
|
||||||
|
|
||||||
|
# Set state for two switches to ensure that they don't impact the condition
|
||||||
|
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||||
|
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||||
|
|
||||||
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, reverse_state)
|
hass.states.async_set(entity_id, reverse_state)
|
||||||
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
@@ -85,7 +100,9 @@ async def test_light_state_condition_behavior_one(
|
|||||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
"condition": {
|
"condition": {
|
||||||
CONF_CONDITION: "light.state",
|
CONF_CONDITION: "light.state",
|
||||||
|
CONF_TARGET: {
|
||||||
ATTR_LABEL_ID: "test_label",
|
ATTR_LABEL_ID: "test_label",
|
||||||
|
},
|
||||||
"behavior": "one",
|
"behavior": "one",
|
||||||
CONF_STATE: condition_state,
|
CONF_STATE: condition_state,
|
||||||
},
|
},
|
||||||
@@ -100,20 +117,20 @@ async def test_light_state_condition_behavior_one(
|
|||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set one light to the condition state -> condition pass
|
# Set one light to the condition state -> condition pass
|
||||||
hass.states.async_set(label_lights[0], condition_state)
|
hass.states.async_set(label_entities[0], condition_state)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set second light to the condition state -> condition fail
|
# Set second light to the condition state -> condition fail
|
||||||
hass.states.async_set(label_lights[1], condition_state)
|
hass.states.async_set(label_entities[1], condition_state)
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set first light to unavailable -> condition pass again since only the
|
# Set first light to unavailable -> condition pass again since only the
|
||||||
# second light is on the condition state
|
# second light is on the condition state
|
||||||
hass.states.async_set(label_lights[0], STATE_UNAVAILABLE)
|
hass.states.async_set(label_entities[0], STATE_UNAVAILABLE)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set all lights to unavailable -> condition fail
|
# Set all lights to unavailable -> condition fail
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
@@ -122,14 +139,14 @@ async def test_light_state_condition_behavior_one(
|
|||||||
async def test_light_state_condition_behavior_any(
|
async def test_light_state_condition_behavior_any(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
service_calls: list[ServiceCall],
|
service_calls: list[ServiceCall],
|
||||||
label_lights: list[str],
|
label_entities: list[str],
|
||||||
condition_state: str,
|
condition_state: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the light state condition with the 'any' behavior."""
|
"""Test the light state condition with the 'any' behavior."""
|
||||||
await async_setup_component(hass, "light", {})
|
await async_setup_component(hass, "light", {})
|
||||||
|
|
||||||
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, reverse_state)
|
hass.states.async_set(entity_id, reverse_state)
|
||||||
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
@@ -140,7 +157,9 @@ async def test_light_state_condition_behavior_any(
|
|||||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
"condition": {
|
"condition": {
|
||||||
CONF_CONDITION: "light.state",
|
CONF_CONDITION: "light.state",
|
||||||
|
CONF_TARGET: {
|
||||||
ATTR_LABEL_ID: "test_label",
|
ATTR_LABEL_ID: "test_label",
|
||||||
|
},
|
||||||
"behavior": "any",
|
"behavior": "any",
|
||||||
CONF_STATE: condition_state,
|
CONF_STATE: condition_state,
|
||||||
},
|
},
|
||||||
@@ -151,24 +170,28 @@ async def test_light_state_condition_behavior_any(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set state for two switches to ensure that they don't impact the condition
|
||||||
|
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||||
|
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||||
|
|
||||||
# No lights on the condition state
|
# No lights on the condition state
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set one light to the condition state -> condition pass
|
# Set one light to the condition state -> condition pass
|
||||||
hass.states.async_set(label_lights[0], condition_state)
|
hass.states.async_set(label_entities[0], condition_state)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set all lights to the condition state -> condition pass
|
# Set all lights to the condition state -> condition pass
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, condition_state)
|
hass.states.async_set(entity_id, condition_state)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set one light to unavailable -> condition pass
|
# Set one light to unavailable -> condition pass
|
||||||
hass.states.async_set(label_lights[0], STATE_UNAVAILABLE)
|
hass.states.async_set(label_entities[0], STATE_UNAVAILABLE)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set all lights to unavailable -> condition fail
|
# Set all lights to unavailable -> condition fail
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
@@ -177,14 +200,18 @@ async def test_light_state_condition_behavior_any(
|
|||||||
async def test_light_state_condition_behavior_all(
|
async def test_light_state_condition_behavior_all(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
service_calls: list[ServiceCall],
|
service_calls: list[ServiceCall],
|
||||||
label_lights: list[str],
|
label_entities: list[str],
|
||||||
condition_state: str,
|
condition_state: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the light state condition with the 'all' behavior."""
|
"""Test the light state condition with the 'all' behavior."""
|
||||||
await async_setup_component(hass, "light", {})
|
await async_setup_component(hass, "light", {})
|
||||||
|
|
||||||
|
# Set state for two switches to ensure that they don't impact the condition
|
||||||
|
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||||
|
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||||
|
|
||||||
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
reverse_state = STATE_OFF if condition_state == STATE_ON else STATE_ON
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, reverse_state)
|
hass.states.async_set(entity_id, reverse_state)
|
||||||
|
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
@@ -195,7 +222,9 @@ async def test_light_state_condition_behavior_all(
|
|||||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
"condition": {
|
"condition": {
|
||||||
CONF_CONDITION: "light.state",
|
CONF_CONDITION: "light.state",
|
||||||
|
CONF_TARGET: {
|
||||||
ATTR_LABEL_ID: "test_label",
|
ATTR_LABEL_ID: "test_label",
|
||||||
|
},
|
||||||
"behavior": "all",
|
"behavior": "all",
|
||||||
CONF_STATE: condition_state,
|
CONF_STATE: condition_state,
|
||||||
},
|
},
|
||||||
@@ -210,18 +239,18 @@ async def test_light_state_condition_behavior_all(
|
|||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set one light to the condition state -> condition fail
|
# Set one light to the condition state -> condition fail
|
||||||
hass.states.async_set(label_lights[0], condition_state)
|
hass.states.async_set(label_entities[0], condition_state)
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set all lights to the condition state -> condition pass
|
# Set all lights to the condition state -> condition pass
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, condition_state)
|
hass.states.async_set(entity_id, condition_state)
|
||||||
assert await has_calls_after_trigger(hass, service_calls)
|
assert await has_calls_after_trigger(hass, service_calls)
|
||||||
|
|
||||||
# Set one light to unavailable -> condition should still pass
|
# Set one light to unavailable -> condition should still pass
|
||||||
hass.states.async_set(label_lights[0], STATE_UNAVAILABLE)
|
hass.states.async_set(label_entities[0], STATE_UNAVAILABLE)
|
||||||
|
|
||||||
# Set all lights to unavailable -> condition fail
|
# Set all lights to unavailable -> condition fail
|
||||||
for entity_id in label_lights:
|
for entity_id in label_entities:
|
||||||
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
|
||||||
assert not await has_calls_after_trigger(hass, service_calls)
|
assert not await has_calls_after_trigger(hass, service_calls)
|
||||||
|
Reference in New Issue
Block a user