From 59d401e7b7bb70978bdd477de74d1c74621f8263 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 25 Aug 2021 21:56:10 +0200 Subject: [PATCH] Add Nanoleaf reauth flow (#55217) Co-authored-by: J. Nick Koston Co-authored-by: Paulus Schoutsen --- homeassistant/components/nanoleaf/__init__.py | 6 ++- .../components/nanoleaf/config_flow.py | 24 ++++++++++ .../components/nanoleaf/strings.json | 1 + .../components/nanoleaf/translations/en.json | 1 + tests/components/nanoleaf/test_config_flow.py | 47 ++++++++++++++++++- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 84a33a14b3e..be61bbc65a3 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -1,10 +1,10 @@ """The Nanoleaf integration.""" -from pynanoleaf.pynanoleaf import Nanoleaf, Unavailable +from pynanoleaf.pynanoleaf import InvalidToken, Nanoleaf, Unavailable from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DEVICE, DOMAIN, NAME, SERIAL_NO from .util import pynanoleaf_get_info @@ -18,6 +18,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: info = await hass.async_add_executor_job(pynanoleaf_get_info, nanoleaf) except Unavailable as err: raise ConfigEntryNotReady from err + except InvalidToken as err: + raise ConfigEntryAuthFailed from err hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { DEVICE: nanoleaf, diff --git a/homeassistant/components/nanoleaf/config_flow.py b/homeassistant/components/nanoleaf/config_flow.py index 0bd7975bbed..9edfd23e6a9 100644 --- a/homeassistant/components/nanoleaf/config_flow.py +++ b/homeassistant/components/nanoleaf/config_flow.py @@ -32,6 +32,8 @@ USER_SCHEMA: Final = vol.Schema( class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Nanoleaf config flow.""" + reauth_entry: config_entries.ConfigEntry | None = None + VERSION = 1 def __init__(self) -> None: @@ -73,6 +75,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self.async_step_link() + async def async_step_reauth(self, data: dict[str, str]) -> FlowResult: + """Handle Nanoleaf reauth flow if token is invalid.""" + self.reauth_entry = cast( + config_entries.ConfigEntry, + self.hass.config_entries.async_get_entry(self.context["entry_id"]), + ) + self.nanoleaf = Nanoleaf(data[CONF_HOST]) + self.context["title_placeholders"] = {"name": self.reauth_entry.title} + return await self.async_step_link() + async def async_step_zeroconf( self, discovery_info: DiscoveryInfoType ) -> FlowResult: @@ -135,6 +147,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except Exception: # pylint: disable=broad-except _LOGGER.exception("Unknown error authorizing Nanoleaf") return self.async_show_form(step_id="link", errors={"base": "unknown"}) + + if self.reauth_entry is not None: + self.hass.config_entries.async_update_entry( + self.reauth_entry, + data={ + **self.reauth_entry.data, + CONF_TOKEN: self.nanoleaf.token, + }, + ) + await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) + return self.async_abort(reason="reauth_successful") + return await self.async_setup_finish() async def async_step_import(self, config: dict[str, Any]) -> FlowResult: diff --git a/homeassistant/components/nanoleaf/strings.json b/homeassistant/components/nanoleaf/strings.json index b08748757b7..96fcfd2622a 100644 --- a/homeassistant/components/nanoleaf/strings.json +++ b/homeassistant/components/nanoleaf/strings.json @@ -21,6 +21,7 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_token": "[%key:common::config_flow::error::invalid_access_token%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } } diff --git a/homeassistant/components/nanoleaf/translations/en.json b/homeassistant/components/nanoleaf/translations/en.json index e76387d0246..7696f056aa3 100644 --- a/homeassistant/components/nanoleaf/translations/en.json +++ b/homeassistant/components/nanoleaf/translations/en.json @@ -4,6 +4,7 @@ "already_configured": "Device is already configured", "cannot_connect": "Failed to connect", "invalid_token": "Invalid access token", + "reauth_successful": "Re-authentication was successful", "unknown": "Unexpected error" }, "error": { diff --git a/tests/components/nanoleaf/test_config_flow.py b/tests/components/nanoleaf/test_config_flow.py index a84d97fda2a..93db43e40c9 100644 --- a/tests/components/nanoleaf/test_config_flow.py +++ b/tests/components/nanoleaf/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Nanoleaf config flow.""" from __future__ import annotations -from unittest.mock import patch +from unittest.mock import MagicMock, patch from pynanoleaf import InvalidToken, NotAuthorizingNewTokens, Unavailable from pynanoleaf.pynanoleaf import NanoleafError @@ -12,6 +12,8 @@ from homeassistant.components.nanoleaf.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry + TEST_NAME = "Canvas ADF9" TEST_HOST = "192.168.0.100" TEST_OTHER_HOST = "192.168.0.200" @@ -283,6 +285,49 @@ async def test_discovery_link_unavailable( assert result["reason"] == "cannot_connect" +async def test_reauth(hass: HomeAssistant) -> None: + """Test Nanoleaf reauth flow.""" + nanoleaf = MagicMock() + nanoleaf.host = TEST_HOST + nanoleaf.token = TEST_TOKEN + + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_NAME, + data={CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_OTHER_TOKEN}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf", + return_value=nanoleaf, + ), patch( + "homeassistant.components.nanoleaf.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + "unique_id": entry.unique_id, + }, + data=entry.data, + ) + assert result["type"] == "form" + assert result["step_id"] == "link" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" + + assert entry.data[CONF_HOST] == TEST_HOST + assert entry.data[CONF_TOKEN] == TEST_TOKEN + + async def test_import_config(hass: HomeAssistant) -> None: """Test configuration import.""" with patch(