mirror of
https://github.com/home-assistant/core.git
synced 2025-04-30 20:27:57 +00:00
deCONZ migrate setup fully to config entry (#13679)
* Initial working config entry with discovery * No need for else * Make sure that imported config doesnt exist as a config entry * Improve checks to make sure there is only instance of deconz * Fix tests and add new tests * Follow upstream changes Fix case when discovery started ongoing config entry and user completes setup from other path it was possible to complete discovered config entry as well * Add test to make sure link doesn't bypass any check for only allowing one config entry * Dont use len to determine an empty sequence * Cleanup * Allways get bridgeid to use as unique identifier for bridge
This commit is contained in:
parent
7d43ad6a37
commit
c5cb28d41f
@ -18,6 +18,7 @@
|
|||||||
"no_key": "Couldn't get an API key"
|
"no_key": "Couldn't get an API key"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "Bridge is already configured",
|
||||||
"no_bridges": "No deCONZ bridges discovered",
|
"no_bridges": "No deCONZ bridges discovered",
|
||||||
"one_instance_only": "Component only supports one deCONZ instance"
|
"one_instance_only": "Component only supports one deCONZ instance"
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,21 @@ Support for deCONZ devices.
|
|||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/deconz/
|
https://home-assistant.io/components/deconz/
|
||||||
"""
|
"""
|
||||||
import logging
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
|
||||||
from homeassistant.components.discovery import SERVICE_DECONZ
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import (
|
||||||
from homeassistant.helpers import discovery, aiohttp_client
|
aiohttp_client, discovery, config_validation as cv)
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.util.json import load_json
|
||||||
from homeassistant.util.json import load_json, save_json
|
|
||||||
|
# Loading the config flow file will register the flow
|
||||||
|
from .config_flow import configured_hosts
|
||||||
|
from .const import CONFIG_FILE, DATA_DECONZ_ID, DOMAIN, _LOGGER
|
||||||
|
|
||||||
REQUIREMENTS = ['pydeconz==36']
|
REQUIREMENTS = ['pydeconz==36']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DOMAIN = 'deconz'
|
|
||||||
DATA_DECONZ_ID = 'deconz_entities'
|
|
||||||
|
|
||||||
CONFIG_FILE = 'deconz.conf'
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
vol.Optional(CONF_API_KEY): cv.string,
|
vol.Optional(CONF_API_KEY): cv.string,
|
||||||
@ -46,44 +38,36 @@ SERVICE_SCHEMA = vol.Schema({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
CONFIG_INSTRUCTIONS = """
|
|
||||||
Unlock your deCONZ gateway to register with Home Assistant.
|
|
||||||
|
|
||||||
1. [Go to deCONZ system settings](http://{}:{}/edit_system.html)
|
|
||||||
2. Press "Unlock Gateway" button
|
|
||||||
|
|
||||||
[deCONZ platform documentation](https://home-assistant.io/components/deconz/)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up services and configuration for deCONZ component."""
|
"""Load configuration for deCONZ component.
|
||||||
result = False
|
|
||||||
|
Discovery has loaded the component if DOMAIN is not present in config.
|
||||||
|
"""
|
||||||
|
if DOMAIN in config:
|
||||||
|
deconz_config = None
|
||||||
config_file = await hass.async_add_job(
|
config_file = await hass.async_add_job(
|
||||||
load_json, hass.config.path(CONFIG_FILE))
|
load_json, hass.config.path(CONFIG_FILE))
|
||||||
|
|
||||||
async def async_deconz_discovered(service, discovery_info):
|
|
||||||
"""Call when deCONZ gateway has been found."""
|
|
||||||
deconz_config = {}
|
|
||||||
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
|
|
||||||
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
|
|
||||||
await async_request_configuration(hass, config, deconz_config)
|
|
||||||
|
|
||||||
if config_file:
|
if config_file:
|
||||||
result = await async_setup_deconz(hass, config, config_file)
|
deconz_config = config_file
|
||||||
|
elif CONF_HOST in config[DOMAIN]:
|
||||||
if not result and DOMAIN in config and CONF_HOST in config[DOMAIN]:
|
|
||||||
deconz_config = config[DOMAIN]
|
deconz_config = config[DOMAIN]
|
||||||
if CONF_API_KEY in deconz_config:
|
if deconz_config and not configured_hosts(hass):
|
||||||
result = await async_setup_deconz(hass, config, deconz_config)
|
hass.async_add_job(hass.config_entries.flow.async_init(
|
||||||
else:
|
DOMAIN, source='import', data=deconz_config
|
||||||
await async_request_configuration(hass, config, deconz_config)
|
))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not result:
|
|
||||||
discovery.async_listen(hass, SERVICE_DECONZ, async_deconz_discovered)
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Set up a deCONZ bridge for a config entry."""
|
||||||
|
if DOMAIN in hass.data:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Config entry failed since one deCONZ instance already exists")
|
||||||
|
return False
|
||||||
|
result = await async_setup_deconz(hass, None, entry.data)
|
||||||
|
if result:
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_deconz(hass, config, deconz_config):
|
async def async_setup_deconz(hass, config, deconz_config):
|
||||||
@ -94,8 +78,8 @@ async def async_setup_deconz(hass, config, deconz_config):
|
|||||||
"""
|
"""
|
||||||
_LOGGER.debug("deCONZ config %s", deconz_config)
|
_LOGGER.debug("deCONZ config %s", deconz_config)
|
||||||
from pydeconz import DeconzSession
|
from pydeconz import DeconzSession
|
||||||
websession = async_get_clientsession(hass)
|
session = aiohttp_client.async_get_clientsession(hass)
|
||||||
deconz = DeconzSession(hass.loop, websession, **deconz_config)
|
deconz = DeconzSession(hass.loop, session, **deconz_config)
|
||||||
result = await deconz.async_load_parameters()
|
result = await deconz.async_load_parameters()
|
||||||
if result is False:
|
if result is False:
|
||||||
_LOGGER.error("Failed to communicate with deCONZ")
|
_LOGGER.error("Failed to communicate with deCONZ")
|
||||||
@ -152,121 +136,3 @@ async def async_setup_deconz(hass, config, deconz_config):
|
|||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_request_configuration(hass, config, deconz_config):
|
|
||||||
"""Request configuration steps from the user."""
|
|
||||||
configurator = hass.components.configurator
|
|
||||||
|
|
||||||
async def async_configuration_callback(data):
|
|
||||||
"""Set up actions to do when our configuration callback is called."""
|
|
||||||
from pydeconz.utils import async_get_api_key
|
|
||||||
websession = async_get_clientsession(hass)
|
|
||||||
api_key = await async_get_api_key(websession, **deconz_config)
|
|
||||||
if api_key:
|
|
||||||
deconz_config[CONF_API_KEY] = api_key
|
|
||||||
result = await async_setup_deconz(hass, config, deconz_config)
|
|
||||||
if result:
|
|
||||||
await hass.async_add_job(
|
|
||||||
save_json, hass.config.path(CONFIG_FILE), deconz_config)
|
|
||||||
configurator.async_request_done(request_id)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
configurator.async_notify_errors(
|
|
||||||
request_id, "Couldn't load configuration.")
|
|
||||||
else:
|
|
||||||
configurator.async_notify_errors(
|
|
||||||
request_id, "Couldn't get an API key.")
|
|
||||||
return
|
|
||||||
|
|
||||||
instructions = CONFIG_INSTRUCTIONS.format(
|
|
||||||
deconz_config[CONF_HOST], deconz_config[CONF_PORT])
|
|
||||||
|
|
||||||
request_id = configurator.async_request_config(
|
|
||||||
"deCONZ", async_configuration_callback,
|
|
||||||
description=instructions,
|
|
||||||
entity_picture="/static/images/logo_deconz.jpeg",
|
|
||||||
submit_caption="I have unlocked the gateway",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@config_entries.HANDLERS.register(DOMAIN)
|
|
||||||
class DeconzFlowHandler(data_entry_flow.FlowHandler):
|
|
||||||
"""Handle a deCONZ config flow."""
|
|
||||||
|
|
||||||
VERSION = 1
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize the deCONZ flow."""
|
|
||||||
self.bridges = []
|
|
||||||
self.deconz_config = {}
|
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
|
||||||
"""Handle a flow start."""
|
|
||||||
from pydeconz.utils import async_discovery
|
|
||||||
|
|
||||||
if DOMAIN in self.hass.data:
|
|
||||||
return self.async_abort(
|
|
||||||
reason='one_instance_only'
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_input is not None:
|
|
||||||
for bridge in self.bridges:
|
|
||||||
if bridge[CONF_HOST] == user_input[CONF_HOST]:
|
|
||||||
self.deconz_config = bridge
|
|
||||||
return await self.async_step_link()
|
|
||||||
|
|
||||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
|
||||||
self.bridges = await async_discovery(session)
|
|
||||||
|
|
||||||
if len(self.bridges) == 1:
|
|
||||||
self.deconz_config = self.bridges[0]
|
|
||||||
return await self.async_step_link()
|
|
||||||
elif len(self.bridges) > 1:
|
|
||||||
hosts = []
|
|
||||||
for bridge in self.bridges:
|
|
||||||
hosts.append(bridge[CONF_HOST])
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id='init',
|
|
||||||
data_schema=vol.Schema({
|
|
||||||
vol.Required(CONF_HOST): vol.In(hosts)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.async_abort(
|
|
||||||
reason='no_bridges'
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_step_link(self, user_input=None):
|
|
||||||
"""Attempt to link with the deCONZ bridge."""
|
|
||||||
from pydeconz.utils import async_get_api_key
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
if user_input is not None:
|
|
||||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
|
||||||
api_key = await async_get_api_key(session, **self.deconz_config)
|
|
||||||
if api_key:
|
|
||||||
self.deconz_config[CONF_API_KEY] = api_key
|
|
||||||
return self.async_create_entry(
|
|
||||||
title='deCONZ',
|
|
||||||
data=self.deconz_config
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
errors['base'] = 'no_key'
|
|
||||||
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id='link',
|
|
||||||
errors=errors,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry):
|
|
||||||
"""Set up a bridge for a config entry."""
|
|
||||||
if DOMAIN in hass.data:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Config entry failed since one deCONZ instance already exists")
|
|
||||||
return False
|
|
||||||
result = await async_setup_deconz(hass, None, entry.data)
|
|
||||||
if result:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
139
homeassistant/components/deconz/config_flow.py
Normal file
139
homeassistant/components/deconz/config_flow.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
"""Config flow to configure deCONZ component."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
from homeassistant.util.json import load_json
|
||||||
|
|
||||||
|
from .const import CONFIG_FILE, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def configured_hosts(hass):
|
||||||
|
"""Return a set of the configured hosts."""
|
||||||
|
return set(entry.data['host'] for entry
|
||||||
|
in hass.config_entries.async_entries(DOMAIN))
|
||||||
|
|
||||||
|
|
||||||
|
@config_entries.HANDLERS.register(DOMAIN)
|
||||||
|
class DeconzFlowHandler(data_entry_flow.FlowHandler):
|
||||||
|
"""Handle a deCONZ config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the deCONZ config flow."""
|
||||||
|
self.bridges = []
|
||||||
|
self.deconz_config = {}
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Handle a deCONZ config flow start."""
|
||||||
|
from pydeconz.utils import async_discovery
|
||||||
|
|
||||||
|
if configured_hosts(self.hass):
|
||||||
|
return self.async_abort(reason='one_instance_only')
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
for bridge in self.bridges:
|
||||||
|
if bridge[CONF_HOST] == user_input[CONF_HOST]:
|
||||||
|
self.deconz_config = bridge
|
||||||
|
return await self.async_step_link()
|
||||||
|
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
self.bridges = await async_discovery(session)
|
||||||
|
|
||||||
|
if len(self.bridges) == 1:
|
||||||
|
self.deconz_config = self.bridges[0]
|
||||||
|
return await self.async_step_link()
|
||||||
|
elif len(self.bridges) > 1:
|
||||||
|
hosts = []
|
||||||
|
for bridge in self.bridges:
|
||||||
|
hosts.append(bridge[CONF_HOST])
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='init',
|
||||||
|
data_schema=vol.Schema({
|
||||||
|
vol.Required(CONF_HOST): vol.In(hosts)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_abort(
|
||||||
|
reason='no_bridges'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_link(self, user_input=None):
|
||||||
|
"""Attempt to link with the deCONZ bridge."""
|
||||||
|
from pydeconz.utils import async_get_api_key, async_get_bridgeid
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
if configured_hosts(self.hass):
|
||||||
|
return self.async_abort(reason='one_instance_only')
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
api_key = await async_get_api_key(session, **self.deconz_config)
|
||||||
|
if api_key:
|
||||||
|
self.deconz_config[CONF_API_KEY] = api_key
|
||||||
|
if 'bridgeid' not in self.deconz_config:
|
||||||
|
self.deconz_config['bridgeid'] = await async_get_bridgeid(
|
||||||
|
session, **self.deconz_config)
|
||||||
|
return self.async_create_entry(
|
||||||
|
title='deCONZ-' + self.deconz_config['bridgeid'],
|
||||||
|
data=self.deconz_config
|
||||||
|
)
|
||||||
|
errors['base'] = 'no_key'
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='link',
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_discovery(self, discovery_info):
|
||||||
|
"""Prepare configuration for a discovered deCONZ bridge.
|
||||||
|
|
||||||
|
This flow is triggered by the discovery component.
|
||||||
|
"""
|
||||||
|
deconz_config = {}
|
||||||
|
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
|
||||||
|
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
|
||||||
|
deconz_config['bridgeid'] = discovery_info.get('serial')
|
||||||
|
|
||||||
|
config_file = await self.hass.async_add_job(
|
||||||
|
load_json, self.hass.config.path(CONFIG_FILE))
|
||||||
|
if config_file and \
|
||||||
|
config_file[CONF_HOST] == deconz_config[CONF_HOST] and \
|
||||||
|
CONF_API_KEY in config_file:
|
||||||
|
deconz_config[CONF_API_KEY] = config_file[CONF_API_KEY]
|
||||||
|
|
||||||
|
return await self.async_step_import(deconz_config)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a deCONZ bridge as a config entry.
|
||||||
|
|
||||||
|
This flow is triggered by `async_setup` for configured bridges.
|
||||||
|
This flow is also triggered by `async_step_discovery`.
|
||||||
|
|
||||||
|
This will execute for any bridge that does not have a
|
||||||
|
config entry yet (based on host).
|
||||||
|
|
||||||
|
If an API key is provided, we will create an entry.
|
||||||
|
Otherwise we will delegate to `link` step which
|
||||||
|
will ask user to link the bridge.
|
||||||
|
"""
|
||||||
|
from pydeconz.utils import async_get_bridgeid
|
||||||
|
|
||||||
|
if configured_hosts(self.hass):
|
||||||
|
return self.async_abort(reason='one_instance_only')
|
||||||
|
elif CONF_API_KEY not in import_config:
|
||||||
|
self.deconz_config = import_config
|
||||||
|
return await self.async_step_link()
|
||||||
|
|
||||||
|
if 'bridgeid' not in import_config:
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
import_config['bridgeid'] = await async_get_bridgeid(
|
||||||
|
session, **import_config)
|
||||||
|
return self.async_create_entry(
|
||||||
|
title='deCONZ-' + import_config['bridgeid'],
|
||||||
|
data=import_config
|
||||||
|
)
|
8
homeassistant/components/deconz/const.py
Normal file
8
homeassistant/components/deconz/const.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""Constants for the deCONZ component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger('homeassistant.components.deconz')
|
||||||
|
|
||||||
|
DOMAIN = 'deconz'
|
||||||
|
CONFIG_FILE = 'deconz.conf'
|
||||||
|
DATA_DECONZ_ID = 'deconz_entities'
|
@ -18,6 +18,7 @@
|
|||||||
"no_key": "Couldn't get an API key"
|
"no_key": "Couldn't get an API key"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
|
"already_configured": "Bridge is already configured",
|
||||||
"no_bridges": "No deCONZ bridges discovered",
|
"no_bridges": "No deCONZ bridges discovered",
|
||||||
"one_instance_only": "Component only supports one deCONZ instance"
|
"one_instance_only": "Component only supports one deCONZ instance"
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
|
|||||||
SERVICE_HOMEKIT = 'homekit'
|
SERVICE_HOMEKIT = 'homekit'
|
||||||
|
|
||||||
CONFIG_ENTRY_HANDLERS = {
|
CONFIG_ENTRY_HANDLERS = {
|
||||||
|
SERVICE_DECONZ: 'deconz',
|
||||||
SERVICE_HUE: 'hue',
|
SERVICE_HUE: 'hue',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +58,6 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_WINK: ('wink', None),
|
SERVICE_WINK: ('wink', None),
|
||||||
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
|
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
|
||||||
SERVICE_TELLDUSLIVE: ('tellduslive', None),
|
SERVICE_TELLDUSLIVE: ('tellduslive', None),
|
||||||
SERVICE_DECONZ: ('deconz', None),
|
|
||||||
SERVICE_DAIKIN: ('daikin', None),
|
SERVICE_DAIKIN: ('daikin', None),
|
||||||
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
||||||
'google_cast': ('media_player', 'cast'),
|
'google_cast': ('media_player', 'cast'),
|
||||||
|
1
tests/components/deconz/__init__.py
Normal file
1
tests/components/deconz/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the deCONZ component."""
|
225
tests/components/deconz/test_config_flow.py
Normal file
225
tests/components/deconz/test_config_flow.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
"""Tests for deCONZ config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant.components.deconz import config_flow
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
import pydeconz
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_works(hass, aioclient_mock):
|
||||||
|
"""Test that config flow works."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': 80}
|
||||||
|
])
|
||||||
|
aioclient_mock.post('http://1.2.3.4:80/api', json=[
|
||||||
|
{"success": {"username": "1234567890ABCDEF"}}
|
||||||
|
])
|
||||||
|
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
await flow.async_step_init()
|
||||||
|
result = await flow.async_step_link(user_input={})
|
||||||
|
|
||||||
|
assert result['type'] == 'create_entry'
|
||||||
|
assert result['title'] == 'deCONZ-id'
|
||||||
|
assert result['data'] == {
|
||||||
|
'bridgeid': 'id',
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'api_key': '1234567890ABCDEF'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_already_registered_bridge(hass):
|
||||||
|
"""Test config flow don't allow more than one bridge to be registered."""
|
||||||
|
MockConfigEntry(domain='deconz', data={
|
||||||
|
'host': '1.2.3.4'
|
||||||
|
}).add_to_hass(hass)
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_no_discovered_bridges(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers no bridges."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[])
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_one_bridge_discovered(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers one bridge."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': 80}
|
||||||
|
])
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_two_bridges_discovered(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers two bridges."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id1', 'internalipaddress': '1.2.3.4', 'internalport': 80},
|
||||||
|
{'id': 'id2', 'internalipaddress': '5.6.7.8', 'internalport': 80}
|
||||||
|
])
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'init'
|
||||||
|
|
||||||
|
with pytest.raises(vol.Invalid):
|
||||||
|
assert result['data_schema']({'host': '0.0.0.0'})
|
||||||
|
|
||||||
|
result['data_schema']({'host': '1.2.3.4'})
|
||||||
|
result['data_schema']({'host': '5.6.7.8'})
|
||||||
|
|
||||||
|
|
||||||
|
async def test_link_no_api_key(hass, aioclient_mock):
|
||||||
|
"""Test config flow should abort if no API key was possible to retrieve."""
|
||||||
|
aioclient_mock.post('http://1.2.3.4:80/api', json=[])
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
|
||||||
|
|
||||||
|
result = await flow.async_step_link(user_input={})
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
assert result['errors'] == {'base': 'no_key'}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_link_already_registered_bridge(hass):
|
||||||
|
"""Test that link verifies to only allow one config entry to complete.
|
||||||
|
|
||||||
|
This is possible with discovery which will allow the user to complete
|
||||||
|
a second config entry and then complete the discovered config entry.
|
||||||
|
"""
|
||||||
|
MockConfigEntry(domain='deconz', data={
|
||||||
|
'host': '1.2.3.4'
|
||||||
|
}).add_to_hass(hass)
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
|
||||||
|
|
||||||
|
result = await flow.async_step_link(user_input={})
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_discovery(hass):
|
||||||
|
"""Test a bridge being discovered with no additional config file."""
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
with patch.object(config_flow, 'load_json', return_value={}):
|
||||||
|
result = await flow.async_step_discovery({
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'serial': 'id'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_discovery_config_file(hass):
|
||||||
|
"""Test a bridge being discovered with a corresponding config file."""
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
with patch.object(config_flow, 'load_json',
|
||||||
|
return_value={'host': '1.2.3.4',
|
||||||
|
'port': 8080,
|
||||||
|
'api_key': '1234567890ABCDEF'}):
|
||||||
|
result = await flow.async_step_discovery({
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'serial': 'id'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'create_entry'
|
||||||
|
assert result['title'] == 'deCONZ-id'
|
||||||
|
assert result['data'] == {
|
||||||
|
'bridgeid': 'id',
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'api_key': '1234567890ABCDEF'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_discovery_other_config_file(hass):
|
||||||
|
"""Test a bridge being discovered with another bridges config file."""
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
with patch.object(config_flow, 'load_json',
|
||||||
|
return_value={'host': '5.6.7.8', 'api_key': '5678'}):
|
||||||
|
result = await flow.async_step_discovery({
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'serial': 'id'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_discovery_already_configured(hass):
|
||||||
|
"""Test if a discovered bridge has already been configured."""
|
||||||
|
MockConfigEntry(domain='deconz', data={
|
||||||
|
'host': '1.2.3.4'
|
||||||
|
}).add_to_hass(hass)
|
||||||
|
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_discovery({
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'serial': 'id'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_without_api_key(hass):
|
||||||
|
"""Test importing a host without an API key."""
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_import({
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_with_api_key(hass):
|
||||||
|
"""Test importing a host with an API key."""
|
||||||
|
flow = config_flow.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_import({
|
||||||
|
'bridgeid': 'id',
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'api_key': '1234567890ABCDEF'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert result['type'] == 'create_entry'
|
||||||
|
assert result['title'] == 'deCONZ-id'
|
||||||
|
assert result['data'] == {
|
||||||
|
'bridgeid': 'id',
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': 80,
|
||||||
|
'api_key': '1234567890ABCDEF'
|
||||||
|
}
|
69
tests/components/deconz/test_init.py
Normal file
69
tests/components/deconz/test_init.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Test deCONZ component setup process."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.components import deconz
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_with_host_passed_to_config_entry(hass):
|
||||||
|
"""Test that configured options for a host are loaded via config entry."""
|
||||||
|
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||||
|
patch.object(deconz, 'configured_hosts', return_value=[]), \
|
||||||
|
patch.object(deconz, 'load_json', return_value={}):
|
||||||
|
assert await async_setup_component(hass, deconz.DOMAIN, {
|
||||||
|
deconz.DOMAIN: {
|
||||||
|
deconz.CONF_HOST: '1.2.3.4',
|
||||||
|
deconz.CONF_PORT: 80
|
||||||
|
}
|
||||||
|
}) is True
|
||||||
|
# Import flow started
|
||||||
|
assert len(mock_config_entries.flow.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_file_passed_to_config_entry(hass):
|
||||||
|
"""Test that configuration file for a host are loaded via config entry."""
|
||||||
|
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||||
|
patch.object(deconz, 'configured_hosts', return_value=[]), \
|
||||||
|
patch.object(deconz, 'load_json',
|
||||||
|
return_value={'host': '1.2.3.4'}):
|
||||||
|
assert await async_setup_component(hass, deconz.DOMAIN, {
|
||||||
|
deconz.DOMAIN: {}
|
||||||
|
}) is True
|
||||||
|
# Import flow started
|
||||||
|
assert len(mock_config_entries.flow.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_without_host_not_passed_to_config_entry(hass):
|
||||||
|
"""Test that a configuration without a host does not initiate an import."""
|
||||||
|
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||||
|
patch.object(deconz, 'configured_hosts', return_value=[]), \
|
||||||
|
patch.object(deconz, 'load_json', return_value={}):
|
||||||
|
assert await async_setup_component(hass, deconz.DOMAIN, {
|
||||||
|
deconz.DOMAIN: {}
|
||||||
|
}) is True
|
||||||
|
# No flow started
|
||||||
|
assert len(mock_config_entries.flow.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_already_registered_not_passed_to_config_entry(hass):
|
||||||
|
"""Test that an already registered host does not initiate an import."""
|
||||||
|
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||||
|
patch.object(deconz, 'configured_hosts',
|
||||||
|
return_value=['1.2.3.4']), \
|
||||||
|
patch.object(deconz, 'load_json', return_value={}):
|
||||||
|
assert await async_setup_component(hass, deconz.DOMAIN, {
|
||||||
|
deconz.DOMAIN: {
|
||||||
|
deconz.CONF_HOST: '1.2.3.4',
|
||||||
|
deconz.CONF_PORT: 80
|
||||||
|
}
|
||||||
|
}) is True
|
||||||
|
# No flow started
|
||||||
|
assert len(mock_config_entries.flow.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_discovery(hass):
|
||||||
|
"""Test that a discovered bridge does not initiate an import."""
|
||||||
|
with patch.object(hass, 'config_entries') as mock_config_entries:
|
||||||
|
assert await async_setup_component(hass, deconz.DOMAIN, {}) is True
|
||||||
|
# No flow started
|
||||||
|
assert len(mock_config_entries.flow.mock_calls) == 0
|
@ -1,97 +0,0 @@
|
|||||||
"""Tests for deCONZ config flow."""
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
import homeassistant.components.deconz as deconz
|
|
||||||
import pydeconz
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_works(hass, aioclient_mock):
|
|
||||||
"""Test config flow."""
|
|
||||||
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
|
||||||
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': '80'}
|
|
||||||
])
|
|
||||||
aioclient_mock.post('http://1.2.3.4:80/api', json=[
|
|
||||||
{"success": {"username": "1234567890ABCDEF"}}
|
|
||||||
])
|
|
||||||
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
await flow.async_step_init()
|
|
||||||
result = await flow.async_step_link(user_input={})
|
|
||||||
|
|
||||||
assert result['type'] == 'create_entry'
|
|
||||||
assert result['title'] == 'deCONZ'
|
|
||||||
assert result['data'] == {
|
|
||||||
'bridgeid': 'id',
|
|
||||||
'host': '1.2.3.4',
|
|
||||||
'port': '80',
|
|
||||||
'api_key': '1234567890ABCDEF'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_already_registered_bridge(hass, aioclient_mock):
|
|
||||||
"""Test config flow don't allow more than one bridge to be registered."""
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
flow.hass.data[deconz.DOMAIN] = True
|
|
||||||
|
|
||||||
result = await flow.async_step_init()
|
|
||||||
assert result['type'] == 'abort'
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_no_discovered_bridges(hass, aioclient_mock):
|
|
||||||
"""Test config flow discovers no bridges."""
|
|
||||||
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[])
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init()
|
|
||||||
assert result['type'] == 'abort'
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_one_bridge_discovered(hass, aioclient_mock):
|
|
||||||
"""Test config flow discovers one bridge."""
|
|
||||||
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
|
||||||
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': '80'}
|
|
||||||
])
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init()
|
|
||||||
assert result['type'] == 'form'
|
|
||||||
assert result['step_id'] == 'link'
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_two_bridges_discovered(hass, aioclient_mock):
|
|
||||||
"""Test config flow discovers two bridges."""
|
|
||||||
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
|
||||||
{'id': 'id1', 'internalipaddress': '1.2.3.4', 'internalport': '80'},
|
|
||||||
{'id': 'id2', 'internalipaddress': '5.6.7.8', 'internalport': '80'}
|
|
||||||
])
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
|
|
||||||
result = await flow.async_step_init()
|
|
||||||
assert result['type'] == 'form'
|
|
||||||
assert result['step_id'] == 'init'
|
|
||||||
|
|
||||||
with pytest.raises(vol.Invalid):
|
|
||||||
assert result['data_schema']({'host': '0.0.0.0'})
|
|
||||||
|
|
||||||
result['data_schema']({'host': '1.2.3.4'})
|
|
||||||
result['data_schema']({'host': '5.6.7.8'})
|
|
||||||
|
|
||||||
|
|
||||||
async def test_flow_no_api_key(hass, aioclient_mock):
|
|
||||||
"""Test config flow discovers no bridges."""
|
|
||||||
aioclient_mock.post('http://1.2.3.4:80/api', json=[])
|
|
||||||
flow = deconz.DeconzFlowHandler()
|
|
||||||
flow.hass = hass
|
|
||||||
flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
|
|
||||||
|
|
||||||
result = await flow.async_step_link(user_input={})
|
|
||||||
assert result['type'] == 'form'
|
|
||||||
assert result['step_id'] == 'link'
|
|
||||||
assert result['errors'] == {'base': 'no_key'}
|
|
Loading…
x
Reference in New Issue
Block a user