Fix target key; add other platform entities on tests

This commit is contained in:
abmantis
2025-08-07 19:11:50 +01:00
parent cef2ccae65
commit b2bd1a4aa0
2 changed files with 70 additions and 32 deletions

View File

@@ -1,10 +1,16 @@
"""Provides conditions for lights."""
from typing import Final
from typing import Final, override
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.helpers import config_validation as cv, target
from homeassistant.helpers.condition import (
@@ -21,18 +27,17 @@ BEHAVIOR_ONE: Final = "one"
BEHAVIOR_ANY: Final = "any"
BEHAVIOR_ALL: Final = "all"
STATE_CONDITION_TYPE = f"{DOMAIN}.state"
STATE_CONDITION_TYPE = "state"
STATE_CONDITION_SCHEMA = vol.All(
{
**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(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In(
[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._config = config
@override
@classmethod
async def async_validate_condition_config(
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
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."""
state = self._config[CONF_STATE]
behavior = self._config.get(ATTR_BEHAVIOR)
@@ -95,10 +102,12 @@ class StateCondition(Condition):
elif behavior == BEHAVIOR_ONE:
matcher = check_one_match_state
target_config = self._config.get(CONF_TARGET, {})
@trace_condition_function
def test_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
"""Test state condition."""
selector_data = target.TargetSelectorData(self._config)
selector_data = target.TargetSelectorData(target_config)
targeted_entities = target.async_extract_referenced_entity_ids(
hass, selector_data, expand_group=False
)

View File

@@ -7,6 +7,7 @@ from homeassistant.const import (
ATTR_LABEL_ID,
CONF_CONDITION,
CONF_STATE,
CONF_TARGET,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
@@ -24,8 +25,8 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
@pytest.fixture
async def label_lights(hass: HomeAssistant) -> None:
"""Create multiple light entities associated with labels."""
async def label_entities(hass: HomeAssistant) -> None:
"""Create multiple entities associated with labels."""
await async_setup_component(hass, "light", {})
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})
# 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 [
"light.label_light_0",
"light.label_light_1",
@@ -67,14 +78,18 @@ async def has_calls_after_trigger(
async def test_light_state_condition_behavior_one(
hass: HomeAssistant,
service_calls: list[ServiceCall],
label_lights: list[str],
label_entities: list[str],
condition_state: str,
) -> None:
"""Test the light state condition with the 'one' behavior."""
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
for entity_id in label_lights:
for entity_id in label_entities:
hass.states.async_set(entity_id, reverse_state)
await async_setup_component(
@@ -85,7 +100,9 @@ async def test_light_state_condition_behavior_one(
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
CONF_CONDITION: "light.state",
ATTR_LABEL_ID: "test_label",
CONF_TARGET: {
ATTR_LABEL_ID: "test_label",
},
"behavior": "one",
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)
# 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)
# 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)
# Set first light to unavailable -> condition pass again since only the
# 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)
# 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)
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(
hass: HomeAssistant,
service_calls: list[ServiceCall],
label_lights: list[str],
label_entities: list[str],
condition_state: str,
) -> None:
"""Test the light state condition with the 'any' behavior."""
await async_setup_component(hass, "light", {})
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)
await async_setup_component(
@@ -140,7 +157,9 @@ async def test_light_state_condition_behavior_any(
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
CONF_CONDITION: "light.state",
ATTR_LABEL_ID: "test_label",
CONF_TARGET: {
ATTR_LABEL_ID: "test_label",
},
"behavior": "any",
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
assert not await has_calls_after_trigger(hass, service_calls)
# 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)
# 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)
assert await has_calls_after_trigger(hass, service_calls)
# 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)
# 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)
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(
hass: HomeAssistant,
service_calls: list[ServiceCall],
label_lights: list[str],
label_entities: list[str],
condition_state: str,
) -> None:
"""Test the light state condition with the 'all' behavior."""
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
for entity_id in label_lights:
for entity_id in label_entities:
hass.states.async_set(entity_id, reverse_state)
await async_setup_component(
@@ -195,7 +222,9 @@ async def test_light_state_condition_behavior_all(
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
CONF_CONDITION: "light.state",
ATTR_LABEL_ID: "test_label",
CONF_TARGET: {
ATTR_LABEL_ID: "test_label",
},
"behavior": "all",
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)
# 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)
# 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)
assert await has_calls_after_trigger(hass, service_calls)
# 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
for entity_id in label_lights:
for entity_id in label_entities:
hass.states.async_set(entity_id, STATE_UNAVAILABLE)
assert not await has_calls_after_trigger(hass, service_calls)