From a334e0c7b9eee57b09149555937b82b89d883f3a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 18 Jan 2022 04:29:55 +0100 Subject: [PATCH] Exception handling config flow yale_smart_alarm (#63623) --- .../yale_smart_alarm/config_flow.py | 70 ++++++------- .../components/yale_smart_alarm/strings.json | 3 +- .../yale_smart_alarm/translations/en.json | 7 +- .../yale_smart_alarm/test_config_flow.py | 97 ++++++++++++++++--- 4 files changed, 127 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index c3eaebaa80c..8994d0b2fbd 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -5,7 +5,7 @@ from typing import Any import voluptuous as vol from yalesmartalarmclient.client import YaleSmartAlarmClient -from yalesmartalarmclient.exceptions import AuthenticationError +from yalesmartalarmclient.exceptions import AuthenticationError, UnknownError from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME @@ -80,24 +80,24 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): ) except AuthenticationError as error: LOGGER.error("Authentication failed. Check credentials %s", error) - return self.async_show_form( - step_id="reauth_confirm", - data_schema=DATA_SCHEMA, - errors={"base": "invalid_auth"}, - ) + errors = {"base": "invalid_auth"} + except (ConnectionError, TimeoutError, UnknownError) as error: + LOGGER.error("Connection to API failed %s", error) + errors = {"base": "cannot_connect"} - existing_entry = await self.async_set_unique_id(username) - if existing_entry: - self.hass.config_entries.async_update_entry( - existing_entry, - data={ - **self.entry.data, - CONF_USERNAME: username, - CONF_PASSWORD: password, - }, - ) - await self.hass.config_entries.async_reload(existing_entry.entry_id) - return self.async_abort(reason="reauth_successful") + if not errors: + existing_entry = await self.async_set_unique_id(username) + if existing_entry: + self.hass.config_entries.async_update_entry( + existing_entry, + data={ + **self.entry.data, + CONF_USERNAME: username, + CONF_PASSWORD: password, + }, + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") return self.async_show_form( step_id="reauth_confirm", @@ -121,25 +121,25 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): ) except AuthenticationError as error: LOGGER.error("Authentication failed. Check credentials %s", error) - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={"base": "invalid_auth"}, + errors = {"base": "invalid_auth"} + except (ConnectionError, TimeoutError, UnknownError) as error: + LOGGER.error("Connection to API failed %s", error) + errors = {"base": "cannot_connect"} + + if not errors: + await self.async_set_unique_id(username) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=username, + data={ + CONF_USERNAME: username, + CONF_PASSWORD: password, + CONF_NAME: name, + CONF_AREA_ID: area, + }, ) - await self.async_set_unique_id(username) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=username, - data={ - CONF_USERNAME: username, - CONF_PASSWORD: password, - CONF_NAME: name, - CONF_AREA_ID: area, - }, - ) - return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, diff --git a/homeassistant/components/yale_smart_alarm/strings.json b/homeassistant/components/yale_smart_alarm/strings.json index 0d08834d6d6..5258e681c05 100644 --- a/homeassistant/components/yale_smart_alarm/strings.json +++ b/homeassistant/components/yale_smart_alarm/strings.json @@ -5,7 +5,8 @@ "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" }, "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": { "user": { diff --git a/homeassistant/components/yale_smart_alarm/translations/en.json b/homeassistant/components/yale_smart_alarm/translations/en.json index 5c70b73d07c..d4b30eb1154 100644 --- a/homeassistant/components/yale_smart_alarm/translations/en.json +++ b/homeassistant/components/yale_smart_alarm/translations/en.json @@ -5,7 +5,8 @@ "reauth_successful": "Re-authentication was successful" }, "error": { - "invalid_auth": "Invalid authentication" + "invalid_auth": "Invalid authentication", + "cannot_connect": "Failed to connect" }, "step": { "reauth_confirm": { @@ -14,8 +15,9 @@ "name": "Name", "password": "Password", "username": "Username" + } } - }, + }, "user": { "data": { "area_id": "Area ID", @@ -24,7 +26,6 @@ "username": "Username" } } - } }, "options": { "error": { diff --git a/tests/components/yale_smart_alarm/test_config_flow.py b/tests/components/yale_smart_alarm/test_config_flow.py index ca4e203a43b..a7c454851bf 100644 --- a/tests/components/yale_smart_alarm/test_config_flow.py +++ b/tests/components/yale_smart_alarm/test_config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations from unittest.mock import patch import pytest -from yalesmartalarmclient.exceptions import AuthenticationError +from yalesmartalarmclient.exceptions import AuthenticationError, UnknownError from homeassistant import config_entries from homeassistant.components.yale_smart_alarm.const import DOMAIN @@ -24,7 +24,7 @@ async def test_form(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM assert result["errors"] == {} with patch( @@ -44,7 +44,7 @@ async def test_form(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - assert result2["type"] == "create_entry" + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "test-username" assert result2["data"] == { "username": "test-username", @@ -55,7 +55,18 @@ async def test_form(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_invalid_auth(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + "sideeffect,p_error", + [ + (AuthenticationError, "invalid_auth"), + (ConnectionError, "cannot_connect"), + (TimeoutError, "cannot_connect"), + (UnknownError, "cannot_connect"), + ], +) +async def test_form_invalid_auth( + hass: HomeAssistant, sideeffect: Exception, p_error: str +) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -63,7 +74,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: with patch( "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - side_effect=AuthenticationError, + side_effect=sideeffect, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -76,8 +87,34 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": p_error} + + with patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + ), patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + "name": "Yale Smart Alarm", + "area_id": "1", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "test-username" + assert result2["data"] == { + "username": "test-username", + "password": "test-password", + "name": "Yale Smart Alarm", + "area_id": "1", + } @pytest.mark.parametrize( @@ -190,7 +227,18 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 -async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + "sideeffect,p_error", + [ + (AuthenticationError, "invalid_auth"), + (ConnectionError, "cannot_connect"), + (TimeoutError, "cannot_connect"), + (UnknownError, "cannot_connect"), + ], +) +async def test_reauth_flow_error( + hass: HomeAssistant, sideeffect: Exception, p_error: str +) -> None: """Test a reauthentication flow.""" entry = MockConfigEntry( domain=DOMAIN, @@ -198,6 +246,8 @@ async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: data={ "username": "test-username", "password": "test-password", + "name": "Yale Smart Alarm", + "area_id": "1", }, ) entry.add_to_hass(hass) @@ -214,7 +264,7 @@ async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: with patch( "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - side_effect=AuthenticationError, + side_effect=sideeffect, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -226,8 +276,33 @@ async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result2["step_id"] == "reauth_confirm" - assert result2["type"] == "form" - assert result2["errors"] == {"base": "invalid_auth"} + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": p_error} + + with patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + return_value="", + ), patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "new-test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert entry.data == { + "username": "test-username", + "password": "new-test-password", + "name": "Yale Smart Alarm", + "area_id": "1", + } async def test_options_flow(hass: HomeAssistant) -> None: