From ce144bf63145813c76fbbe4f9423341764695057 Mon Sep 17 00:00:00 2001 From: Khole Date: Sat, 25 Jun 2022 23:13:30 +0100 Subject: [PATCH] Add Hive device configuration to config flow (#73955) Co-authored-by: Martin Hjelmare --- homeassistant/components/hive/config_flow.py | 37 +++- homeassistant/components/hive/const.py | 1 + homeassistant/components/hive/manifest.json | 2 +- homeassistant/components/hive/strings.json | 9 +- .../components/hive/translations/en.json | 9 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/hive/test_config_flow.py | 162 +++++++++++++++--- 8 files changed, 190 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py index 16d83dc311d..5368aa22c3f 100644 --- a/homeassistant/components/hive/config_flow.py +++ b/homeassistant/components/hive/config_flow.py @@ -14,7 +14,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.core import callback -from .const import CONF_CODE, CONFIG_ENTRY_VERSION, DOMAIN +from .const import CONF_CODE, CONF_DEVICE_NAME, CONFIG_ENTRY_VERSION, DOMAIN class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -29,6 +29,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.tokens = {} self.entry = None self.device_registration = False + self.device_name = "Home Assistant" async def async_step_user(self, user_input=None): """Prompt user input. Create or edit entry.""" @@ -60,7 +61,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_2fa() if not errors: - # Complete the entry setup. + # Complete the entry. try: return await self.async_setup_hive_entry() except UnknownHiveError: @@ -89,15 +90,36 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "no_internet_available" if not errors: - try: - self.device_registration = True + if self.context["source"] == config_entries.SOURCE_REAUTH: return await self.async_setup_hive_entry() - except UnknownHiveError: - errors["base"] = "unknown" + self.device_registration = True + return await self.async_step_configuration() schema = vol.Schema({vol.Required(CONF_CODE): str}) return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors) + async def async_step_configuration(self, user_input=None): + """Handle hive configuration step.""" + errors = {} + + if user_input: + if self.device_registration: + self.device_name = user_input["device_name"] + await self.hive_auth.device_registration(user_input["device_name"]) + self.data["device_data"] = await self.hive_auth.get_device_data() + + try: + return await self.async_setup_hive_entry() + except UnknownHiveError: + errors["base"] = "unknown" + + schema = vol.Schema( + {vol.Optional(CONF_DEVICE_NAME, default=self.device_name): str} + ) + return self.async_show_form( + step_id="configuration", data_schema=schema, errors=errors + ) + async def async_setup_hive_entry(self): """Finish setup and create the config entry.""" @@ -105,9 +127,6 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): raise UnknownHiveError # Setup the config entry - if self.device_registration: - await self.hive_auth.device_registration("Home Assistant") - self.data["device_data"] = await self.hive_auth.getDeviceData() self.data["tokens"] = self.tokens if self.context["source"] == config_entries.SOURCE_REAUTH: self.hass.config_entries.async_update_entry( diff --git a/homeassistant/components/hive/const.py b/homeassistant/components/hive/const.py index 82c07761eef..b7a2be6910f 100644 --- a/homeassistant/components/hive/const.py +++ b/homeassistant/components/hive/const.py @@ -5,6 +5,7 @@ ATTR_MODE = "mode" ATTR_TIME_PERIOD = "time_period" ATTR_ONOFF = "on_off" CONF_CODE = "2fa" +CONF_DEVICE_NAME = "device_name" CONFIG_ENTRY_VERSION = 1 DEFAULT_NAME = "Hive" DOMAIN = "hive" diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 29477bf7414..406b32d86f8 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -6,7 +6,7 @@ "models": ["HHKBridge*"] }, "documentation": "https://www.home-assistant.io/integrations/hive", - "requirements": ["pyhiveapi==0.5.11"], + "requirements": ["pyhiveapi==0.5.13"], "codeowners": ["@Rendili", "@KJonline"], "iot_class": "cloud_polling", "loggers": ["apyhiveapi"] diff --git a/homeassistant/components/hive/strings.json b/homeassistant/components/hive/strings.json index 7628abc5b06..3435517aec7 100644 --- a/homeassistant/components/hive/strings.json +++ b/homeassistant/components/hive/strings.json @@ -3,7 +3,7 @@ "step": { "user": { "title": "Hive Login", - "description": "Enter your Hive login information and configuration.", + "description": "Enter your Hive login information.", "data": { "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", @@ -17,6 +17,13 @@ "2fa": "Two-factor code" } }, + "configuration": { + "data": { + "device_name": "Device Name" + }, + "description": "Enter your Hive configuration ", + "title": "Hive Configuration." + }, "reauth": { "title": "Hive Login", "description": "Re-enter your Hive login information.", diff --git a/homeassistant/components/hive/translations/en.json b/homeassistant/components/hive/translations/en.json index 32453da0a0c..3ef7b3d0f43 100644 --- a/homeassistant/components/hive/translations/en.json +++ b/homeassistant/components/hive/translations/en.json @@ -20,6 +20,13 @@ "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", "title": "Hive Two-factor Authentication." }, + "configuration": { + "data": { + "device_name": "Device Name" + }, + "description": "Enter your Hive configuration ", + "title": "Hive Configuration." + }, "reauth": { "data": { "password": "Password", @@ -34,7 +41,7 @@ "scan_interval": "Scan Interval (seconds)", "username": "Username" }, - "description": "Enter your Hive login information and configuration.", + "description": "Enter your Hive login information.", "title": "Hive Login" } } diff --git a/requirements_all.txt b/requirements_all.txt index 656cd4d5bcd..dca486c0d3f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1541,7 +1541,7 @@ pyheos==0.7.2 pyhik==0.3.0 # homeassistant.components.hive -pyhiveapi==0.5.11 +pyhiveapi==0.5.13 # homeassistant.components.homematic pyhomematic==0.1.77 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4728716b779..73f98c5eafc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1035,7 +1035,7 @@ pyhaversion==22.4.1 pyheos==0.7.2 # homeassistant.components.hive -pyhiveapi==0.5.11 +pyhiveapi==0.5.13 # homeassistant.components.homematic pyhomematic==0.1.77 diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py index 35e20e8eee3..e6e2a06501a 100644 --- a/tests/components/hive/test_config_flow.py +++ b/tests/components/hive/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import patch from apyhiveapi.helper import hive_exceptions from homeassistant import config_entries, data_entry_flow -from homeassistant.components.hive.const import CONF_CODE, DOMAIN +from homeassistant.components.hive.const import CONF_CODE, CONF_DEVICE_NAME, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from tests.common import MockConfigEntry @@ -16,6 +16,7 @@ UPDATED_PASSWORD = "updated-password" INCORRECT_PASSWORD = "incorrect-password" SCAN_INTERVAL = 120 UPDATED_SCAN_INTERVAL = 60 +DEVICE_NAME = "Test Home Assistant" MFA_CODE = "1234" MFA_RESEND_CODE = "0000" MFA_INVALID_CODE = "HIVE" @@ -148,11 +149,23 @@ async def test_user_flow_2fa(hass): "AccessToken": "mock-access-token", }, }, - ), patch( + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_CODE: MFA_CODE, + }, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == "configuration" + assert result3["errors"] == {} + + with patch( "homeassistant.components.hive.config_flow.Auth.device_registration", return_value=True, ), patch( - "homeassistant.components.hive.config_flow.Auth.getDeviceData", + "homeassistant.components.hive.config_flow.Auth.get_device_data", return_value=[ "mock-device-group-key", "mock-device-key", @@ -164,14 +177,17 @@ async def test_user_flow_2fa(hass): "homeassistant.components.hive.async_setup_entry", return_value=True, ) as mock_setup_entry: - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], {CONF_CODE: MFA_CODE} + result4 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_DEVICE_NAME: DEVICE_NAME, + }, ) await hass.async_block_till_done() - assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result3["title"] == USERNAME - assert result3["data"] == { + assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result4["title"] == USERNAME + assert result4["data"] == { CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, "tokens": { @@ -235,9 +251,6 @@ async def test_reauth_flow(hass): "AccessToken": "mock-access-token", }, }, - ), patch( - "homeassistant.components.hive.config_flow.Auth.device_registration", - return_value=True, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -255,6 +268,82 @@ async def test_reauth_flow(hass): assert len(hass.config_entries.async_entries(DOMAIN)) == 1 +async def test_reauth_2fa_flow(hass): + """Test the reauth flow.""" + + mock_config = MockConfigEntry( + domain=DOMAIN, + unique_id=USERNAME, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: INCORRECT_PASSWORD, + "tokens": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + }, + ) + mock_config.add_to_hass(hass) + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidPassword(), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": mock_config.unique_id, + }, + data=mock_config.data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_password"} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: UPDATED_PASSWORD, + }, + ) + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + CONF_CODE: MFA_CODE, + }, + ) + await hass.async_block_till_done() + + assert mock_config.data.get("username") == USERNAME + assert mock_config.data.get("password") == UPDATED_PASSWORD + assert result3["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result3["reason"] == "reauth_successful" + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + async def test_option_flow(hass): """Test config flow options.""" @@ -343,11 +432,23 @@ async def test_user_flow_2fa_send_new_code(hass): "AccessToken": "mock-access-token", }, }, - ), patch( + ): + result4 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_CODE: MFA_CODE, + }, + ) + + assert result4["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result4["step_id"] == "configuration" + assert result4["errors"] == {} + + with patch( "homeassistant.components.hive.config_flow.Auth.device_registration", return_value=True, ), patch( - "homeassistant.components.hive.config_flow.Auth.getDeviceData", + "homeassistant.components.hive.config_flow.Auth.get_device_data", return_value=[ "mock-device-group-key", "mock-device-key", @@ -359,14 +460,14 @@ async def test_user_flow_2fa_send_new_code(hass): "homeassistant.components.hive.async_setup_entry", return_value=True, ) as mock_setup_entry: - result4 = await hass.config_entries.flow.async_configure( - result3["flow_id"], {CONF_CODE: MFA_CODE} + result5 = await hass.config_entries.flow.async_configure( + result4["flow_id"], {CONF_DEVICE_NAME: DEVICE_NAME} ) await hass.async_block_till_done() - assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result4["title"] == USERNAME - assert result4["data"] == { + assert result5["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result5["title"] == USERNAME + assert result5["data"] == { CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, "tokens": { @@ -610,7 +711,28 @@ async def test_user_flow_2fa_unknown_error(hass): result2["flow_id"], {CONF_CODE: MFA_CODE}, ) - await hass.async_block_till_done() assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result3["errors"] == {"base": "unknown"} + assert result3["step_id"] == "configuration" + assert result3["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.device_registration", + return_value=True, + ), patch( + "homeassistant.components.hive.config_flow.Auth.get_device_data", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], + ): + result4 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_DEVICE_NAME: DEVICE_NAME}, + ) + await hass.async_block_till_done() + + assert result4["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result4["step_id"] == "configuration" + assert result4["errors"] == {"base": "unknown"}