From e53513301696fb31b01589a68607752fe689bff1 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 19 Jan 2020 23:13:15 -0500 Subject: [PATCH] Fix options update during import config flow step for vizio component (Bugfix for #30653) (#30977) * fix options update logic during import * add missing tests * fix abort reasons and strings, add missing test * combine steps when testing esn already exists * readd removed test * no mock_coro_func needed * add block_until_done and assert entry options --- homeassistant/components/vizio/config_flow.py | 19 +-- homeassistant/components/vizio/strings.json | 8 +- tests/components/vizio/test_config_flow.py | 108 +++++++++++++++--- 3 files changed, 107 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index b02be9a5934..560b01df83a 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -111,7 +111,9 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if await self.async_set_unique_id( unique_id=unique_id, raise_on_progress=True ): - return self.async_abort(reason="already_setup") + return self.async_abort( + reason="already_setup_with_diff_host_and_name" + ) return self.async_create_entry( title=user_input[CONF_NAME], data=user_input @@ -128,16 +130,19 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if entry.data[CONF_HOST] == import_config[CONF_HOST] and entry.data[ CONF_NAME ] == import_config.get(CONF_NAME): - new_options = {} + updated_options = {} if entry.data[CONF_VOLUME_STEP] != import_config[CONF_VOLUME_STEP]: - new_options[CONF_VOLUME_STEP] = import_config[CONF_VOLUME_STEP] + updated_options[CONF_VOLUME_STEP] = import_config[CONF_VOLUME_STEP] + + if updated_options: + new_data = entry.data.copy() + new_data.update(updated_options) + new_options = entry.options.copy() + new_options.update(updated_options) - if new_options: self.hass.config_entries.async_update_entry( - entry=entry, - data=entry.data.copy().update(new_options), - options=entry.options.copy().update(new_options), + entry=entry, data=new_data, options=new_options, ) return self.async_abort(reason="updated_options") diff --git a/homeassistant/components/vizio/strings.json b/homeassistant/components/vizio/strings.json index 07bbfc666cf..a6367cb3c8f 100644 --- a/homeassistant/components/vizio/strings.json +++ b/homeassistant/components/vizio/strings.json @@ -13,16 +13,14 @@ } }, "error": { - "host_exists": "Host already configured.", - "name_exists": "Name already configured.", + "host_exists": "Vizio device with specified host already configured.", + "name_exists": "Vizio device with specified name already configured.", "cant_connect": "Could not connect to the device. [Review the docs](https://www.home-assistant.io/integrations/vizio/) and re-verify that:\n- The device is powered on\n- The device is connected to the network\n- The values you filled in are accurate\nbefore attempting to resubmit.", "tv_needs_token": "When Device Type is `tv` then a valid Access Token is needed." }, "abort": { - "already_in_progress": "Config flow for vizio component already in progress.", "already_setup": "This entry has already been setup.", - "host_exists": "Vizio component with host already configured.", - "name_exists": "Vizio component with name already configured.", + "already_setup_with_diff_host_and_name": "This entry appears to have already been setup with a different host and name based on its serial number. Please remove any old entries from your configuration.yaml and from the Integrations menu before reattempting to add this device.", "updated_options": "This entry has already been setup but the options defined in the config do not match the previously imported options values so the config entry has been updated accordingly." } }, diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index c8255b9f5fe..4dbb375c3fe 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -14,6 +14,7 @@ from homeassistant.components.vizio.const import ( DOMAIN, VIZIO_SCHEMA, ) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_DEVICE_CLASS, @@ -27,7 +28,9 @@ from tests.common import MockConfigEntry _LOGGER = logging.getLogger(__name__) NAME = "Vizio" +NAME2 = "Vizio2" HOST = "192.168.1.1:9000" +HOST2 = "192.168.1.2:9000" ACCESS_TOKEN = "deadbeef" VOLUME_STEP = 2 UNIQUE_ID = "testid" @@ -69,12 +72,27 @@ def vizio_connect_fixture(): ), patch( "homeassistant.components.vizio.config_flow.VizioAsync.get_unique_id", return_value=UNIQUE_ID, - ), patch( - "homeassistant.components.vizio.async_setup_entry", return_value=True ): yield +@pytest.fixture(name="vizio_bypass_setup") +def vizio_bypass_setup_fixture(): + """Mock component setup.""" + with patch("homeassistant.components.vizio.async_setup_entry", return_value=True): + yield + + +@pytest.fixture(name="vizio_bypass_update") +def vizio_bypass_update_fixture(): + """Mock component update.""" + with patch( + "homeassistant.components.vizio.media_player.VizioAsync.can_connect", + return_value=True, + ), patch("homeassistant.components.vizio.media_player.VizioDevice.async_update"): + yield + + @pytest.fixture(name="vizio_cant_connect") def vizio_cant_connect_fixture(): """Mock vizio device cant connect.""" @@ -85,11 +103,13 @@ def vizio_cant_connect_fixture(): yield -async def test_user_flow_minimum_fields(hass: HomeAssistantType, vizio_connect) -> None: +async def test_user_flow_minimum_fields( + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup +) -> None: """Test user config flow with minimum fields.""" # test form shows result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" @@ -110,11 +130,13 @@ async def test_user_flow_minimum_fields(hass: HomeAssistantType, vizio_connect) assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER -async def test_user_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> None: +async def test_user_flow_all_fields( + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup +) -> None: """Test user config flow with all fields.""" # test form shows result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -162,7 +184,7 @@ async def test_options_flow(hass: HomeAssistantType) -> None: async def test_user_host_already_configured( - hass: HomeAssistantType, vizio_connect + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup ) -> None: """Test host is already configured during user setup.""" entry = MockConfigEntry( @@ -175,7 +197,7 @@ async def test_user_host_already_configured( fail_entry[CONF_NAME] = "newtestname" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -190,7 +212,7 @@ async def test_user_host_already_configured( async def test_user_name_already_configured( - hass: HomeAssistantType, vizio_connect + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup ) -> None: """Test name is already configured during user setup.""" entry = MockConfigEntry( @@ -204,7 +226,7 @@ async def test_user_name_already_configured( fail_entry[CONF_HOST] = "0.0.0.0" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" @@ -217,12 +239,34 @@ async def test_user_name_already_configured( assert result["errors"] == {CONF_NAME: "name_exists"} +async def test_user_esn_already_exists( + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup +) -> None: + """Test ESN is already configured with different host and name during user setup.""" + # Set up new entry + MockConfigEntry( + domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID + ).add_to_hass(hass) + + # Set up new entry with same unique_id but different host and name + fail_entry = MOCK_SPEAKER_CONFIG.copy() + fail_entry[CONF_HOST] = HOST2 + fail_entry[CONF_NAME] = NAME2 + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=fail_entry + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_setup_with_diff_host_and_name" + + async def test_user_error_on_could_not_connect( hass: HomeAssistantType, vizio_cant_connect ) -> None: """Test with could_not_connect during user_setup.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -236,11 +280,11 @@ async def test_user_error_on_could_not_connect( async def test_user_error_on_tv_needs_token( - hass: HomeAssistantType, vizio_connect + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup ) -> None: """Test when config fails custom validation for non null access token when device_class = tv during user setup.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "user"} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -255,7 +299,7 @@ async def test_user_error_on_tv_needs_token( async def test_import_flow_minimum_fields( - hass: HomeAssistantType, vizio_connect + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup ) -> None: """Test import config flow with minimum fields.""" result = await hass.config_entries.flow.async_init( @@ -274,7 +318,9 @@ async def test_import_flow_minimum_fields( assert result["data"][CONF_VOLUME_STEP] == DEFAULT_VOLUME_STEP -async def test_import_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> None: +async def test_import_flow_all_fields( + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup +) -> None: """Test import config flow with all fields.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -292,7 +338,7 @@ async def test_import_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> async def test_import_entity_already_configured( - hass: HomeAssistantType, vizio_connect + hass: HomeAssistantType, vizio_connect, vizio_bypass_setup ) -> None: """Test entity is already configured during import setup.""" entry = MockConfigEntry( @@ -309,3 +355,33 @@ async def test_import_entity_already_configured( assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" + + +async def test_import_flow_update_options( + hass: HomeAssistantType, vizio_connect, vizio_bypass_update +) -> None: + """Test import config flow with updated options.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_CONFIG), + ) + await hass.async_block_till_done() + assert result["result"].options == {CONF_VOLUME_STEP: VOLUME_STEP} + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entry_id = result["result"].entry_id + + updated_config = MOCK_IMPORT_VALID_TV_CONFIG.copy() + updated_config[CONF_VOLUME_STEP] = VOLUME_STEP + 1 + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=vol.Schema(VIZIO_SCHEMA)(updated_config), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "updated_options" + assert ( + hass.config_entries.async_get_entry(entry_id).options[CONF_VOLUME_STEP] + == VOLUME_STEP + 1 + )