From 6524cd4eb234c925e77d1bd3b03eff71caec464e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Nov 2021 11:46:54 -0600 Subject: [PATCH] Fix user input malformed with deleted entities in HomeKit exclude flow (#60061) --- .../components/homekit/config_flow.py | 17 +++-- tests/components/homekit/test_config_flow.py | 63 ++++++++++++++++++- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index dc7f7b9013e..0d8bf967c5b 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -453,12 +453,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): data_schema = {} entity_schema = vol.In - # Strip out entities that no longer exist to prevent error in the UI - entities = [ - entity_id - for entity_id in entity_filter.get(CONF_INCLUDE_ENTITIES, []) - if entity_id in all_supported_entities - ] + entities = entity_filter.get(CONF_INCLUDE_ENTITIES, []) if self.hk_options[CONF_HOMEKIT_MODE] != HOMEKIT_MODE_ACCESSORY: include_exclude_mode = MODE_INCLUDE if not entities: @@ -469,9 +464,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ] = vol.In(INCLUDE_EXCLUDE_MODES) entity_schema = cv.multi_select - data_schema[vol.Optional(CONF_ENTITIES, default=entities)] = entity_schema( - all_supported_entities - ) + # Strip out entities that no longer exist to prevent error in the UI + valid_entities = [ + entity_id for entity_id in entities if entity_id in all_supported_entities + ] + data_schema[ + vol.Optional(CONF_ENTITIES, default=valid_entities) + ] = entity_schema(all_supported_entities) return self.async_show_form( step_id="include_exclude", data_schema=vol.Schema(data_schema) diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index fb65895604e..f076d8e00ae 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -514,8 +514,10 @@ async def test_options_flow_devices_preserved_when_advanced_off( } -async def test_options_flow_with_non_existant_entity(hass, mock_get_source_ip): - """Test config flow options in include mode.""" +async def test_options_flow_include_mode_with_non_existant_entity( + hass, mock_get_source_ip +): + """Test config flow options in include mode with a non-existent entity.""" config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}, @@ -568,6 +570,63 @@ async def test_options_flow_with_non_existant_entity(hass, mock_get_source_ip): } +async def test_options_flow_exclude_mode_with_non_existant_entity( + hass, mock_get_source_ip +): + """Test config flow options in exclude mode with a non-existent entity.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + options={ + "filter": { + "include_domains": ["climate"], + "exclude_entities": ["climate.not_exist", "climate.front_gate"], + }, + }, + ) + config_entry.add_to_hass(hass) + hass.states.async_set("climate.front_gate", "off") + hass.states.async_set("climate.new", "off") + + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": False} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"domains": ["climate"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "include_exclude" + + entities = result["data_schema"]({})["entities"] + assert "climate.not_exist" not in entities + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.new", "climate.front_gate"], + "include_exclude_mode": "exclude", + }, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == { + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.new", "climate.front_gate"], + "include_domains": ["climate"], + "include_entities": [], + }, + } + + async def test_options_flow_include_mode_basic(hass, mock_get_source_ip): """Test config flow options in include mode."""