From c5cb28d41fcdf1b8cf4b7c8c852a5606ca8351da Mon Sep 17 00:00:00 2001 From: Kane610 Date: Wed, 18 Apr 2018 16:27:44 +0200 Subject: [PATCH] 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 --- .../components/deconz/.translations/en.json | 1 + homeassistant/components/deconz/__init__.py | 206 +++------------- .../components/deconz/config_flow.py | 139 +++++++++++ homeassistant/components/deconz/const.py | 8 + homeassistant/components/deconz/strings.json | 1 + homeassistant/components/discovery.py | 2 +- tests/components/deconz/__init__.py | 1 + tests/components/deconz/test_config_flow.py | 225 ++++++++++++++++++ tests/components/deconz/test_init.py | 69 ++++++ tests/components/test_deconz.py | 97 -------- 10 files changed, 481 insertions(+), 268 deletions(-) create mode 100644 homeassistant/components/deconz/config_flow.py create mode 100644 homeassistant/components/deconz/const.py create mode 100644 tests/components/deconz/__init__.py create mode 100644 tests/components/deconz/test_config_flow.py create mode 100644 tests/components/deconz/test_init.py delete mode 100644 tests/components/test_deconz.py diff --git a/homeassistant/components/deconz/.translations/en.json b/homeassistant/components/deconz/.translations/en.json index 69165dbbbaf..7ea68af01c1 100644 --- a/homeassistant/components/deconz/.translations/en.json +++ b/homeassistant/components/deconz/.translations/en.json @@ -18,6 +18,7 @@ "no_key": "Couldn't get an API key" }, "abort": { + "already_configured": "Bridge is already configured", "no_bridges": "No deCONZ bridges discovered", "one_instance_only": "Component only supports one deCONZ instance" } diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 0cf96576223..064725eda95 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -4,29 +4,21 @@ Support for deCONZ devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/deconz/ """ -import logging - import voluptuous as vol -from homeassistant import config_entries, data_entry_flow -from homeassistant.components.discovery import SERVICE_DECONZ from homeassistant.const import ( CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import discovery, aiohttp_client -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.util.json import load_json, save_json +from homeassistant.helpers import ( + aiohttp_client, discovery, config_validation as cv) +from homeassistant.util.json import load_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'] -_LOGGER = logging.getLogger(__name__) - -DOMAIN = 'deconz' -DATA_DECONZ_ID = 'deconz_entities' - -CONFIG_FILE = 'deconz.conf' - CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_API_KEY): cv.string, @@ -46,46 +38,38 @@ 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): - """Set up services and configuration for deCONZ component.""" - result = False - config_file = await hass.async_add_job( - 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: - result = await async_setup_deconz(hass, config, config_file) - - if not result and DOMAIN in config and CONF_HOST in config[DOMAIN]: - deconz_config = config[DOMAIN] - if CONF_API_KEY in deconz_config: - result = await async_setup_deconz(hass, config, deconz_config) - else: - await async_request_configuration(hass, config, deconz_config) - return True - - if not result: - discovery.async_listen(hass, SERVICE_DECONZ, async_deconz_discovered) + """Load configuration for deCONZ component. + 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( + load_json, hass.config.path(CONFIG_FILE)) + if config_file: + deconz_config = config_file + elif CONF_HOST in config[DOMAIN]: + deconz_config = config[DOMAIN] + if deconz_config and not configured_hosts(hass): + hass.async_add_job(hass.config_entries.flow.async_init( + DOMAIN, source='import', data=deconz_config + )) return True +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 False + + async def async_setup_deconz(hass, config, deconz_config): """Set up a deCONZ session. @@ -94,8 +78,8 @@ async def async_setup_deconz(hass, config, deconz_config): """ _LOGGER.debug("deCONZ config %s", deconz_config) from pydeconz import DeconzSession - websession = async_get_clientsession(hass) - deconz = DeconzSession(hass.loop, websession, **deconz_config) + session = aiohttp_client.async_get_clientsession(hass) + deconz = DeconzSession(hass.loop, session, **deconz_config) result = await deconz.async_load_parameters() if result is False: _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) 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 diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py new file mode 100644 index 00000000000..e900782ea65 --- /dev/null +++ b/homeassistant/components/deconz/config_flow.py @@ -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 + ) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py new file mode 100644 index 00000000000..c5820c971f6 --- /dev/null +++ b/homeassistant/components/deconz/const.py @@ -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' diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index 69165dbbbaf..7ea68af01c1 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -18,6 +18,7 @@ "no_key": "Couldn't get an API key" }, "abort": { + "already_configured": "Bridge is already configured", "no_bridges": "No deCONZ bridges discovered", "one_instance_only": "Component only supports one deCONZ instance" } diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 31ec3f2f60a..f0ebcba8366 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -43,6 +43,7 @@ SERVICE_SAMSUNG_PRINTER = 'samsung_printer' SERVICE_HOMEKIT = 'homekit' CONFIG_ENTRY_HANDLERS = { + SERVICE_DECONZ: 'deconz', SERVICE_HUE: 'hue', } @@ -57,7 +58,6 @@ SERVICE_HANDLERS = { SERVICE_WINK: ('wink', None), SERVICE_XIAOMI_GW: ('xiaomi_aqara', None), SERVICE_TELLDUSLIVE: ('tellduslive', None), - SERVICE_DECONZ: ('deconz', None), SERVICE_DAIKIN: ('daikin', None), SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'), 'google_cast': ('media_player', 'cast'), diff --git a/tests/components/deconz/__init__.py b/tests/components/deconz/__init__.py new file mode 100644 index 00000000000..59b903e8900 --- /dev/null +++ b/tests/components/deconz/__init__.py @@ -0,0 +1 @@ +"""Tests for the deCONZ component.""" diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py new file mode 100644 index 00000000000..d86475b35ef --- /dev/null +++ b/tests/components/deconz/test_config_flow.py @@ -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' + } diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py new file mode 100644 index 00000000000..cbc8a373972 --- /dev/null +++ b/tests/components/deconz/test_init.py @@ -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 diff --git a/tests/components/test_deconz.py b/tests/components/test_deconz.py deleted file mode 100644 index 2c7c656d560..00000000000 --- a/tests/components/test_deconz.py +++ /dev/null @@ -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'}