Convert Nanoleaf integration to use Async library aionanoleaf (#56548)

This commit is contained in:
Milan Meulemans 2021-09-23 22:37:37 +02:00 committed by GitHub
parent 7ece35cd6f
commit 0b53f73fe2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 136 deletions

View file

@ -695,7 +695,6 @@ omit =
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/light.py homeassistant/components/nanoleaf/light.py
homeassistant/components/nanoleaf/util.py
homeassistant/components/neato/__init__.py homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py homeassistant/components/neato/api.py
homeassistant/components/neato/camera.py homeassistant/components/neato/camera.py

View file

@ -1,21 +1,22 @@
"""The Nanoleaf integration.""" """The Nanoleaf integration."""
from pynanoleaf.pynanoleaf import InvalidToken, Nanoleaf, Unavailable from aionanoleaf import InvalidToken, Nanoleaf, Unavailable
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DEVICE, DOMAIN, NAME, SERIAL_NO from .const import DEVICE, DOMAIN, NAME, SERIAL_NO
from .util import pynanoleaf_get_info
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Nanoleaf from a config entry.""" """Set up Nanoleaf from a config entry."""
nanoleaf = Nanoleaf(entry.data[CONF_HOST]) nanoleaf = Nanoleaf(
nanoleaf.token = entry.data[CONF_TOKEN] async_get_clientsession(hass), entry.data[CONF_HOST], entry.data[CONF_TOKEN]
)
try: try:
info = await hass.async_add_executor_job(pynanoleaf_get_info, nanoleaf) await nanoleaf.get_info()
except Unavailable as err: except Unavailable as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
except InvalidToken as err: except InvalidToken as err:
@ -23,8 +24,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
DEVICE: nanoleaf, DEVICE: nanoleaf,
NAME: info["name"], NAME: nanoleaf.name,
SERIAL_NO: info["serialNo"], SERIAL_NO: nanoleaf.serial_no,
} }
hass.async_create_task( hass.async_create_task(

View file

@ -5,17 +5,17 @@ import logging
import os import os
from typing import Any, Final, cast from typing import Any, Final, cast
from pynanoleaf import InvalidToken, Nanoleaf, NotAuthorizingNewTokens, Unavailable from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
from .const import DOMAIN from .const import DOMAIN
from .util import pynanoleaf_get_info
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -53,9 +53,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=USER_SCHEMA, last_step=False step_id="user", data_schema=USER_SCHEMA, last_step=False
) )
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
self.nanoleaf = Nanoleaf(user_input[CONF_HOST]) self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), user_input[CONF_HOST]
)
try: try:
await self.hass.async_add_executor_job(self.nanoleaf.authorize) await self.nanoleaf.authorize()
except Unavailable: except Unavailable:
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
@ -63,7 +65,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors={"base": "cannot_connect"}, errors={"base": "cannot_connect"},
last_step=False, last_step=False,
) )
except NotAuthorizingNewTokens: except Unauthorized:
pass pass
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unknown error connecting to Nanoleaf") _LOGGER.exception("Unknown error connecting to Nanoleaf")
@ -81,7 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
config_entries.ConfigEntry, config_entries.ConfigEntry,
self.hass.config_entries.async_get_entry(self.context["entry_id"]), self.hass.config_entries.async_get_entry(self.context["entry_id"]),
) )
self.nanoleaf = Nanoleaf(data[CONF_HOST]) self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), data[CONF_HOST])
self.context["title_placeholders"] = {"name": self.reauth_entry.title} self.context["title_placeholders"] = {"name": self.reauth_entry.title}
return await self.async_step_link() return await self.async_step_link()
@ -106,7 +108,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
name = discovery_info["name"].replace(f".{discovery_info['type']}", "") name = discovery_info["name"].replace(f".{discovery_info['type']}", "")
await self.async_set_unique_id(name) await self.async_set_unique_id(name)
self._abort_if_unique_id_configured({CONF_HOST: host}) self._abort_if_unique_id_configured({CONF_HOST: host})
self.nanoleaf = Nanoleaf(host)
# Import from discovery integration # Import from discovery integration
self.device_id = discovery_info["properties"]["id"] self.device_id = discovery_info["properties"]["id"]
@ -116,16 +117,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
load_json, self.hass.config.path(CONFIG_FILE) load_json, self.hass.config.path(CONFIG_FILE)
), ),
) )
self.nanoleaf.token = self.discovery_conf.get(self.device_id, {}).get( auth_token: str | None = self.discovery_conf.get(self.device_id, {}).get(
"token", # >= 2021.4 "token", # >= 2021.4
self.discovery_conf.get(host, {}).get("token"), # < 2021.4 self.discovery_conf.get(host, {}).get("token"), # < 2021.4
) )
if self.nanoleaf.token is not None: if auth_token is not None:
self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), host, auth_token
)
_LOGGER.warning( _LOGGER.warning(
"Importing Nanoleaf %s from the discovery integration", name "Importing Nanoleaf %s from the discovery integration", name
) )
return await self.async_setup_finish(discovery_integration_import=True) return await self.async_setup_finish(discovery_integration_import=True)
self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), host)
self.context["title_placeholders"] = {"name": name} self.context["title_placeholders"] = {"name": name}
return await self.async_step_link() return await self.async_step_link()
@ -137,8 +141,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form(step_id="link") return self.async_show_form(step_id="link")
try: try:
await self.hass.async_add_executor_job(self.nanoleaf.authorize) await self.nanoleaf.authorize()
except NotAuthorizingNewTokens: except Unauthorized:
return self.async_show_form( return self.async_show_form(
step_id="link", errors={"base": "not_allowing_new_tokens"} step_id="link", errors={"base": "not_allowing_new_tokens"}
) )
@ -153,7 +157,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.reauth_entry, self.reauth_entry,
data={ data={
**self.reauth_entry.data, **self.reauth_entry.data,
CONF_TOKEN: self.nanoleaf.token, CONF_TOKEN: self.nanoleaf.auth_token,
}, },
) )
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
@ -167,8 +171,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.debug( _LOGGER.debug(
"Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST] "Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST]
) )
self.nanoleaf = Nanoleaf(config[CONF_HOST]) self.nanoleaf = Nanoleaf(
self.nanoleaf.token = config[CONF_TOKEN] async_get_clientsession(self.hass), config[CONF_HOST], config[CONF_TOKEN]
)
return await self.async_setup_finish() return await self.async_setup_finish()
async def async_setup_finish( async def async_setup_finish(
@ -176,9 +181,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) -> FlowResult: ) -> FlowResult:
"""Finish Nanoleaf config flow.""" """Finish Nanoleaf config flow."""
try: try:
info = await self.hass.async_add_executor_job( await self.nanoleaf.get_info()
pynanoleaf_get_info, self.nanoleaf
)
except Unavailable: except Unavailable:
return self.async_abort(reason="cannot_connect") return self.async_abort(reason="cannot_connect")
except InvalidToken: except InvalidToken:
@ -188,7 +191,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host "Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host
) )
return self.async_abort(reason="unknown") return self.async_abort(reason="unknown")
name = info["name"] name = self.nanoleaf.name
await self.async_set_unique_id(name) await self.async_set_unique_id(name)
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host}) self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
@ -215,6 +218,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
title=name, title=name,
data={ data={
CONF_HOST: self.nanoleaf.host, CONF_HOST: self.nanoleaf.host,
CONF_TOKEN: self.nanoleaf.token, CONF_TOKEN: self.nanoleaf.auth_token,
}, },
) )

View file

@ -3,7 +3,8 @@ from __future__ import annotations
import logging import logging
from pynanoleaf import Unavailable from aiohttp import ServerDisconnectedError
from aionanoleaf import Unavailable
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import ( from homeassistant.components.light import (
@ -153,7 +154,7 @@ class NanoleafLight(LightEntity):
@property @property
def is_on(self): def is_on(self):
"""Return true if light is on.""" """Return true if light is on."""
return self._state return self._light.is_on
@property @property
def hs_color(self): def hs_color(self):
@ -165,7 +166,7 @@ class NanoleafLight(LightEntity):
"""Flag supported features.""" """Flag supported features."""
return SUPPORT_NANOLEAF return SUPPORT_NANOLEAF
def turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Instruct the light to turn on.""" """Instruct the light to turn on."""
brightness = kwargs.get(ATTR_BRIGHTNESS) brightness = kwargs.get(ATTR_BRIGHTNESS)
hs_color = kwargs.get(ATTR_HS_COLOR) hs_color = kwargs.get(ATTR_HS_COLOR)
@ -175,57 +176,61 @@ class NanoleafLight(LightEntity):
if hs_color: if hs_color:
hue, saturation = hs_color hue, saturation = hs_color
self._light.hue = int(hue) await self._light.set_hue(int(hue))
self._light.saturation = int(saturation) await self._light.set_saturation(int(saturation))
if color_temp_mired: if color_temp_mired:
self._light.color_temperature = mired_to_kelvin(color_temp_mired) await self._light.set_color_temperature(mired_to_kelvin(color_temp_mired))
if transition: if transition:
if brightness: # tune to the required brightness in n seconds if brightness: # tune to the required brightness in n seconds
self._light.brightness_transition( await self._light.set_brightness(
int(brightness / 2.55), int(transition) int(brightness / 2.55), transition=int(kwargs[ATTR_TRANSITION])
) )
else: # If brightness is not specified, assume full brightness else: # If brightness is not specified, assume full brightness
self._light.brightness_transition(100, int(transition)) await self._light.set_brightness(
100, transition=int(kwargs[ATTR_TRANSITION])
)
else: # If no transition is occurring, turn on the light else: # If no transition is occurring, turn on the light
self._light.on = True await self._light.turn_on()
if brightness: if brightness:
self._light.brightness = int(brightness / 2.55) await self._light.set_brightness(int(brightness / 2.55))
if effect: if effect:
if effect not in self._effects_list: if effect not in self._effects_list:
raise ValueError( raise ValueError(
f"Attempting to apply effect not in the effect list: '{effect}'" f"Attempting to apply effect not in the effect list: '{effect}'"
) )
self._light.effect = effect await self._light.set_effect(effect)
def turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Instruct the light to turn off.""" """Instruct the light to turn off."""
transition = kwargs.get(ATTR_TRANSITION) transition = kwargs.get(ATTR_TRANSITION)
if transition: if transition:
self._light.brightness_transition(0, int(transition)) await self._light.set_brightness(0, transition=int(transition))
else: else:
self._light.on = False await self._light.turn_off()
def update(self): async def async_update(self) -> None:
"""Fetch new state data for this light.""" """Fetch new state data for this light."""
try: try:
self._available = self._light.available await self._light.get_info()
self._brightness = self._light.brightness except ServerDisconnectedError:
self._effects_list = self._light.effects # Retry the request once if the device disconnected
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color. await self._light.get_info()
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359). except Unavailable:
# Until fixed at the library level, we should ensure the effect exists before saving to light properties
self._effect = (
self._light.effect if self._light.effect in self._effects_list else None
)
if self._effect is None:
self._color_temp = self._light.color_temperature
self._hs_color = self._light.hue, self._light.saturation
else:
self._color_temp = None
self._hs_color = None
self._state = self._light.on
except Unavailable as err:
_LOGGER.error("Could not update status for %s (%s)", self.name, err)
self._available = False self._available = False
return
self._available = True
self._brightness = self._light.brightness
self._effects_list = self._light.effects_list
# Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color.
# This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359).
# Until fixed at the library level, we should ensure the effect exists before saving to light properties
self._effect = (
self._light.effect if self._light.effect in self._effects_list else None
)
if self._effect is None:
self._color_temp = self._light.color_temperature
self._hs_color = self._light.hue, self._light.saturation
else:
self._color_temp = None
self._hs_color = None
self._state = self._light.is_on

View file

@ -3,7 +3,7 @@
"name": "Nanoleaf", "name": "Nanoleaf",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/nanoleaf", "documentation": "https://www.home-assistant.io/integrations/nanoleaf",
"requirements": ["pynanoleaf==0.1.0"], "requirements": ["aionanoleaf==0.0.1"],
"zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."], "zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."],
"homekit" : { "homekit" : {
"models": [ "models": [

View file

@ -1,7 +0,0 @@
"""Nanoleaf integration util."""
from pynanoleaf.pynanoleaf import Nanoleaf
def pynanoleaf_get_info(nanoleaf_light: Nanoleaf) -> dict:
"""Get Nanoleaf light info."""
return nanoleaf_light.info

View file

@ -218,6 +218,9 @@ aiomodernforms==0.1.8
# homeassistant.components.yamaha_musiccast # homeassistant.components.yamaha_musiccast
aiomusiccast==0.9.2 aiomusiccast==0.9.2
# homeassistant.components.nanoleaf
aionanoleaf==0.0.1
# homeassistant.components.keyboard_remote # homeassistant.components.keyboard_remote
aionotify==0.2.0 aionotify==0.2.0
@ -1649,9 +1652,6 @@ pymyq==3.1.4
# homeassistant.components.mysensors # homeassistant.components.mysensors
pymysensors==0.21.0 pymysensors==0.21.0
# homeassistant.components.nanoleaf
pynanoleaf==0.1.0
# homeassistant.components.nello # homeassistant.components.nello
pynello==2.0.3 pynello==2.0.3

View file

@ -145,6 +145,9 @@ aiomodernforms==0.1.8
# homeassistant.components.yamaha_musiccast # homeassistant.components.yamaha_musiccast
aiomusiccast==0.9.2 aiomusiccast==0.9.2
# homeassistant.components.nanoleaf
aionanoleaf==0.0.1
# homeassistant.components.notion # homeassistant.components.notion
aionotion==3.0.2 aionotion==3.0.2
@ -968,9 +971,6 @@ pymyq==3.1.4
# homeassistant.components.mysensors # homeassistant.components.mysensors
pymysensors==0.21.0 pymysensors==0.21.0
# homeassistant.components.nanoleaf
pynanoleaf==0.1.0
# homeassistant.components.netgear # homeassistant.components.netgear
pynetgear==0.7.0 pynetgear==0.7.0

View file

@ -1,10 +1,9 @@
"""Test the Nanoleaf config flow.""" """Test the Nanoleaf config flow."""
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from pynanoleaf import InvalidToken, NotAuthorizingNewTokens, Unavailable from aionanoleaf import InvalidToken, NanoleafException, Unauthorized, Unavailable
from pynanoleaf.pynanoleaf import NanoleafError
import pytest import pytest
from homeassistant import config_entries from homeassistant import config_entries
@ -23,6 +22,21 @@ TEST_DEVICE_ID = "5E:2E:EA:XX:XX:XX"
TEST_OTHER_DEVICE_ID = "5E:2E:EA:YY:YY:YY" TEST_OTHER_DEVICE_ID = "5E:2E:EA:YY:YY:YY"
def _mock_nanoleaf(
host: str = TEST_HOST,
auth_token: str = TEST_TOKEN,
authorize_error: Exception | None = None,
get_info_error: Exception | None = None,
):
nanoleaf = MagicMock()
nanoleaf.name = TEST_NAME
nanoleaf.host = host
nanoleaf.auth_token = auth_token
nanoleaf.authorize = AsyncMock(side_effect=authorize_error)
nanoleaf.get_info = AsyncMock(side_effect=get_info_error)
return nanoleaf
async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None: async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None:
"""Test we handle Unavailable in user and link step.""" """Test we handle Unavailable in user and link step."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -30,7 +44,7 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
) )
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
side_effect=Unavailable("message"), side_effect=Unavailable,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -58,7 +72,7 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
side_effect=Unavailable("message"), side_effect=Unavailable,
): ):
result3 = await hass.config_entries.flow.async_configure( result3 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -71,8 +85,8 @@ async def test_user_unavailable_user_step_link_step(hass: HomeAssistant) -> None
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, reason", "error, reason",
[ [
(Unavailable("message"), "cannot_connect"), (Unavailable, "cannot_connect"),
(InvalidToken("message"), "invalid_token"), (InvalidToken, "invalid_token"),
(Exception, "unknown"), (Exception, "unknown"),
], ],
) )
@ -85,7 +99,6 @@ async def test_user_error_setup_finish(
) )
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
return_value=None,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -98,9 +111,8 @@ async def test_user_error_setup_finish(
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
return_value=None,
), patch( ), patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
side_effect=error, side_effect=error,
): ):
result3 = await hass.config_entries.flow.async_configure( result3 = await hass.config_entries.flow.async_configure(
@ -117,19 +129,10 @@ async def test_user_not_authorizing_new_tokens_user_step_link_step(
"""Test we handle NotAuthorizingNewTokens in user step and link step.""" """Test we handle NotAuthorizingNewTokens in user step and link step."""
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
return_value=_mock_nanoleaf(authorize_error=Unauthorized()),
) as mock_nanoleaf, patch( ) as mock_nanoleaf, patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
return_value={"name": TEST_NAME},
), patch(
"homeassistant.components.nanoleaf.async_setup_entry", return_value=True "homeassistant.components.nanoleaf.async_setup_entry", return_value=True
) as mock_setup_entry: ) as mock_setup_entry:
nanoleaf = mock_nanoleaf.return_value
nanoleaf.authorize.side_effect = NotAuthorizingNewTokens(
"Not authorizing new tokens"
)
nanoleaf.host = TEST_HOST
nanoleaf.token = TEST_TOKEN
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
@ -160,8 +163,7 @@ async def test_user_not_authorizing_new_tokens_user_step_link_step(
assert result4["errors"] == {"base": "not_allowing_new_tokens"} assert result4["errors"] == {"base": "not_allowing_new_tokens"}
assert result4["step_id"] == "link" assert result4["step_id"] == "link"
nanoleaf.authorize.side_effect = None mock_nanoleaf.return_value.authorize.side_effect = None
nanoleaf.authorize.return_value = None
result5 = await hass.config_entries.flow.async_configure( result5 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -183,8 +185,8 @@ async def test_user_exception_user_step(hass: HomeAssistant) -> None:
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
side_effect=Exception, return_value=_mock_nanoleaf(authorize_error=Exception()),
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -198,36 +200,29 @@ async def test_user_exception_user_step(hass: HomeAssistant) -> None:
assert not result2["last_step"] assert not result2["last_step"]
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
return_value=None, return_value=_mock_nanoleaf(),
): ) as mock_nanoleaf:
result3 = await hass.config_entries.flow.async_configure( result3 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
CONF_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
}, },
) )
assert result3["step_id"] == "link" assert result3["step_id"] == "link"
mock_nanoleaf.return_value.authorize.side_effect = Exception()
with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
side_effect=Exception,
):
result4 = await hass.config_entries.flow.async_configure( result4 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{}, {},
) )
assert result4["type"] == "form" assert result4["type"] == "form"
assert result4["step_id"] == "link" assert result4["step_id"] == "link"
assert result4["errors"] == {"base": "unknown"} assert result4["errors"] == {"base": "unknown"}
with patch( mock_nanoleaf.return_value.authorize.side_effect = None
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", mock_nanoleaf.return_value.get_info.side_effect = Exception()
return_value=None,
), patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info",
side_effect=Exception,
):
result5 = await hass.config_entries.flow.async_configure( result5 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{}, {},
@ -249,8 +244,7 @@ async def test_discovery_link_unavailable(
) -> None: ) -> None:
"""Test discovery and abort if device is unavailable.""" """Test discovery and abort if device is unavailable."""
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
return_value={"name": TEST_NAME},
), patch( ), patch(
"homeassistant.components.nanoleaf.config_flow.load_json", "homeassistant.components.nanoleaf.config_flow.load_json",
return_value={}, return_value={},
@ -278,7 +272,7 @@ async def test_discovery_link_unavailable(
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize",
side_effect=Unavailable("message"), side_effect=Unavailable,
): ):
result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == "abort" assert result["type"] == "abort"
@ -287,10 +281,6 @@ async def test_discovery_link_unavailable(
async def test_reauth(hass: HomeAssistant) -> None: async def test_reauth(hass: HomeAssistant) -> None:
"""Test Nanoleaf reauth flow.""" """Test Nanoleaf reauth flow."""
nanoleaf = MagicMock()
nanoleaf.host = TEST_HOST
nanoleaf.token = TEST_TOKEN
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=TEST_NAME, unique_id=TEST_NAME,
@ -300,7 +290,7 @@ async def test_reauth(hass: HomeAssistant) -> None:
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.Nanoleaf", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
return_value=nanoleaf, return_value=_mock_nanoleaf(),
), patch( ), patch(
"homeassistant.components.nanoleaf.async_setup_entry", "homeassistant.components.nanoleaf.async_setup_entry",
return_value=True, return_value=True,
@ -331,8 +321,8 @@ async def test_reauth(hass: HomeAssistant) -> None:
async def test_import_config(hass: HomeAssistant) -> None: async def test_import_config(hass: HomeAssistant) -> None:
"""Test configuration import.""" """Test configuration import."""
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
return_value={"name": TEST_NAME}, return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN),
), patch( ), patch(
"homeassistant.components.nanoleaf.async_setup_entry", "homeassistant.components.nanoleaf.async_setup_entry",
return_value=True, return_value=True,
@ -355,17 +345,17 @@ async def test_import_config(hass: HomeAssistant) -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, reason", "error, reason",
[ [
(Unavailable("message"), "cannot_connect"), (Unavailable, "cannot_connect"),
(InvalidToken("message"), "invalid_token"), (InvalidToken, "invalid_token"),
(Exception, "unknown"), (Exception, "unknown"),
], ],
) )
async def test_import_config_error( async def test_import_config_error(
hass: HomeAssistant, error: NanoleafError, reason: str hass: HomeAssistant, error: NanoleafException, reason: str
) -> None: ) -> None:
"""Test configuration import with errors in setup_finish.""" """Test configuration import with errors in setup_finish."""
with patch( with patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info", "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info",
side_effect=error, side_effect=error,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -432,8 +422,8 @@ async def test_import_discovery_integration(
"homeassistant.components.nanoleaf.config_flow.load_json", "homeassistant.components.nanoleaf.config_flow.load_json",
return_value=dict(nanoleaf_conf_file), return_value=dict(nanoleaf_conf_file),
), patch( ), patch(
"homeassistant.components.nanoleaf.config_flow.pynanoleaf_get_info", "homeassistant.components.nanoleaf.config_flow.Nanoleaf",
return_value={"name": TEST_NAME}, return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN),
), patch( ), patch(
"homeassistant.components.nanoleaf.config_flow.save_json", "homeassistant.components.nanoleaf.config_flow.save_json",
return_value=None, return_value=None,