mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add config flow + async support for SmartHab integration (#34387)
* Setup barebones SmartHab config flow
* Setup authentication flow
* Make setup async, add config flow receivers
* Add French translation
* Fix async issues
* Address review comments (thanks bdraco!)
* Fix unloading entries
* Migrate translations dir according to warning
* Create list of components
* Fix pylint false positive
* Fix bad copy-pastes 🤭
* Add async support to SmartHab component
* Address review comments (bdraco)
* Fix pylint
* Improve exception handling (bdraco)
* Apply suggestions from code review (bdraco)
Co-authored-by: J. Nick Koston <nick@koston.org>
* Don't log exceptions manually, fix error
* Reduce repeated lines in async_step_user (bdraco)
* Remove useless else (pylint)
* Remove broad exception handler
* Create strings.json + remove fr i18n
* Write tests for smarthab config flow
* Test import flow
* Fix import test
* Update homeassistant/components/smarthab/config_flow.py
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
10893f6246
commit
3062312649
@ -732,7 +732,9 @@ omit =
|
|||||||
homeassistant/components/smappee/sensor.py
|
homeassistant/components/smappee/sensor.py
|
||||||
homeassistant/components/smappee/switch.py
|
homeassistant/components/smappee/switch.py
|
||||||
homeassistant/components/smarty/*
|
homeassistant/components/smarty/*
|
||||||
homeassistant/components/smarthab/*
|
homeassistant/components/smarthab/__init__.py
|
||||||
|
homeassistant/components/smarthab/cover.py
|
||||||
|
homeassistant/components/smarthab/light.py
|
||||||
homeassistant/components/sms/*
|
homeassistant/components/sms/*
|
||||||
homeassistant/components/smtp/notify.py
|
homeassistant/components/smtp/notify.py
|
||||||
homeassistant/components/snapcast/*
|
homeassistant/components/snapcast/*
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
"""Support for SmartHab device integration."""
|
"""Support for SmartHab device integration."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pysmarthab
|
import pysmarthab
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.discovery import load_platform
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
DOMAIN = "smarthab"
|
DOMAIN = "smarthab"
|
||||||
DATA_HUB = "hub"
|
DATA_HUB = "hub"
|
||||||
|
COMPONENTS = ["light", "cover"]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -26,34 +30,61 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config) -> bool:
|
async def async_setup(hass, config) -> bool:
|
||||||
"""Set up the SmartHab platform."""
|
"""Set up the SmartHab platform."""
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
sh_conf = config.get(DOMAIN)
|
sh_conf = config.get(DOMAIN)
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=sh_conf,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
|
"""Set up config entry for SmartHab integration."""
|
||||||
|
|
||||||
# Assign configuration variables
|
# Assign configuration variables
|
||||||
username = sh_conf[CONF_EMAIL]
|
username = entry.data[CONF_EMAIL]
|
||||||
password = sh_conf[CONF_PASSWORD]
|
password = entry.data[CONF_PASSWORD]
|
||||||
|
|
||||||
# Setup connection with SmartHab API
|
# Setup connection with SmartHab API
|
||||||
hub = pysmarthab.SmartHab()
|
hub = pysmarthab.SmartHab()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hub.login(username, password)
|
await hub.async_login(username, password)
|
||||||
except pysmarthab.RequestFailedException as ex:
|
except pysmarthab.RequestFailedException:
|
||||||
_LOGGER.error("Error while trying to reach SmartHab API.")
|
_LOGGER.exception("Error while trying to reach SmartHab API")
|
||||||
_LOGGER.debug(ex, exc_info=True)
|
raise ConfigEntryNotReady
|
||||||
return False
|
|
||||||
|
|
||||||
# Verify that passed in configuration works
|
|
||||||
if not hub.is_logged_in():
|
|
||||||
_LOGGER.error("Could not authenticate with SmartHab API")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Pass hub object to child platforms
|
# Pass hub object to child platforms
|
||||||
hass.data[DOMAIN] = {DATA_HUB: hub}
|
hass.data[DOMAIN][entry.entry_id] = {DATA_HUB: hub}
|
||||||
|
|
||||||
load_platform(hass, "light", DOMAIN, None, config)
|
for component in COMPONENTS:
|
||||||
load_platform(hass, "cover", DOMAIN, None, config)
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||||
|
"""Unload config entry from SmartHab integration."""
|
||||||
|
|
||||||
|
result = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in COMPONENTS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
77
homeassistant/components/smarthab/config_flow.py
Normal file
77
homeassistant/components/smarthab/config_flow.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""SmartHab configuration flow."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pysmarthab
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
|
|
||||||
|
# pylint: disable=unused-import
|
||||||
|
from . import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SmartHabConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""SmartHab config flow."""
|
||||||
|
|
||||||
|
def _show_setup_form(self, user_input=None, errors=None):
|
||||||
|
"""Show the setup form to the user."""
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
user_input = {}
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_EMAIL, default=user_input.get(CONF_EMAIL, "")
|
||||||
|
): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initiated by the user."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return self._show_setup_form(user_input, None)
|
||||||
|
|
||||||
|
username = user_input[CONF_EMAIL]
|
||||||
|
password = user_input[CONF_PASSWORD]
|
||||||
|
|
||||||
|
# Check if already configured
|
||||||
|
if self.unique_id is None:
|
||||||
|
await self.async_set_unique_id(username)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
# Setup connection with SmartHab API
|
||||||
|
hub = pysmarthab.SmartHab()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await hub.async_login(username, password)
|
||||||
|
|
||||||
|
# Verify that passed in configuration works
|
||||||
|
if hub.is_logged_in():
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=username, data={CONF_EMAIL: username, CONF_PASSWORD: password}
|
||||||
|
)
|
||||||
|
|
||||||
|
errors["base"] = "wrong_login"
|
||||||
|
except pysmarthab.RequestFailedException:
|
||||||
|
_LOGGER.exception("Error while trying to reach SmartHab API")
|
||||||
|
errors["base"] = "service"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected error during login")
|
||||||
|
errors["base"] = "unknown_error"
|
||||||
|
|
||||||
|
return self._show_setup_form(user_input, errors)
|
||||||
|
|
||||||
|
async def async_step_import(self, user_input):
|
||||||
|
"""Handle import from legacy config."""
|
||||||
|
return await self.async_step_user(user_input)
|
@ -7,6 +7,7 @@ from requests.exceptions import Timeout
|
|||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
|
DEVICE_CLASS_WINDOW,
|
||||||
SUPPORT_CLOSE,
|
SUPPORT_CLOSE,
|
||||||
SUPPORT_OPEN,
|
SUPPORT_OPEN,
|
||||||
SUPPORT_SET_POSITION,
|
SUPPORT_SET_POSITION,
|
||||||
@ -20,21 +21,17 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the SmartHab roller shutters platform."""
|
"""Set up SmartHab covers from a config entry."""
|
||||||
|
hub = hass.data[DOMAIN][config_entry.entry_id][DATA_HUB]
|
||||||
hub = hass.data[DOMAIN][DATA_HUB]
|
|
||||||
devices = hub.get_device_list()
|
|
||||||
|
|
||||||
_LOGGER.debug("Found a total of %s devices", str(len(devices)))
|
|
||||||
|
|
||||||
entities = (
|
entities = (
|
||||||
SmartHabCover(cover)
|
SmartHabCover(cover)
|
||||||
for cover in devices
|
for cover in await hub.async_get_device_list()
|
||||||
if isinstance(cover, pysmarthab.Shutter)
|
if isinstance(cover, pysmarthab.Shutter)
|
||||||
)
|
)
|
||||||
|
|
||||||
add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class SmartHabCover(CoverEntity):
|
class SmartHabCover(CoverEntity):
|
||||||
@ -51,7 +48,7 @@ class SmartHabCover(CoverEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the display name of this light."""
|
"""Return the display name of this cover."""
|
||||||
return self._cover.label
|
return self._cover.label
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -65,12 +62,7 @@ class SmartHabCover(CoverEntity):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE
|
return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
|
||||||
|
|
||||||
if self.current_cover_position is not None:
|
|
||||||
supported_features |= SUPPORT_SET_POSITION
|
|
||||||
|
|
||||||
return supported_features
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
@ -80,24 +72,24 @@ class SmartHabCover(CoverEntity):
|
|||||||
@property
|
@property
|
||||||
def device_class(self) -> str:
|
def device_class(self) -> str:
|
||||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||||
return "window"
|
return DEVICE_CLASS_WINDOW
|
||||||
|
|
||||||
def open_cover(self, **kwargs):
|
async def async_open_cover(self, **kwargs):
|
||||||
"""Open the cover."""
|
"""Open the cover."""
|
||||||
self._cover.open()
|
await self._cover.async_open()
|
||||||
|
|
||||||
def close_cover(self, **kwargs):
|
async def async_close_cover(self, **kwargs):
|
||||||
"""Close cover."""
|
"""Close cover."""
|
||||||
self._cover.close()
|
await self._cover.async_close()
|
||||||
|
|
||||||
def set_cover_position(self, **kwargs):
|
async def async_set_cover_position(self, **kwargs):
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
self._cover.state = kwargs[ATTR_POSITION]
|
await self._cover.async_set_state(kwargs[ATTR_POSITION])
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Fetch new state data for this cover."""
|
"""Fetch new state data for this cover."""
|
||||||
try:
|
try:
|
||||||
self._cover.update()
|
await self._cover.async_update()
|
||||||
except Timeout:
|
except Timeout:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Reached timeout while updating cover %s from API", self.entity_id
|
"Reached timeout while updating cover %s from API", self.entity_id
|
||||||
|
@ -14,19 +14,17 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the SmartHab lights platform."""
|
"""Set up SmartHab lights from a config entry."""
|
||||||
|
hub = hass.data[DOMAIN][config_entry.entry_id][DATA_HUB]
|
||||||
hub = hass.data[DOMAIN][DATA_HUB]
|
|
||||||
devices = hub.get_device_list()
|
|
||||||
|
|
||||||
_LOGGER.debug("Found a total of %s devices", str(len(devices)))
|
|
||||||
|
|
||||||
entities = (
|
entities = (
|
||||||
SmartHabLight(light) for light in devices if isinstance(light, pysmarthab.Light)
|
SmartHabLight(light)
|
||||||
|
for light in await hub.async_get_device_list()
|
||||||
|
if isinstance(light, pysmarthab.Light)
|
||||||
)
|
)
|
||||||
|
|
||||||
add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class SmartHabLight(LightEntity):
|
class SmartHabLight(LightEntity):
|
||||||
@ -51,18 +49,18 @@ class SmartHabLight(LightEntity):
|
|||||||
"""Return true if light is on."""
|
"""Return true if light is on."""
|
||||||
return self._light.state
|
return self._light.state
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Instruct the light to turn on."""
|
"""Instruct the light to turn on."""
|
||||||
self._light.turn_on()
|
await self._light.async_turn_on()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
"""Instruct the light to turn off."""
|
"""Instruct the light to turn off."""
|
||||||
self._light.turn_off()
|
await self._light.async_turn_off()
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Fetch new state data for this light."""
|
"""Fetch new state data for this light."""
|
||||||
try:
|
try:
|
||||||
self._light.update()
|
await self._light.async_update()
|
||||||
except Timeout:
|
except Timeout:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Reached timeout while updating light %s from API", self.entity_id
|
"Reached timeout while updating light %s from API", self.entity_id
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"domain": "smarthab",
|
"domain": "smarthab",
|
||||||
"name": "SmartHab",
|
"name": "SmartHab",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/smarthab",
|
"documentation": "https://www.home-assistant.io/integrations/smarthab",
|
||||||
"requirements": ["smarthab==0.20"],
|
"config_flow": true,
|
||||||
|
"requirements": ["smarthab==0.21"],
|
||||||
"codeowners": ["@outadoc"]
|
"codeowners": ["@outadoc"]
|
||||||
}
|
}
|
||||||
|
19
homeassistant/components/smarthab/strings.json
Normal file
19
homeassistant/components/smarthab/strings.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"service": "Error while trying to reach SmartHab. Service might be down. Check your connection.",
|
||||||
|
"wrong_login": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown_error": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"email": "[%key:common::config_flow::data::email%]"
|
||||||
|
},
|
||||||
|
"description": "For technical reasons, be sure to use a secondary account specific to your Home Assistant setup. You can create one from the SmartHab application.",
|
||||||
|
"title": "Setup SmartHab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
homeassistant/components/smarthab/translations/en.json
Normal file
19
homeassistant/components/smarthab/translations/en.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"service": "Error while trying to reach SmartHab. Service might be down. Check your connection.",
|
||||||
|
"wrong_login": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown_error": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"email": "[%key:common::config_flow::data::email%]"
|
||||||
|
},
|
||||||
|
"description": "For technical reasons, be sure to use a secondary account specific to your Home Assistant setup. You can create one from the SmartHab application.",
|
||||||
|
"title": "Setup SmartHab"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -140,6 +140,7 @@ FLOWS = [
|
|||||||
"shopping_list",
|
"shopping_list",
|
||||||
"simplisafe",
|
"simplisafe",
|
||||||
"smappee",
|
"smappee",
|
||||||
|
"smarthab",
|
||||||
"smartthings",
|
"smartthings",
|
||||||
"smhi",
|
"smhi",
|
||||||
"sms",
|
"sms",
|
||||||
|
@ -1975,7 +1975,7 @@ sleepyq==0.7
|
|||||||
slixmpp==1.5.1
|
slixmpp==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.smarthab
|
# homeassistant.components.smarthab
|
||||||
smarthab==0.20
|
smarthab==0.21
|
||||||
|
|
||||||
# homeassistant.components.bh1750
|
# homeassistant.components.bh1750
|
||||||
# homeassistant.components.bme280
|
# homeassistant.components.bme280
|
||||||
|
@ -852,6 +852,9 @@ simplisafe-python==9.2.0
|
|||||||
# homeassistant.components.sleepiq
|
# homeassistant.components.sleepiq
|
||||||
sleepyq==0.7
|
sleepyq==0.7
|
||||||
|
|
||||||
|
# homeassistant.components.smarthab
|
||||||
|
smarthab==0.21
|
||||||
|
|
||||||
# homeassistant.components.smhi
|
# homeassistant.components.smhi
|
||||||
smhi-pkg==1.0.13
|
smhi-pkg==1.0.13
|
||||||
|
|
||||||
|
1
tests/components/smarthab/__init__.py
Normal file
1
tests/components/smarthab/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the SmartHab integration."""
|
126
tests/components/smarthab/test_config_flow.py
Normal file
126
tests/components/smarthab/test_config_flow.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
"""Test the SmartHab config flow."""
|
||||||
|
import pysmarthab
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.smarthab import DOMAIN
|
||||||
|
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass):
|
||||||
|
"""Test we get the form."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch("pysmarthab.SmartHab.async_login"), patch(
|
||||||
|
"pysmarthab.SmartHab.is_logged_in", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.smarthab.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.smarthab.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_EMAIL: "mock@example.com", CONF_PASSWORD: "test-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "mock@example.com"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_EMAIL: "mock@example.com",
|
||||||
|
CONF_PASSWORD: "test-password",
|
||||||
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass):
|
||||||
|
"""Test we handle invalid auth."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("pysmarthab.SmartHab.async_login"), patch(
|
||||||
|
"pysmarthab.SmartHab.is_logged_in", return_value=False
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_EMAIL: "mock@example.com", CONF_PASSWORD: "test-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "wrong_login"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_service_error(hass):
|
||||||
|
"""Test we handle service errors."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pysmarthab.SmartHab.async_login",
|
||||||
|
side_effect=pysmarthab.RequestFailedException(42),
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_EMAIL: "mock@example.com", CONF_PASSWORD: "test-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "service"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_unknown_error(hass):
|
||||||
|
"""Test we handle unknown errors."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pysmarthab.SmartHab.async_login", side_effect=Exception,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_EMAIL: "mock@example.com", CONF_PASSWORD: "test-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "unknown_error"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import(hass):
|
||||||
|
"""Test import."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
|
imported_conf = {
|
||||||
|
CONF_EMAIL: "mock@example.com",
|
||||||
|
CONF_PASSWORD: "test-password",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("pysmarthab.SmartHab.async_login"), patch(
|
||||||
|
"pysmarthab.SmartHab.is_logged_in", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.smarthab.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.smarthab.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=imported_conf
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == "mock@example.com"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_EMAIL: "mock@example.com",
|
||||||
|
CONF_PASSWORD: "test-password",
|
||||||
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user