From 293d455cba56bf92d40dbe9d7265db25048447e3 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:09:04 -0500 Subject: [PATCH] Add check for invalid options with specific platforms (#140082) --- homeassistant/components/template/config.py | 91 +++++++++++++-------- tests/components/template/test_config.py | 50 +++++++++++ 2 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 tests/components/template/test_config.py diff --git a/homeassistant/components/template/config.py b/homeassistant/components/template/config.py index 9c92ed2b334..9963731c784 100644 --- a/homeassistant/components/template/config.py +++ b/homeassistant/components/template/config.py @@ -1,5 +1,6 @@ """Template config validator.""" +from collections.abc import Callable from contextlib import suppress import logging @@ -52,41 +53,63 @@ from .helpers import async_get_blueprints PACKAGE_MERGE_HINT = "list" + +def ensure_domains_do_not_have_trigger_or_action(*keys: str) -> Callable[[dict], dict]: + """Validate that config does not contain trigger and action.""" + domains = set(keys) + + def validate(obj: dict): + options = set(obj.keys()) + if found_domains := domains.intersection(options): + invalid = {CONF_TRIGGER, CONF_ACTION} + if found_invalid := invalid.intersection(set(obj.keys())): + raise vol.Invalid( + f"Unsupported option(s) found for domain {found_domains.pop()}, please remove ({', '.join(found_invalid)}) from your configuration", + ) + + return obj + + return validate + + CONFIG_SECTION_SCHEMA = vol.Schema( - { - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_TRIGGER): cv.TRIGGER_SCHEMA, - vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA, - vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA, - vol.Optional(NUMBER_DOMAIN): vol.All( - cv.ensure_list, [number_platform.NUMBER_SCHEMA] - ), - vol.Optional(SENSOR_DOMAIN): vol.All( - cv.ensure_list, [sensor_platform.SENSOR_SCHEMA] - ), - vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys( - sensor_platform.LEGACY_SENSOR_SCHEMA - ), - vol.Optional(BINARY_SENSOR_DOMAIN): vol.All( - cv.ensure_list, [binary_sensor_platform.BINARY_SENSOR_SCHEMA] - ), - vol.Optional(CONF_BINARY_SENSORS): cv.schema_with_slug_keys( - binary_sensor_platform.LEGACY_BINARY_SENSOR_SCHEMA - ), - vol.Optional(SELECT_DOMAIN): vol.All( - cv.ensure_list, [select_platform.SELECT_SCHEMA] - ), - vol.Optional(BUTTON_DOMAIN): vol.All( - cv.ensure_list, [button_platform.BUTTON_SCHEMA] - ), - vol.Optional(IMAGE_DOMAIN): vol.All( - cv.ensure_list, [image_platform.IMAGE_SCHEMA] - ), - vol.Optional(WEATHER_DOMAIN): vol.All( - cv.ensure_list, [weather_platform.WEATHER_SCHEMA] - ), - }, + vol.All( + { + vol.Optional(CONF_UNIQUE_ID): cv.string, + vol.Optional(CONF_TRIGGER): cv.TRIGGER_SCHEMA, + vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA, + vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA, + vol.Optional(NUMBER_DOMAIN): vol.All( + cv.ensure_list, [number_platform.NUMBER_SCHEMA] + ), + vol.Optional(SENSOR_DOMAIN): vol.All( + cv.ensure_list, [sensor_platform.SENSOR_SCHEMA] + ), + vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys( + sensor_platform.LEGACY_SENSOR_SCHEMA + ), + vol.Optional(BINARY_SENSOR_DOMAIN): vol.All( + cv.ensure_list, [binary_sensor_platform.BINARY_SENSOR_SCHEMA] + ), + vol.Optional(CONF_BINARY_SENSORS): cv.schema_with_slug_keys( + binary_sensor_platform.LEGACY_BINARY_SENSOR_SCHEMA + ), + vol.Optional(SELECT_DOMAIN): vol.All( + cv.ensure_list, [select_platform.SELECT_SCHEMA] + ), + vol.Optional(BUTTON_DOMAIN): vol.All( + cv.ensure_list, [button_platform.BUTTON_SCHEMA] + ), + vol.Optional(IMAGE_DOMAIN): vol.All( + cv.ensure_list, [image_platform.IMAGE_SCHEMA] + ), + vol.Optional(WEATHER_DOMAIN): vol.All( + cv.ensure_list, [weather_platform.WEATHER_SCHEMA] + ), + }, + ensure_domains_do_not_have_trigger_or_action(BUTTON_DOMAIN), + ) ) TEMPLATE_BLUEPRINT_INSTANCE_SCHEMA = vol.Schema( diff --git a/tests/components/template/test_config.py b/tests/components/template/test_config.py new file mode 100644 index 00000000000..b14ff0efa5a --- /dev/null +++ b/tests/components/template/test_config.py @@ -0,0 +1,50 @@ +"""Test Template config.""" + +from __future__ import annotations + +import pytest +import voluptuous as vol + +from homeassistant.components.template.config import CONFIG_SECTION_SCHEMA +from homeassistant.core import HomeAssistant + + +@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) -- GitLab