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