"""Test Template config.""" from __future__ import annotations import pytest import voluptuous as vol from homeassistant.components.template import DOMAIN from homeassistant.components.template.config import ( CONFIG_SECTION_SCHEMA, async_validate_config_section, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.template import Template from homeassistant.setup import async_setup_component @pytest.mark.parametrize( "config", [ { "trigger": {"trigger": "event", "event_type": "my_event"}, "button": { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, { "trigger": {"trigger": "event", "event_type": "my_event"}, "action": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "button": { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, ], ) async def test_invalid_schema(hass: HomeAssistant, config: dict) -> None: """Test invalid config schemas.""" with pytest.raises(vol.Invalid): CONFIG_SECTION_SCHEMA(config) async def test_valid_default_entity_id(hass: HomeAssistant) -> None: """Test valid default_entity_id schemas.""" config = { "button": { "press": [], "default_entity_id": "button.test", }, } assert CONFIG_SECTION_SCHEMA(config) == { "button": [ { "press": [], "name": Template("Template Button", hass), "default_entity_id": "button.test", } ] } @pytest.mark.parametrize( "default_entity_id", [ "foo", "{{ 'my_template' }}", "SJLIVan as dfkaj;heafha faass00", 48, None, "bttn.test", ], ) async def test_invalid_default_entity_id( hass: HomeAssistant, default_entity_id: dict ) -> None: """Test invalid default_entity_id schemas.""" config = { "button": { "press": [], "default_entity_id": default_entity_id, }, } with pytest.raises(vol.Invalid): CONFIG_SECTION_SCHEMA(config) @pytest.mark.parametrize( ("config", "expected_error"), [ ( { "trigger": {"trigger": "event", "event_type": "my_event"}, "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "unique_id": "test", "name": "test", "auto_off": "00:00:01", }, }, None, ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "name": "test", }, }, None, ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: name: Template Binary Sensor", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "name": "test", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: name: test", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "unique_id": "test_unique_id", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: unique_id: test_unique_id", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "name": "test", "unique_id": "test_unique_id", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: name: test", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "default_entity_id": "binary_sensor.test_entity_id", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: default_entity_id: binary_sensor.test_entity_id", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "name": "test", "unique_id": "test_unique_id", "default_entity_id": "binary_sensor.test_entity_id", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: name: test", ), ( { "binary_sensor": { "state": "{{ states('binary_sensor.test') }}", "unique_id": "test_unique_id", "default_entity_id": "binary_sensor.test_entity_id", "auto_off": "00:00:01", }, }, "The auto_off option for template binary sensor: default_entity_id: binary_sensor.test_entity_id", ), ], ) async def test_invalid_binary_sensor_schema_with_auto_off( hass: HomeAssistant, config: dict, expected_error: str | None, caplog: pytest.LogCaptureFixture, ) -> None: """Test invalid config schemas create issue and log warning.""" await async_setup_component(hass, "template", {"template": [config]}) assert ( expected_error is None and "ERROR" not in caplog.text ) or expected_error in caplog.text @pytest.mark.parametrize( ("config", "expected"), [ ( { "variables": {"a": 1}, "button": { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "variables": {"b": 2}, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, {"a": 1, "b": 2}, ), ( { "variables": {"a": 1}, "button": [ { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "variables": {"b": 2}, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", } ], }, {"a": 1, "b": 2}, ), ( { "variables": {"a": 1}, "button": [ { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "variables": {"a": 2, "b": 2}, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", } ], }, {"a": 2, "b": 2}, ), ( { "variables": {"a": 1}, "button": { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, {"a": 1}, ), ( { "button": { "press": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "variables": {"b": 2}, "device_class": "restart", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, {"b": 2}, ), ], ) async def test_combined_state_variables( hass: HomeAssistant, config: dict, expected: dict ) -> None: """Tests combining variables for state based template entities.""" validated = await async_validate_config_section(hass, config) assert "variables" not in validated variables: ScriptVariables = validated["button"][0]["variables"] assert variables.as_dict() == expected @pytest.mark.parametrize( ("config", "expected_root", "expected_entity"), [ ( { "trigger": {"trigger": "event", "event_type": "my_event"}, "variables": {"a": 1}, "binary_sensor": { "name": "test", "state": "{{ trigger.event.event_type }}", "variables": {"b": 2}, }, }, {"a": 1}, {"b": 2}, ), ( { "triggers": {"trigger": "event", "event_type": "my_event"}, "variables": {"a": 1}, "binary_sensor": { "name": "test", "state": "{{ trigger.event.event_type }}", }, }, {"a": 1}, {}, ), ( { "trigger": {"trigger": "event", "event_type": "my_event"}, "binary_sensor": { "name": "test", "state": "{{ trigger.event.event_type }}", "variables": {"b": 2}, }, }, {}, {"b": 2}, ), ], ) async def test_combined_trigger_variables( hass: HomeAssistant, config: dict, expected_root: dict, expected_entity: dict, ) -> None: """Tests variable are not combined for trigger based template entities.""" empty = ScriptVariables({}) validated = await async_validate_config_section(hass, config) root_variables: ScriptVariables = validated.get("variables", empty) assert root_variables.as_dict() == expected_root variables: ScriptVariables = validated["binary_sensor"][0].get("variables", empty) assert variables.as_dict() == expected_entity async def test_state_init_attribute_variables( hass: HomeAssistant, ) -> None: """Test a state based template entity initializes icon, name, and picture with variables.""" source = "switch.foo" entity_id = "sensor.foo" hass.states.async_set(source, "on", {"friendly_name": "Foo"}) config = { "template": [ { "variables": { "switch": "switch.foo", "on_icon": "mdi:lightbulb", "on_picture": "on.png", }, "sensor": { "variables": { "off_icon": "mdi:lightbulb-off", "off_picture": "off.png", }, "name": "{{ state_attr(switch, 'friendly_name') }}", "icon": "{{ on_icon if is_state(switch, 'on') else off_icon }}", "picture": "{{ on_picture if is_state(switch, 'on') else off_picture }}", "state": "{{ is_state(switch, 'on') }}", }, } ], } assert await async_setup_component( hass, DOMAIN, config, ) await hass.async_block_till_done() # Check initial state sensor = hass.states.get(entity_id) assert sensor assert sensor.state == "True" assert sensor.attributes["icon"] == "mdi:lightbulb" assert sensor.attributes["entity_picture"] == "on.png" assert sensor.attributes["friendly_name"] == "Foo" hass.states.async_set(source, "off", {"friendly_name": "Foo"}) await hass.async_block_till_done() # Check to see that the template light works sensor = hass.states.get(entity_id) assert sensor assert sensor.state == "False" assert sensor.attributes["icon"] == "mdi:lightbulb-off" assert sensor.attributes["entity_picture"] == "off.png" assert sensor.attributes["friendly_name"] == "Foo" @pytest.mark.parametrize( ("config", "expected_warning"), [ ( { "trigger": {"trigger": "event", "event_type": "my_event"}, }, "Invalid template configuration found, trigger option is missing matching domain", ), ( { "action": { "service": "test.automation", "data_template": {"caller": "{{ this.entity_id }}"}, }, "sensor": { "state": "{{ states('sensor.test') }}", "unique_id": "test", "name": "test", "icon": "mdi:test", }, }, "Invalid template configuration found, action option requires a trigger", ), ], ) async def test_invalid_schema_raises_issue( hass: HomeAssistant, config: dict, expected_warning: str, issue_registry: ir.IssueRegistry, caplog: pytest.LogCaptureFixture, ) -> None: """Test invalid config schemas create issue and log warning.""" await async_setup_component(hass, "template", {"template": [config]}) assert expected_warning in caplog.text assert len(issue_registry.issues) == 1 issue = next(iter(issue_registry.issues.values())) assert issue.domain == "template" assert issue.severity == ir.IssueSeverity.WARNING