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