From 4236764fd5f8d9fa118532b4a643a12e88d78a4b Mon Sep 17 00:00:00 2001
From: Erik Montnemery <erik@montnemery.com>
Date: Thu, 17 Feb 2022 13:11:49 +0100
Subject: [PATCH] Don't allow creating or updating input_select with duplicates
 (#66718)

* Don't allow creating or updating input_select with duplicates

* Simplify error message

* Improve error message
---
 .../components/input_select/__init__.py       | 16 ++++++++++--
 tests/components/input_select/test_init.py    | 26 ++++++-------------
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py
index 288c6d6e588..ae5fc9d251e 100644
--- a/homeassistant/components/input_select/__init__.py
+++ b/homeassistant/components/input_select/__init__.py
@@ -45,15 +45,27 @@ STORAGE_KEY = DOMAIN
 STORAGE_VERSION = 1
 STORAGE_VERSION_MINOR = 2
 
+
+def _unique(options: Any) -> Any:
+    try:
+        return vol.Unique()(options)
+    except vol.Invalid as exc:
+        raise HomeAssistantError("Duplicate options are not allowed") from exc
+
+
 CREATE_FIELDS = {
     vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
-    vol.Required(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]),
+    vol.Required(CONF_OPTIONS): vol.All(
+        cv.ensure_list, vol.Length(min=1), _unique, [cv.string]
+    ),
     vol.Optional(CONF_INITIAL): cv.string,
     vol.Optional(CONF_ICON): cv.icon,
 }
 UPDATE_FIELDS = {
     vol.Optional(CONF_NAME): cv.string,
-    vol.Optional(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]),
+    vol.Optional(CONF_OPTIONS): vol.All(
+        cv.ensure_list, vol.Length(min=1), _unique, [cv.string]
+    ),
     vol.Optional(CONF_INITIAL): cv.string,
     vol.Optional(CONF_ICON): cv.icon,
 }
diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py
index 7a239bac45b..d65140dcbf9 100644
--- a/tests/components/input_select/test_init.py
+++ b/tests/components/input_select/test_init.py
@@ -707,16 +707,12 @@ async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog):
         }
     )
     resp = await client.receive_json()
-    assert resp["success"]
-
-    assert (
-        "Input select 'from storage' with options "
-        "['new option', 'newer option', 'newer option'] "
-        "had duplicated options, the duplicates have been removed"
-    ) in caplog.text
+    assert not resp["success"]
+    assert resp["error"]["code"] == "unknown_error"
+    assert resp["error"]["message"] == "Duplicate options are not allowed"
 
     state = hass.states.get(input_entity_id)
-    assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"]
+    assert state.attributes[ATTR_OPTIONS] == ["yaml update 1", "yaml update 2"]
 
 
 async def test_ws_create(hass, hass_ws_client, storage_setup):
@@ -774,17 +770,11 @@ async def test_ws_create_duplicates(hass, hass_ws_client, storage_setup, caplog)
         }
     )
     resp = await client.receive_json()
-    assert resp["success"]
-
-    assert (
-        "Input select 'New Input' with options "
-        "['new option', 'even newer option', 'even newer option'] "
-        "had duplicated options, the duplicates have been removed"
-    ) in caplog.text
+    assert not resp["success"]
+    assert resp["error"]["code"] == "unknown_error"
+    assert resp["error"]["message"] == "Duplicate options are not allowed"
 
-    state = hass.states.get(input_entity_id)
-    assert state.state == "even newer option"
-    assert state.attributes[ATTR_OPTIONS] == ["new option", "even newer option"]
+    assert not hass.states.get(input_entity_id)
 
 
 async def test_setup_no_config(hass, hass_admin_user):
-- 
GitLab