diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 25b9a4417dc..7cfee8fad93 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -32,6 +32,9 @@ def validate_above_below(value): if above is None or below is None: return value + if isinstance(above, str) or isinstance(below, str): + return value + if above > below: raise vol.Invalid( f"A value can never be above {above} and below {below} at the same time. You probably want two different triggers.", @@ -45,8 +48,8 @@ TRIGGER_SCHEMA = vol.All( { vol.Required(CONF_PLATFORM): "numeric_state", vol.Required(CONF_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_BELOW): vol.Coerce(float), - vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_BELOW): cv.NUMERIC_STATE_THRESHOLD_SCHEMA, + vol.Optional(CONF_ABOVE): cv.NUMERIC_STATE_THRESHOLD_SCHEMA, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_FOR): cv.positive_time_period_template, vol.Optional(CONF_ATTRIBUTE): cv.match_all, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index a4499209640..28f18cf9407 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -916,18 +916,18 @@ SERVICE_SCHEMA = vol.All( has_at_least_one_key(CONF_SERVICE, CONF_SERVICE_TEMPLATE), ) +NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any( + vol.Coerce(float), vol.All(str, entity_domain("input_number")) +) + NUMERIC_STATE_CONDITION_SCHEMA = vol.All( vol.Schema( { vol.Required(CONF_CONDITION): "numeric_state", vol.Required(CONF_ENTITY_ID): entity_ids, vol.Optional(CONF_ATTRIBUTE): str, - CONF_BELOW: vol.Any( - vol.Coerce(float), vol.All(str, entity_domain("input_number")) - ), - CONF_ABOVE: vol.Any( - vol.Coerce(float), vol.All(str, entity_domain("input_number")) - ), + CONF_BELOW: NUMERIC_STATE_THRESHOLD_SCHEMA, + CONF_ABOVE: NUMERIC_STATE_THRESHOLD_SCHEMA, vol.Optional(CONF_VALUE_TEMPLATE): template, } ), diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index d63cb970f80..b9696fffe06 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -29,12 +29,27 @@ def calls(hass): @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Initialize components.""" mock_component(hass, "group") + await async_setup_component( + hass, + "input_number", + { + "input_number": { + "value_3": {"min": 0, "max": 255, "initial": 3}, + "value_5": {"min": 0, "max": 255, "initial": 5}, + "value_8": {"min": 0, "max": 255, "initial": 8}, + "value_10": {"min": 0, "max": 255, "initial": 10}, + "value_12": {"min": 0, "max": 255, "initial": 12}, + "value_100": {"min": 0, "max": 255, "initial": 100}, + } + }, + ) -async def test_if_not_fires_on_entity_removal(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_removal(hass, calls, below): """Test the firing with removed entity.""" hass.states.async_set("test.entity", 11) @@ -46,7 +61,7 @@ async def test_if_not_fires_on_entity_removal(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -59,7 +74,8 @@ async def test_if_not_fires_on_entity_removal(hass, calls): assert len(calls) == 0 -async def test_if_fires_on_entity_change_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_entity_change_below(hass, calls, below): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -73,7 +89,7 @@ async def test_if_fires_on_entity_change_below(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -99,7 +115,8 @@ async def test_if_fires_on_entity_change_below(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_over_to_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_entity_change_over_to_below(hass, calls, below): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -112,7 +129,7 @@ async def test_if_fires_on_entity_change_over_to_below(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -125,7 +142,8 @@ async def test_if_fires_on_entity_change_over_to_below(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entities_change_over_to_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_entities_change_over_to_below(hass, calls, below): """Test the firing with changed entities.""" hass.states.async_set("test.entity_1", 11) hass.states.async_set("test.entity_2", 11) @@ -139,7 +157,7 @@ async def test_if_fires_on_entities_change_over_to_below(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": ["test.entity_1", "test.entity_2"], - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -155,7 +173,8 @@ async def test_if_fires_on_entities_change_over_to_below(hass, calls): assert len(calls) == 2 -async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_change_below_to_below(hass, calls, below): """Test the firing with changed entity.""" context = Context() hass.states.async_set("test.entity", 11) @@ -169,7 +188,7 @@ async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -193,7 +212,8 @@ async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): assert len(calls) == 1 -async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls, below): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -206,7 +226,7 @@ async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -219,7 +239,8 @@ async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): assert len(calls) == 0 -async def test_if_fires_on_initial_entity_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_initial_entity_below(hass, calls, below): """Test the firing when starting with a match.""" hass.states.async_set("test.entity", 9) await hass.async_block_till_done() @@ -232,7 +253,7 @@ async def test_if_fires_on_initial_entity_below(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -245,7 +266,8 @@ async def test_if_fires_on_initial_entity_below(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_initial_entity_above(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_if_fires_on_initial_entity_above(hass, calls, above): """Test the firing when starting with a match.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -258,7 +280,7 @@ async def test_if_fires_on_initial_entity_above(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": {"service": "test.automation"}, } @@ -271,7 +293,8 @@ async def test_if_fires_on_initial_entity_above(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_above(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_if_fires_on_entity_change_above(hass, calls, above): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 9) await hass.async_block_till_done() @@ -284,7 +307,7 @@ async def test_if_fires_on_entity_change_above(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": {"service": "test.automation"}, } @@ -296,7 +319,8 @@ async def test_if_fires_on_entity_change_above(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_below_to_above(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_if_fires_on_entity_change_below_to_above(hass, calls, above): """Test the firing with changed entity.""" # set initial state hass.states.async_set("test.entity", 9) @@ -310,7 +334,7 @@ async def test_if_fires_on_entity_change_below_to_above(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": {"service": "test.automation"}, } @@ -323,7 +347,8 @@ async def test_if_fires_on_entity_change_below_to_above(hass, calls): assert len(calls) == 1 -async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_change_above_to_above(hass, calls, above): """Test the firing with changed entity.""" # set initial state hass.states.async_set("test.entity", 9) @@ -337,7 +362,7 @@ async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": {"service": "test.automation"}, } @@ -355,7 +380,8 @@ async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): assert len(calls) == 1 -async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls, above): """Test the firing with changed entity.""" # set initial state hass.states.async_set("test.entity", 9) @@ -369,7 +395,7 @@ async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": {"service": "test.automation"}, } @@ -382,7 +408,16 @@ async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): assert len(calls) == 0 -async def test_if_fires_on_entity_change_below_range(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (5, 10), + (5, "input_number.value_10"), + ("input_number.value_5", 10), + ("input_number.value_5", "input_number.value_10"), + ), +) +async def test_if_fires_on_entity_change_below_range(hass, calls, above, below): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -395,8 +430,8 @@ async def test_if_fires_on_entity_change_below_range(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, - "above": 5, + "below": below, + "above": above, }, "action": {"service": "test.automation"}, } @@ -408,7 +443,16 @@ async def test_if_fires_on_entity_change_below_range(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_below_above_range(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (5, 10), + (5, "input_number.value_10"), + ("input_number.value_5", 10), + ("input_number.value_5", "input_number.value_10"), + ), +) +async def test_if_fires_on_entity_change_below_above_range(hass, calls, above, below): """Test the firing with changed entity.""" assert await async_setup_component( hass, @@ -418,8 +462,8 @@ async def test_if_fires_on_entity_change_below_above_range(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, - "above": 5, + "below": below, + "above": above, }, "action": {"service": "test.automation"}, } @@ -431,7 +475,16 @@ async def test_if_fires_on_entity_change_below_above_range(hass, calls): assert len(calls) == 0 -async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (5, 10), + (5, "input_number.value_10"), + ("input_number.value_5", 10), + ("input_number.value_5", "input_number.value_10"), + ), +) +async def test_if_fires_on_entity_change_over_to_below_range(hass, calls, above, below): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -444,8 +497,8 @@ async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, - "above": 5, + "below": below, + "above": above, }, "action": {"service": "test.automation"}, } @@ -458,7 +511,18 @@ async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_over_to_below_above_range(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (5, 10), + (5, "input_number.value_10"), + ("input_number.value_5", 10), + ("input_number.value_5", "input_number.value_10"), + ), +) +async def test_if_fires_on_entity_change_over_to_below_above_range( + hass, calls, above, below +): """Test the firing with changed entity.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -471,8 +535,8 @@ async def test_if_fires_on_entity_change_over_to_below_above_range(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, - "above": 5, + "below": above, + "above": below, }, "action": {"service": "test.automation"}, } @@ -485,7 +549,8 @@ async def test_if_fires_on_entity_change_over_to_below_above_range(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_if_entity_not_match(hass, calls): +@pytest.mark.parametrize("below", (100, "input_number.value_100")) +async def test_if_not_fires_if_entity_not_match(hass, calls, below): """Test if not fired with non matching entity.""" assert await async_setup_component( hass, @@ -495,7 +560,7 @@ async def test_if_not_fires_if_entity_not_match(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.another_entity", - "below": 100, + "below": below, }, "action": {"service": "test.automation"}, } @@ -507,7 +572,8 @@ async def test_if_not_fires_if_entity_not_match(hass, calls): assert len(calls) == 0 -async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_entity_change_below_with_attribute(hass, calls, below): """Test attributes change.""" hass.states.async_set("test.entity", 11, {"test_attribute": 11}) await hass.async_block_till_done() @@ -520,7 +586,7 @@ async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -532,7 +598,10 @@ async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): assert len(calls) == 1 -async def test_if_not_fires_on_entity_change_not_below_with_attribute(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_change_not_below_with_attribute( + hass, calls, below +): """Test attributes.""" assert await async_setup_component( hass, @@ -542,7 +611,7 @@ async def test_if_not_fires_on_entity_change_not_below_with_attribute(hass, call "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -554,7 +623,8 @@ async def test_if_not_fires_on_entity_change_not_below_with_attribute(hass, call assert len(calls) == 0 -async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls, below): """Test attributes change.""" hass.states.async_set("test.entity", "entity", {"test_attribute": 11}) await hass.async_block_till_done() @@ -568,7 +638,7 @@ async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -580,7 +650,10 @@ async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): assert len(calls) == 1 -async def test_if_not_fires_on_attribute_change_with_attribute_not_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_attribute_change_with_attribute_not_below( + hass, calls, below +): """Test attributes change.""" assert await async_setup_component( hass, @@ -591,7 +664,7 @@ async def test_if_not_fires_on_attribute_change_with_attribute_not_below(hass, c "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -603,7 +676,8 @@ async def test_if_not_fires_on_attribute_change_with_attribute_not_below(hass, c assert len(calls) == 0 -async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls, below): """Test attributes change.""" assert await async_setup_component( hass, @@ -614,7 +688,7 @@ async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -626,7 +700,10 @@ async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_on_entity_change_with_not_attribute_below(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_if_not_fires_on_entity_change_with_not_attribute_below( + hass, calls, below +): """Test attributes change.""" assert await async_setup_component( hass, @@ -637,7 +714,7 @@ async def test_if_not_fires_on_entity_change_with_not_attribute_below(hass, call "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -649,7 +726,10 @@ async def test_if_not_fires_on_entity_change_with_not_attribute_below(hass, call assert len(calls) == 0 -async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr( + hass, calls, below +): """Test attributes change.""" hass.states.async_set( "test.entity", "entity", {"test_attribute": 11, "not_test_attribute": 11} @@ -664,7 +744,7 @@ async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(hass, "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -678,7 +758,8 @@ async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(hass, assert len(calls) == 1 -async def test_template_list(hass, calls): +@pytest.mark.parametrize("below", (10, "input_number.value_10")) +async def test_template_list(hass, calls, below): """Test template list.""" hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 11]}) await hass.async_block_till_done() @@ -691,7 +772,7 @@ async def test_template_list(hass, calls): "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute[2] }}", - "below": 10, + "below": below, }, "action": {"service": "test.automation"}, } @@ -703,7 +784,8 @@ async def test_template_list(hass, calls): assert len(calls) == 1 -async def test_template_string(hass, calls): +@pytest.mark.parametrize("below", (10.0, "input_number.value_10")) +async def test_template_string(hass, calls, below): """Test template string.""" assert await async_setup_component( hass, @@ -714,7 +796,7 @@ async def test_template_string(hass, calls): "platform": "numeric_state", "entity_id": "test.entity", "value_template": "{{ state.attributes.test_attribute | multiply(10) }}", - "below": 10, + "below": below, }, "action": { "service": "test.automation", @@ -742,7 +824,7 @@ async def test_template_string(hass, calls): assert len(calls) == 1 assert ( calls[0].data["some"] - == "numeric_state - test.entity - 10.0 - None - test state 1 - test state 2" + == f"numeric_state - test.entity - {below} - None - test state 1 - test state 2" ) @@ -771,7 +853,16 @@ async def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(hass, assert len(calls) == 0 -async def test_if_action(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_action(hass, calls, above, below): """Test if action.""" entity_id = "domain.test_entity" assert await async_setup_component( @@ -783,8 +874,8 @@ async def test_if_action(hass, calls): "condition": { "condition": "numeric_state", "entity_id": entity_id, - "above": 8, - "below": 12, + "above": above, + "below": below, }, "action": {"service": "test.automation"}, } @@ -810,7 +901,16 @@ async def test_if_action(hass, calls): assert len(calls) == 2 -async def test_if_fails_setup_bad_for(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fails_setup_bad_for(hass, calls, above, below): """Test for setup failure for bad for.""" hass.states.async_set("test.entity", 5) await hass.async_block_till_done() @@ -823,8 +923,8 @@ async def test_if_fails_setup_bad_for(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"invalid": 5}, }, "action": {"service": "homeassistant.turn_on"}, @@ -857,7 +957,16 @@ async def test_if_fails_setup_for_without_above_below(hass, calls): ) -async def test_if_not_fires_on_entity_change_with_for(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_not_fires_on_entity_change_with_for(hass, calls, above, below): """Test for not firing on entity change with for.""" assert await async_setup_component( hass, @@ -867,8 +976,8 @@ async def test_if_not_fires_on_entity_change_with_for(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": {"service": "test.automation"}, @@ -885,7 +994,18 @@ async def test_if_not_fires_on_entity_change_with_for(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_not_fires_on_entities_change_with_for_after_stop( + hass, calls, above, below +): """Test for not firing on entities change with for after stop.""" hass.states.async_set("test.entity_1", 0) hass.states.async_set("test.entity_2", 0) @@ -899,8 +1019,8 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": ["test.entity_1", "test.entity_2"], - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": {"service": "test.automation"}, @@ -932,7 +1052,18 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_entity_change_with_for_attribute_change( + hass, calls, above, below +): """Test for firing on entity change with for and attribute change.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -945,8 +1076,8 @@ async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": {"service": "test.automation"}, @@ -970,7 +1101,16 @@ async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_entity_change_with_for(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_entity_change_with_for(hass, calls, above, below): """Test for firing on entity change with for.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -983,8 +1123,8 @@ async def test_if_fires_on_entity_change_with_for(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": {"service": "test.automation"}, @@ -999,7 +1139,8 @@ async def test_if_fires_on_entity_change_with_for(hass, calls): assert len(calls) == 1 -async def test_wait_template_with_trigger(hass, calls): +@pytest.mark.parametrize("above", (10, "input_number.value_10")) +async def test_wait_template_with_trigger(hass, calls, above): """Test using wait template with 'trigger.entity_id'.""" hass.states.async_set("test.entity", "0") await hass.async_block_till_done() @@ -1012,7 +1153,7 @@ async def test_wait_template_with_trigger(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 10, + "above": above, }, "action": [ {"wait_template": "{{ states(trigger.entity_id) | int < 10 }}"}, @@ -1039,7 +1180,16 @@ async def test_wait_template_with_trigger(hass, calls): assert "numeric_state - test.entity - 12" == calls[0].data["some"] -async def test_if_fires_on_entities_change_no_overlap(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_entities_change_no_overlap(hass, calls, above, below): """Test for firing on entities change with no overlap.""" hass.states.async_set("test.entity_1", 0) hass.states.async_set("test.entity_2", 0) @@ -1053,8 +1203,8 @@ async def test_if_fires_on_entities_change_no_overlap(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": ["test.entity_1", "test.entity_2"], - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": { @@ -1086,7 +1236,16 @@ async def test_if_fires_on_entities_change_no_overlap(hass, calls): assert calls[1].data["some"] == "test.entity_2" -async def test_if_fires_on_entities_change_overlap(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_entities_change_overlap(hass, calls, above, below): """Test for firing on entities change with overlap.""" hass.states.async_set("test.entity_1", 0) hass.states.async_set("test.entity_2", 0) @@ -1100,8 +1259,8 @@ async def test_if_fires_on_entities_change_overlap(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": ["test.entity_1", "test.entity_2"], - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": 5}, }, "action": { @@ -1144,7 +1303,16 @@ async def test_if_fires_on_entities_change_overlap(hass, calls): assert calls[1].data["some"] == "test.entity_2" -async def test_if_fires_on_change_with_for_template_1(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_change_with_for_template_1(hass, calls, above, below): """Test for firing on change with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1157,8 +1325,8 @@ async def test_if_fires_on_change_with_for_template_1(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": {"seconds": "{{ 5 }}"}, }, "action": {"service": "test.automation"}, @@ -1174,7 +1342,16 @@ async def test_if_fires_on_change_with_for_template_1(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_change_with_for_template_2(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_change_with_for_template_2(hass, calls, above, below): """Test for firing on change with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1187,8 +1364,8 @@ async def test_if_fires_on_change_with_for_template_2(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": "{{ 5 }}", }, "action": {"service": "test.automation"}, @@ -1204,7 +1381,16 @@ async def test_if_fires_on_change_with_for_template_2(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_change_with_for_template_3(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_change_with_for_template_3(hass, calls, above, below): """Test for firing on change with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1217,8 +1403,8 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": "00:00:{{ 5 }}", }, "action": {"service": "test.automation"}, @@ -1234,7 +1420,16 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): assert len(calls) == 1 -async def test_invalid_for_template(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_invalid_for_template(hass, calls, above, below): """Test for invalid for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1247,8 +1442,8 @@ async def test_invalid_for_template(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 8, - "below": 12, + "above": above, + "below": below, "for": "{{ five }}", }, "action": {"service": "test.automation"}, @@ -1262,7 +1457,18 @@ async def test_invalid_for_template(hass, calls): assert mock_logger.error.called -async def test_if_fires_on_entities_change_overlap_for_template(hass, calls): +@pytest.mark.parametrize( + "above, below", + ( + (8, 12), + (8, "input_number.value_12"), + ("input_number.value_8", 12), + ("input_number.value_8", "input_number.value_12"), + ), +) +async def test_if_fires_on_entities_change_overlap_for_template( + hass, calls, above, below +): """Test for firing on entities change with overlap and for template.""" hass.states.async_set("test.entity_1", 0) hass.states.async_set("test.entity_2", 0) @@ -1276,8 +1482,8 @@ async def test_if_fires_on_entities_change_overlap_for_template(hass, calls): "trigger": { "platform": "numeric_state", "entity_id": ["test.entity_1", "test.entity_2"], - "above": 8, - "below": 12, + "above": above, + "below": below, "for": '{{ 5 if trigger.entity_id == "test.entity_1"' " else 10 }}", }, @@ -1335,7 +1541,30 @@ def test_below_above(): ) -async def test_attribute_if_fires_on_entity_change_with_both_filters(hass, calls): +def test_schema_input_number(): + """Test input_number only is accepted for above/below.""" + with pytest.raises(vol.Invalid): + numeric_state_trigger.TRIGGER_SCHEMA( + { + "platform": "numeric_state", + "above": "input_datetime.some_input", + "below": 1000, + } + ) + with pytest.raises(vol.Invalid): + numeric_state_trigger.TRIGGER_SCHEMA( + { + "platform": "numeric_state", + "below": "input_datetime.some_input", + "above": 1200, + } + ) + + +@pytest.mark.parametrize("above", (3, "input_number.value_3")) +async def test_attribute_if_fires_on_entity_change_with_both_filters( + hass, calls, above +): """Test for firing if both filters are match attribute.""" hass.states.async_set("test.entity", "bla", {"test-measurement": 1}) @@ -1347,7 +1576,7 @@ async def test_attribute_if_fires_on_entity_change_with_both_filters(hass, calls "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 3, + "above": above, "attribute": "test-measurement", }, "action": {"service": "test.automation"}, @@ -1361,8 +1590,9 @@ async def test_attribute_if_fires_on_entity_change_with_both_filters(hass, calls assert len(calls) == 1 +@pytest.mark.parametrize("above", (3, "input_number.value_3")) async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( - hass, calls + hass, calls, above ): """Test for not firing on entity change with for after stop trigger.""" hass.states.async_set("test.entity", "bla", {"test-measurement": 1}) @@ -1375,7 +1605,7 @@ async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( "trigger": { "platform": "numeric_state", "entity_id": "test.entity", - "above": 3, + "above": above, "attribute": "test-measurement", "for": 5, },