core/tests/components/template/test_number.py
Petro31 8d95fb3b31
Fix empty actions (#142292)
* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test
2025-04-04 22:17:52 +02:00

642 lines
20 KiB
Python

"""The tests for the Template number platform."""
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import number, template
from homeassistant.components.input_number import (
ATTR_VALUE as INPUT_NUMBER_ATTR_VALUE,
DOMAIN as INPUT_NUMBER_DOMAIN,
SERVICE_SET_VALUE as INPUT_NUMBER_SERVICE_SET_VALUE,
)
from homeassistant.components.number import (
ATTR_MAX,
ATTR_MIN,
ATTR_STEP,
ATTR_VALUE as NUMBER_ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE as NUMBER_SERVICE_SET_VALUE,
)
from homeassistant.components.template import DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ICON,
CONF_ENTITY_ID,
CONF_UNIT_OF_MEASUREMENT,
STATE_UNKNOWN,
)
from homeassistant.core import Context, HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from tests.common import MockConfigEntry, assert_setup_component, async_capture_events
_TEST_OBJECT_ID = "template_number"
_TEST_NUMBER = f"number.{_TEST_OBJECT_ID}"
# Represent for number's value
_VALUE_INPUT_NUMBER = "input_number.value"
# Represent for number's minimum
_MINIMUM_INPUT_NUMBER = "input_number.minimum"
# Represent for number's maximum
_MAXIMUM_INPUT_NUMBER = "input_number.maximum"
# Represent for number's step
_STEP_INPUT_NUMBER = "input_number.step"
# Config for `_VALUE_INPUT_NUMBER`
_VALUE_INPUT_NUMBER_CONFIG = {
"value": {
"min": 0.0,
"max": 100.0,
"name": "Value",
"step": 1.0,
"mode": "slider",
}
}
async def async_setup_modern_format(
hass: HomeAssistant, count: int, number_config: dict[str, Any]
) -> None:
"""Do setup of number integration via new format."""
config = {"template": {"number": number_config}}
with assert_setup_component(count, template.DOMAIN):
assert await async_setup_component(
hass,
template.DOMAIN,
config,
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
@pytest.fixture
async def setup_number(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
number_config: dict[str, Any],
) -> None:
"""Do setup of number integration."""
if style == ConfigurationStyle.MODERN:
await async_setup_modern_format(
hass, count, {"name": _TEST_OBJECT_ID, **number_config}
)
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Test the config flow."""
template_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
"name": "My template",
"template_type": "number",
"state": "{{ 10 }}",
"min": 0,
"max": 100,
"step": 0.1,
"set_value": {
"action": "input_number.set_value",
"target": {"entity_id": "input_number.test"},
"data": {"value": "{{ value }}"},
},
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("number.my_template")
assert state is not None
assert state == snapshot
async def test_missing_optional_config(hass: HomeAssistant) -> None:
"""Test: missing optional template is ok."""
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"number": {
"state": "{{ 4 }}",
"set_value": {"service": "script.set_value"},
"step": "{{ 1 }}",
}
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, 4, 1, 0.0, 100.0, None)
async def test_missing_required_keys(hass: HomeAssistant) -> None:
"""Test: missing required fields will fail."""
with assert_setup_component(0, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"number": {
"set_value": {"service": "script.set_value"},
}
}
},
)
with assert_setup_component(0, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"number": {
"state": "{{ 4 }}",
}
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.async_all("number") == []
async def test_all_optional_config(hass: HomeAssistant) -> None:
"""Test: including all optional templates is ok."""
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"number": {
"state": "{{ 4 }}",
"set_value": {"service": "script.set_value"},
"min": "{{ 3 }}",
"max": "{{ 5 }}",
"step": "{{ 1 }}",
"unit_of_measurement": "beer",
}
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, 4, 1, 3, 5, "beer")
async def test_templates_with_entities(
hass: HomeAssistant, entity_registry: er.EntityRegistry, calls: list[ServiceCall]
) -> None:
"""Test templates with values from other entities."""
with assert_setup_component(4, "input_number"):
assert await setup.async_setup_component(
hass,
"input_number",
{
"input_number": {
**_VALUE_INPUT_NUMBER_CONFIG,
"step": {
"min": 0.0,
"max": 100.0,
"name": "Step",
"step": 1.0,
"mode": "slider",
},
"minimum": {
"min": 0.0,
"max": 100.0,
"name": "Minimum",
"step": 1.0,
"mode": "slider",
},
"maximum": {
"min": 0.0,
"max": 100.0,
"name": "Maximum",
"step": 1.0,
"mode": "slider",
},
}
},
)
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"unique_id": "b",
"number": {
"state": f"{{{{ states('{_VALUE_INPUT_NUMBER}') }}}}",
"step": f"{{{{ states('{_STEP_INPUT_NUMBER}') }}}}",
"min": f"{{{{ states('{_MINIMUM_INPUT_NUMBER}') }}}}",
"max": f"{{{{ states('{_MAXIMUM_INPUT_NUMBER}') }}}}",
"set_value": [
{
"service": "input_number.set_value",
"data_template": {
"entity_id": _VALUE_INPUT_NUMBER,
"value": "{{ value }}",
},
},
{
"service": "test.automation",
"data_template": {
"action": "set_value",
"caller": "{{ this.entity_id }}",
"value": "{{ value }}",
},
},
],
"optimistic": True,
"unique_id": "a",
},
}
},
)
hass.states.async_set(_VALUE_INPUT_NUMBER, 4)
hass.states.async_set(_STEP_INPUT_NUMBER, 1)
hass.states.async_set(_MINIMUM_INPUT_NUMBER, 3)
hass.states.async_set(_MAXIMUM_INPUT_NUMBER, 5)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
entry = entity_registry.async_get(_TEST_NUMBER)
assert entry
assert entry.unique_id == "b-a"
_verify(hass, 4, 1, 3, 5, None)
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 5},
blocking=True,
)
await hass.async_block_till_done()
_verify(hass, 5, 1, 3, 5, None)
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _STEP_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 2},
blocking=True,
)
await hass.async_block_till_done()
_verify(hass, 5, 2, 3, 5, None)
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _MINIMUM_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 2},
blocking=True,
)
await hass.async_block_till_done()
_verify(hass, 5, 2, 2, 5, None)
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _MAXIMUM_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 6},
blocking=True,
)
await hass.async_block_till_done()
_verify(hass, 5, 2, 2, 6, None)
await hass.services.async_call(
NUMBER_DOMAIN,
NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _TEST_NUMBER, NUMBER_ATTR_VALUE: 2},
blocking=True,
)
_verify(hass, 2, 2, 2, 6, None)
# Check this variable can be used in set_value script
assert len(calls) == 1
assert calls[-1].data["action"] == "set_value"
assert calls[-1].data["caller"] == _TEST_NUMBER
assert calls[-1].data["value"] == 2
async def test_trigger_number(hass: HomeAssistant) -> None:
"""Test trigger based template number."""
events = async_capture_events(hass, "test_number_event")
assert await setup.async_setup_component(
hass,
"template",
{
"template": [
{"invalid": "config"},
# Config after invalid should still be set up
{
"unique_id": "listening-test-event",
"trigger": {"platform": "event", "event_type": "test_event"},
"number": [
{
"name": "Hello Name",
"unique_id": "hello_name-id",
"state": "{{ trigger.event.data.beers_drank }}",
"min": "{{ trigger.event.data.min_beers }}",
"max": "{{ trigger.event.data.max_beers }}",
"step": "{{ trigger.event.data.step }}",
"unit_of_measurement": "beer",
"set_value": {
"event": "test_number_event",
"event_data": {"entity_id": "{{ this.entity_id }}"},
},
"optimistic": True,
},
],
},
],
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("number.hello_name")
assert state is not None
assert state.state == STATE_UNKNOWN
assert state.attributes["min"] == 0.0
assert state.attributes["max"] == 100.0
assert state.attributes["step"] == 1.0
assert state.attributes["unit_of_measurement"] == "beer"
context = Context()
hass.bus.async_fire(
"test_event",
{
"beers_drank": 3,
"min_beers": 1.0,
"max_beers": 5.0,
"step": 0.5,
},
context=context,
)
await hass.async_block_till_done()
state = hass.states.get("number.hello_name")
assert state is not None
assert state.state == "3.0"
assert state.attributes["min"] == 1.0
assert state.attributes["max"] == 5.0
assert state.attributes["step"] == 0.5
await hass.services.async_call(
NUMBER_DOMAIN,
NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: "number.hello_name", NUMBER_ATTR_VALUE: 2},
blocking=True,
)
assert len(events) == 1
assert events[0].event_type == "test_number_event"
entity_id = events[0].data.get("entity_id")
assert entity_id is not None
assert entity_id == "number.hello_name"
def _verify(
hass: HomeAssistant,
expected_value: int,
expected_step: int,
expected_minimum: int,
expected_maximum: int,
expected_unit_of_measurement: str | None,
) -> None:
"""Verify number's state."""
state = hass.states.get(_TEST_NUMBER)
attributes = state.attributes
assert state.state == str(float(expected_value))
assert attributes.get(ATTR_STEP) == float(expected_step)
assert attributes.get(ATTR_MAX) == float(expected_maximum)
assert attributes.get(ATTR_MIN) == float(expected_minimum)
assert attributes.get(CONF_UNIT_OF_MEASUREMENT) == expected_unit_of_measurement
async def test_icon_template(hass: HomeAssistant) -> None:
"""Test template numbers with icon templates."""
with assert_setup_component(1, "input_number"):
assert await setup.async_setup_component(
hass,
"input_number",
{"input_number": _VALUE_INPUT_NUMBER_CONFIG},
)
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"unique_id": "b",
"number": {
"state": f"{{{{ states('{_VALUE_INPUT_NUMBER}') }}}}",
"step": 1,
"min": 0,
"max": 100,
"set_value": {
"service": "input_number.set_value",
"data_template": {
"entity_id": _VALUE_INPUT_NUMBER,
"value": "{{ value }}",
},
},
"icon": "{% if ((states.input_number.value.state or 0) | int) > 50 %}mdi:greater{% else %}mdi:less{% endif %}",
},
}
},
)
hass.states.async_set(_VALUE_INPUT_NUMBER, 49)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 49
assert state.attributes[ATTR_ICON] == "mdi:less"
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 51},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 51
assert state.attributes[ATTR_ICON] == "mdi:greater"
async def test_icon_template_with_trigger(hass: HomeAssistant) -> None:
"""Test template numbers with icon templates."""
with assert_setup_component(1, "input_number"):
assert await setup.async_setup_component(
hass,
"input_number",
{"input_number": _VALUE_INPUT_NUMBER_CONFIG},
)
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"trigger": {"platform": "state", "entity_id": _VALUE_INPUT_NUMBER},
"unique_id": "b",
"number": {
"state": "{{ trigger.to_state.state }}",
"step": 1,
"min": 0,
"max": 100,
"set_value": {
"service": "input_number.set_value",
"data_template": {
"entity_id": _VALUE_INPUT_NUMBER,
"value": "{{ value }}",
},
},
"icon": "{% if ((trigger.to_state.state or 0) | int) > 50 %}mdi:greater{% else %}mdi:less{% endif %}",
},
}
},
)
hass.states.async_set(_VALUE_INPUT_NUMBER, 49)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 49
assert state.attributes[ATTR_ICON] == "mdi:less"
await hass.services.async_call(
INPUT_NUMBER_DOMAIN,
INPUT_NUMBER_SERVICE_SET_VALUE,
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 51},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 51
assert state.attributes[ATTR_ICON] == "mdi:greater"
async def test_device_id(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test for device for number template."""
device_config_entry = MockConfigEntry()
device_config_entry.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
config_entry_id=device_config_entry.entry_id,
identifiers={("test", "identifier_test")},
connections={("mac", "30:31:32:33:34:35")},
)
await hass.async_block_till_done()
assert device_entry is not None
assert device_entry.id is not None
template_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
"name": "My template",
"template_type": "number",
"state": "{{ 10 }}",
"min": 0,
"max": 100,
"step": 0.1,
"set_value": {
"action": "input_number.set_value",
"target": {"entity_id": "input_number.test"},
"data": {"value": "{{ value }}"},
},
"device_id": device_entry.id,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
template_entity = entity_registry.async_get("number.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id
@pytest.mark.parametrize(
("count", "number_config"),
[
(
1,
{
"state": "{{ 1 }}",
"set_value": [],
"step": "{{ 1 }}",
"optimistic": True,
},
)
],
)
@pytest.mark.parametrize(
"style",
[
ConfigurationStyle.MODERN,
],
)
async def test_empty_action_config(hass: HomeAssistant, setup_number) -> None:
"""Test configuration with empty script."""
await hass.services.async_call(
number.DOMAIN,
number.SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: _TEST_NUMBER, "value": 4},
blocking=True,
)
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 4