Use config entry unique id for deCONZ (#30122)

* Use config entry unique id

* Clean up

* Backwards compatiblity note

* Fix some of Balloobs comments

* Bump dependency to v66

* Black somehow missed config flow tests...

* Move set unique ID til after possibility to update existing entry
This commit is contained in:
Robert Svensson 2020-01-03 11:50:53 +01:00 committed by Paulus Schoutsen
parent c130e81638
commit 0a4f3ec1ec
12 changed files with 136 additions and 226 deletions

View File

@ -4,8 +4,8 @@ import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from .config_flow import get_master_gateway from .config_flow import get_master_gateway
from .const import CONF_BRIDGEID, CONF_MASTER_GATEWAY, CONF_UUID, DOMAIN from .const import CONF_MASTER_GATEWAY, DOMAIN
from .gateway import DeconzGateway, get_gateway_from_config_entry from .gateway import DeconzGateway
from .services import async_setup_services, async_unload_services from .services import async_setup_services, async_unload_services
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
@ -35,13 +35,16 @@ async def async_setup_entry(hass, config_entry):
if not await gateway.async_setup(): if not await gateway.async_setup():
return False return False
hass.data[DOMAIN][gateway.bridgeid] = gateway # 0.104 introduced config entry unique id, this makes upgrading possible
if config_entry.unique_id is None:
hass.config_entries.async_update_entry(
config_entry, unique_id=gateway.api.config.bridgeid
)
hass.data[DOMAIN][config_entry.unique_id] = gateway
await gateway.async_update_device_registry() await gateway.async_update_device_registry()
if CONF_UUID not in config_entry.data:
await async_add_uuid_to_config_entry(hass, config_entry)
await async_setup_services(hass) await async_setup_services(hass)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
@ -51,7 +54,7 @@ async def async_setup_entry(hass, config_entry):
async def async_unload_entry(hass, config_entry): async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry.""" """Unload deCONZ config entry."""
gateway = hass.data[DOMAIN].pop(config_entry.data[CONF_BRIDGEID]) gateway = hass.data[DOMAIN].pop(config_entry.unique_id)
if not hass.data[DOMAIN]: if not hass.data[DOMAIN]:
await async_unload_services(hass) await async_unload_services(hass)
@ -74,11 +77,3 @@ async def async_update_master_gateway(hass, config_entry):
options = {**config_entry.options, CONF_MASTER_GATEWAY: master} options = {**config_entry.options, CONF_MASTER_GATEWAY: master}
hass.config_entries.async_update_entry(config_entry, options=options) hass.config_entries.async_update_entry(config_entry, options=options)
async def async_add_uuid_to_config_entry(hass, config_entry):
"""Add UUID to config entry to help discovery identify entries."""
gateway = get_gateway_from_config_entry(hass, config_entry)
config = {**config_entry.data, CONF_UUID: gateway.api.config.uuid}
hass.config_entries.async_update_entry(config_entry, data=config)

View File

@ -4,7 +4,12 @@ from urllib.parse import urlparse
import async_timeout import async_timeout
from pydeconz.errors import RequestError, ResponseError from pydeconz.errors import RequestError, ResponseError
from pydeconz.utils import async_discovery, async_get_api_key, async_get_gateway_config from pydeconz.utils import (
async_discovery,
async_get_api_key,
async_get_bridge_id,
normalize_bridge_id,
)
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -14,11 +19,9 @@ from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import ( from .const import (
_LOGGER,
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_CLIP_SENSOR,
CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_DECONZ_GROUPS,
CONF_BRIDGEID, CONF_BRIDGEID,
CONF_UUID,
DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR,
DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS,
DEFAULT_PORT, DEFAULT_PORT,
@ -29,15 +32,6 @@ DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de"
CONF_SERIAL = "serial" CONF_SERIAL = "serial"
@callback
def configured_gateways(hass):
"""Return a set of all configured gateways."""
return {
entry.data[CONF_BRIDGEID]: entry
for entry in hass.config_entries.async_entries(DOMAIN)
}
@callback @callback
def get_master_gateway(hass): def get_master_gateway(hass):
"""Return the gateway which is marked as master.""" """Return the gateway which is marked as master."""
@ -62,6 +56,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self): def __init__(self):
"""Initialize the deCONZ config flow.""" """Initialize the deCONZ config flow."""
self.bridge_id = None
self.bridges = [] self.bridges = []
self.deconz_config = {} self.deconz_config = {}
@ -79,7 +74,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
for bridge in self.bridges: for bridge in self.bridges:
if bridge[CONF_HOST] == user_input[CONF_HOST]: if bridge[CONF_HOST] == user_input[CONF_HOST]:
self.deconz_config = bridge self.bridge_id = bridge[CONF_BRIDGEID]
self.deconz_config = {
CONF_HOST: bridge[CONF_HOST],
CONF_PORT: bridge[CONF_PORT],
}
return await self.async_step_link() return await self.async_step_link()
self.deconz_config = user_input self.deconz_config = user_input
@ -95,8 +94,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self.bridges = [] self.bridges = []
if len(self.bridges) == 1: if len(self.bridges) == 1:
self.deconz_config = self.bridges[0] return await self.async_step_user(self.bridges[0])
return await self.async_step_link()
if len(self.bridges) > 1: if len(self.bridges) > 1:
hosts = [] hosts = []
@ -141,23 +139,21 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def _create_entry(self): async def _create_entry(self):
"""Create entry for gateway.""" """Create entry for gateway."""
if CONF_BRIDGEID not in self.deconz_config: if not self.bridge_id:
session = aiohttp_client.async_get_clientsession(self.hass) session = aiohttp_client.async_get_clientsession(self.hass)
try: try:
with async_timeout.timeout(10): with async_timeout.timeout(10):
gateway_config = await async_get_gateway_config( self.bridge_id = await async_get_bridge_id(
session, **self.deconz_config session, **self.deconz_config
) )
self.deconz_config[CONF_BRIDGEID] = gateway_config.bridgeid await self.async_set_unique_id(self.bridge_id)
self.deconz_config[CONF_UUID] = gateway_config.uuid self._abort_if_unique_id_configured()
except asyncio.TimeoutError: except asyncio.TimeoutError:
return self.async_abort(reason="no_bridges") return self.async_abort(reason="no_bridges")
return self.async_create_entry( return self.async_create_entry(title=self.bridge_id, data=self.deconz_config)
title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], data=self.deconz_config
)
def _update_entry(self, entry, host, port, api_key=None): def _update_entry(self, entry, host, port, api_key=None):
"""Update existing entry.""" """Update existing entry."""
@ -182,27 +178,17 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL: if discovery_info[ssdp.ATTR_UPNP_MANUFACTURER_URL] != DECONZ_MANUFACTURERURL:
return self.async_abort(reason="not_deconz_bridge") return self.async_abort(reason="not_deconz_bridge")
uuid = discovery_info[ssdp.ATTR_UPNP_UDN].replace("uuid:", "") self.bridge_id = normalize_bridge_id(discovery_info[ssdp.ATTR_UPNP_SERIAL])
_LOGGER.debug("deCONZ gateway discovered (%s)", uuid)
parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
for entry in self.hass.config_entries.async_entries(DOMAIN): for entry in self.hass.config_entries.async_entries(DOMAIN):
if uuid == entry.data.get(CONF_UUID): if self.bridge_id == entry.unique_id:
if entry.source == "hassio": if entry.source == "hassio":
return self.async_abort(reason="already_configured") return self.async_abort(reason="already_configured")
return self._update_entry(entry, parsed_url.hostname, parsed_url.port) return self._update_entry(entry, parsed_url.hostname, parsed_url.port)
bridgeid = discovery_info[ssdp.ATTR_UPNP_SERIAL] await self.async_set_unique_id(self.bridge_id)
if any(
bridgeid == flow["context"][CONF_BRIDGEID]
for flow in self._async_in_progress()
):
return self.async_abort(reason="already_in_progress")
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
self.context[CONF_BRIDGEID] = bridgeid
self.context["title_placeholders"] = {"host": parsed_url.hostname} self.context["title_placeholders"] = {"host": parsed_url.hostname}
self.deconz_config = { self.deconz_config = {
@ -217,18 +203,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
This flow is triggered by the discovery component. This flow is triggered by the discovery component.
""" """
bridgeid = user_input[CONF_SERIAL] self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL])
gateway_entries = configured_gateways(self.hass) gateway = self.hass.data.get(DOMAIN, {}).get(self.bridge_id)
if bridgeid in gateway_entries: if gateway:
entry = gateway_entries[bridgeid]
return self._update_entry( return self._update_entry(
entry, gateway.config_entry,
user_input[CONF_HOST], user_input[CONF_HOST],
user_input[CONF_PORT], user_input[CONF_PORT],
user_input[CONF_API_KEY], user_input[CONF_API_KEY],
) )
await self.async_set_unique_id(self.bridge_id)
self._hassio_discovery = user_input self._hassio_discovery = user_input
return await self.async_step_hassio_confirm() return await self.async_step_hassio_confirm()
@ -239,7 +225,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self.deconz_config = { self.deconz_config = {
CONF_HOST: self._hassio_discovery[CONF_HOST], CONF_HOST: self._hassio_discovery[CONF_HOST],
CONF_PORT: self._hassio_discovery[CONF_PORT], CONF_PORT: self._hassio_discovery[CONF_PORT],
CONF_BRIDGEID: self._hassio_discovery[CONF_SERIAL],
CONF_API_KEY: self._hassio_discovery[CONF_API_KEY], CONF_API_KEY: self._hassio_discovery[CONF_API_KEY],
} }

View File

@ -6,7 +6,6 @@ _LOGGER = logging.getLogger(__package__)
DOMAIN = "deconz" DOMAIN = "deconz"
CONF_BRIDGEID = "bridgeid" CONF_BRIDGEID = "bridgeid"
CONF_UUID = "uuid"
DEFAULT_PORT = 80 DEFAULT_PORT = 80
DEFAULT_ALLOW_CLIP_SENSOR = False DEFAULT_ALLOW_CLIP_SENSOR = False

View File

@ -15,9 +15,7 @@ from homeassistant.const import (
) )
from . import DOMAIN from . import DOMAIN
from .config_flow import configured_gateways
from .deconz_event import CONF_DECONZ_EVENT, CONF_UNIQUE_ID from .deconz_event import CONF_DECONZ_EVENT, CONF_UNIQUE_ID
from .gateway import get_gateway_from_config_entry
CONF_SUBTYPE = "subtype" CONF_SUBTYPE = "subtype"
@ -287,10 +285,8 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
def _get_deconz_event_from_device_id(hass, device_id): def _get_deconz_event_from_device_id(hass, device_id):
"""Resolve deconz event from device id.""" """Resolve deconz event from device id."""
deconz_config_entries = configured_gateways(hass) for gateway in hass.data.get(DOMAIN):
for config_entry in deconz_config_entries.values():
gateway = get_gateway_from_config_entry(hass, config_entry)
for deconz_event in gateway.events: for deconz_event in gateway.events:
if device_id == deconz_event.device_id: if device_id == deconz_event.device_id:

View File

@ -22,7 +22,6 @@ from .const import (
_LOGGER, _LOGGER,
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_CLIP_SENSOR,
CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_DECONZ_GROUPS,
CONF_BRIDGEID,
CONF_MASTER_GATEWAY, CONF_MASTER_GATEWAY,
DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR,
DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS,
@ -36,7 +35,7 @@ from .errors import AuthenticationRequired, CannotConnect
@callback @callback
def get_gateway_from_config_entry(hass, config_entry): def get_gateway_from_config_entry(hass, config_entry):
"""Return gateway with a matching bridge id.""" """Return gateway with a matching bridge id."""
return hass.data[DOMAIN][config_entry.data[CONF_BRIDGEID]] return hass.data[DOMAIN][config_entry.unique_id]
class DeconzGateway: class DeconzGateway:
@ -56,7 +55,7 @@ class DeconzGateway:
@property @property
def bridgeid(self) -> str: def bridgeid(self) -> str:
"""Return the unique identifier of the gateway.""" """Return the unique identifier of the gateway."""
return self.config_entry.data[CONF_BRIDGEID] return self.config_entry.unique_id
@property @property
def master(self) -> bool: def master(self) -> bool:
@ -92,11 +91,9 @@ class DeconzGateway:
async def async_setup(self) -> bool: async def async_setup(self) -> bool:
"""Set up a deCONZ gateway.""" """Set up a deCONZ gateway."""
hass = self.hass
try: try:
self.api = await get_gateway( self.api = await get_gateway(
hass, self.hass,
self.config_entry.data, self.config_entry.data,
self.async_add_device_callback, self.async_add_device_callback,
self.async_connection_status_callback, self.async_connection_status_callback,
@ -110,8 +107,8 @@ class DeconzGateway:
return False return False
for component in SUPPORTED_PLATFORMS: for component in SUPPORTED_PLATFORMS:
hass.async_create_task( self.hass.async_create_task(
hass.config_entries.async_forward_entry_setup( self.hass.config_entries.async_forward_entry_setup(
self.config_entry, component self.config_entry, component
) )
) )

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/deconz", "documentation": "https://www.home-assistant.io/integrations/deconz",
"requirements": [ "requirements": [
"pydeconz==65" "pydeconz==66"
], ],
"ssdp": [ "ssdp": [
{ {

View File

@ -1187,7 +1187,7 @@ pydaikin==1.6.1
pydanfossair==0.1.0 pydanfossair==0.1.0
# homeassistant.components.deconz # homeassistant.components.deconz
pydeconz==65 pydeconz==66
# homeassistant.components.delijn # homeassistant.components.delijn
pydelijn==0.5.1 pydelijn==0.5.1

View File

@ -410,7 +410,7 @@ pycoolmasternet==0.0.4
pydaikin==1.6.1 pydaikin==1.6.1
# homeassistant.components.deconz # homeassistant.components.deconz
pydeconz==65 pydeconz==66
# homeassistant.components.zwave # homeassistant.components.zwave
pydispatcher==2.0.5 pydispatcher==2.0.5

View File

@ -1,11 +1,20 @@
"""Tests for deCONZ config flow.""" """Tests for deCONZ config flow."""
import asyncio import asyncio
from unittest.mock import Mock, patch from copy import deepcopy
from asynctest import Mock, patch
import pydeconz import pydeconz
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.deconz import config_flow from homeassistant.components.deconz import config_flow
from homeassistant.components.deconz.const import CONF_BRIDGEID
from .test_gateway import (
BRIDGEID,
DECONZ_WEB_REQUEST,
ENTRY_CONFIG,
setup_deconz_integration,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -14,7 +23,7 @@ async def test_flow_works(hass, aioclient_mock):
"""Test that config flow works.""" """Test that config flow works."""
aioclient_mock.get( aioclient_mock.get(
pydeconz.utils.URL_DISCOVER, pydeconz.utils.URL_DISCOVER,
json=[{"id": "id", "internalipaddress": "1.2.3.4", "internalport": 80}], json=[{"id": BRIDGEID, "internalipaddress": "1.2.3.4", "internalport": 80}],
headers={"content-type": "application/json"}, headers={"content-type": "application/json"},
) )
aioclient_mock.post( aioclient_mock.post(
@ -35,9 +44,8 @@ async def test_flow_works(hass, aioclient_mock):
) )
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "deCONZ-id" assert result["title"] == BRIDGEID
assert result["data"] == { assert result["data"] == {
config_flow.CONF_BRIDGEID: "id",
config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
config_flow.CONF_API_KEY: "1234567890ABCDEF", config_flow.CONF_API_KEY: "1234567890ABCDEF",
@ -117,12 +125,12 @@ async def test_user_step_two_bridges_selection(hass, aioclient_mock):
flow.hass = hass flow.hass = hass
flow.bridges = [ flow.bridges = [
{ {
config_flow.CONF_BRIDGEID: "id1", CONF_BRIDGEID: "id1",
config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
}, },
{ {
config_flow.CONF_BRIDGEID: "id2", CONF_BRIDGEID: "id2",
config_flow.CONF_HOST: "5.6.7.8", config_flow.CONF_HOST: "5.6.7.8",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
}, },
@ -241,25 +249,22 @@ async def test_bridge_discovery_update_existing_entry(hass):
"""Test if a discovered bridge has already been configured.""" """Test if a discovered bridge has already been configured."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=config_flow.DOMAIN, domain=config_flow.DOMAIN,
data={ source="user",
config_flow.CONF_HOST: "1.2.3.4", data={config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_PORT: 80},
config_flow.CONF_BRIDGEID: "123ABC", unique_id=BRIDGEID,
config_flow.CONF_UUID: "456DEF",
},
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
gateway = Mock() gateway = Mock()
gateway.config_entry = entry gateway.config_entry = entry
hass.data[config_flow.DOMAIN] = {"123ABC": gateway} hass.data[config_flow.DOMAIN] = {BRIDGEID: gateway}
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ data={
ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/",
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL,
ssdp.ATTR_UPNP_SERIAL: "123ABC", ssdp.ATTR_UPNP_SERIAL: BRIDGEID,
ssdp.ATTR_UPNP_UDN: "uuid:456DEF",
}, },
context={"source": "ssdp"}, context={"source": "ssdp"},
) )
@ -277,8 +282,8 @@ async def test_bridge_discovery_dont_update_existing_hassio_entry(hass):
data={ data={
config_flow.CONF_HOST: "core-deconz", config_flow.CONF_HOST: "core-deconz",
config_flow.CONF_BRIDGEID: "123ABC", config_flow.CONF_BRIDGEID: "123ABC",
config_flow.CONF_UUID: "456DEF",
}, },
unique_id="123ABC",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@ -292,7 +297,6 @@ async def test_bridge_discovery_dont_update_existing_hassio_entry(hass):
ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/", ssdp.ATTR_SSDP_LOCATION: "http://mock-deconz/",
ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL, ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.DECONZ_MANUFACTURERURL,
ssdp.ATTR_UPNP_SERIAL: "123ABC", ssdp.ATTR_UPNP_SERIAL: "123ABC",
ssdp.ATTR_UPNP_UDN: "uuid:456DEF",
}, },
context={"source": "ssdp"}, context={"source": "ssdp"},
) )
@ -306,11 +310,12 @@ async def test_create_entry(hass, aioclient_mock):
"""Test that _create_entry work and that bridgeid can be requested.""" """Test that _create_entry work and that bridgeid can be requested."""
aioclient_mock.get( aioclient_mock.get(
"http://1.2.3.4:80/api/1234567890ABCDEF/config", "http://1.2.3.4:80/api/1234567890ABCDEF/config",
json={"bridgeid": "123ABC", "uuid": "456DEF"}, json={"bridgeid": BRIDGEID, "uuid": "456DEF"},
headers={"content-type": "application/json"}, headers={"content-type": "application/json"},
) )
flow = config_flow.DeconzFlowHandler() flow = config_flow.DeconzFlowHandler()
flow.context = {}
flow.hass = hass flow.hass = hass
flow.deconz_config = { flow.deconz_config = {
config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_HOST: "1.2.3.4",
@ -321,13 +326,11 @@ async def test_create_entry(hass, aioclient_mock):
result = await flow._create_entry() result = await flow._create_entry()
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "deCONZ-123ABC" assert result["title"] == BRIDGEID
assert result["data"] == { assert result["data"] == {
config_flow.CONF_BRIDGEID: "123ABC",
config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
config_flow.CONF_API_KEY: "1234567890ABCDEF", config_flow.CONF_API_KEY: "1234567890ABCDEF",
config_flow.CONF_UUID: "456DEF",
} }
@ -342,7 +345,7 @@ async def test_create_entry_timeout(hass, aioclient_mock):
} }
with patch( with patch(
"homeassistant.components.deconz.config_flow.async_get_gateway_config", "homeassistant.components.deconz.config_flow.async_get_bridge_id",
side_effect=asyncio.TimeoutError, side_effect=asyncio.TimeoutError,
): ):
result = await flow._create_entry() result = await flow._create_entry()
@ -353,54 +356,44 @@ async def test_create_entry_timeout(hass, aioclient_mock):
async def test_hassio_update_instance(hass): async def test_hassio_update_instance(hass):
"""Test we can update an existing config entry.""" """Test we can update an existing config entry."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=config_flow.DOMAIN, entry_config = deepcopy(ENTRY_CONFIG)
data={ gateway = await setup_deconz_integration(
config_flow.CONF_BRIDGEID: "id", hass, entry_config, options={}, get_state_response=data
config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 40850,
config_flow.CONF_API_KEY: "secret",
},
) )
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ data={
config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_HOST: "2.3.4.5",
config_flow.CONF_PORT: 8080, config_flow.CONF_PORT: 8080,
config_flow.CONF_API_KEY: "updated", config_flow.CONF_API_KEY: "updated",
config_flow.CONF_SERIAL: "id", config_flow.CONF_SERIAL: BRIDGEID,
}, },
context={"source": "hassio"}, context={"source": "hassio"},
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "updated_instance" assert result["reason"] == "updated_instance"
assert entry.data[config_flow.CONF_HOST] == "mock-deconz" assert gateway.config_entry.data[config_flow.CONF_HOST] == "2.3.4.5"
assert entry.data[config_flow.CONF_PORT] == 8080 assert gateway.config_entry.data[config_flow.CONF_PORT] == 8080
assert entry.data[config_flow.CONF_API_KEY] == "updated" assert gateway.config_entry.data[config_flow.CONF_API_KEY] == "updated"
async def test_hassio_dont_update_instance(hass): async def test_hassio_dont_update_instance(hass):
"""Test we can update an existing config entry.""" """Test we can update an existing config entry."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=config_flow.DOMAIN, await setup_deconz_integration(
data={ hass, ENTRY_CONFIG, options={}, get_state_response=data
config_flow.CONF_BRIDGEID: "id",
config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 8080,
config_flow.CONF_API_KEY: "secret",
},
) )
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, config_flow.DOMAIN,
data={ data={
config_flow.CONF_HOST: "1.2.3.4", config_flow.CONF_HOST: "1.2.3.4",
config_flow.CONF_PORT: 8080, config_flow.CONF_PORT: 80,
config_flow.CONF_API_KEY: "secret", config_flow.CONF_API_KEY: "ABCDEF",
config_flow.CONF_SERIAL: "id", config_flow.CONF_SERIAL: BRIDGEID,
}, },
context={"source": "hassio"}, context={"source": "hassio"},
) )
@ -417,7 +410,7 @@ async def test_hassio_confirm(hass):
"addon": "Mock Addon", "addon": "Mock Addon",
config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_HOST: "mock-deconz",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
config_flow.CONF_SERIAL: "id", config_flow.CONF_SERIAL: BRIDGEID,
config_flow.CONF_API_KEY: "1234567890ABCDEF", config_flow.CONF_API_KEY: "1234567890ABCDEF",
}, },
context={"source": "hassio"}, context={"source": "hassio"},
@ -434,7 +427,6 @@ async def test_hassio_confirm(hass):
assert result["result"].data == { assert result["result"].data == {
config_flow.CONF_HOST: "mock-deconz", config_flow.CONF_HOST: "mock-deconz",
config_flow.CONF_PORT: 80, config_flow.CONF_PORT: 80,
config_flow.CONF_BRIDGEID: "id",
config_flow.CONF_API_KEY: "1234567890ABCDEF", config_flow.CONF_API_KEY: "1234567890ABCDEF",
} }

View File

@ -10,14 +10,12 @@ from homeassistant.components import deconz, ssdp
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
BRIDGEID = "0123456789" BRIDGEID = "01234E56789A"
ENTRY_CONFIG = { ENTRY_CONFIG = {
deconz.config_flow.CONF_API_KEY: "ABCDEF", deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: BRIDGEID,
deconz.config_flow.CONF_HOST: "1.2.3.4", deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80, deconz.config_flow.CONF_PORT: 80,
deconz.config_flow.CONF_UUID: "456DEF",
} }
DECONZ_CONFIG = { DECONZ_CONFIG = {
@ -60,7 +58,8 @@ async def setup_deconz_integration(hass, config, options, get_state_response):
hass.config_entries._entries.append(config_entry) hass.config_entries._entries.append(config_entry)
return hass.data[deconz.DOMAIN].get(config[deconz.CONF_BRIDGEID]) bridgeid = get_state_response["config"]["bridgeid"]
return hass.data[deconz.DOMAIN].get(bridgeid)
async def test_gateway_setup(hass): async def test_gateway_setup(hass):

View File

@ -1,13 +1,14 @@
"""Test deCONZ component setup process.""" """Test deCONZ component setup process."""
import asyncio import asyncio
from copy import deepcopy
from asynctest import Mock, patch from asynctest import patch
import pytest import pytest
from homeassistant.components import deconz from homeassistant.components import deconz
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from tests.common import MockConfigEntry from .test_gateway import DECONZ_WEB_REQUEST, ENTRY_CONFIG, setup_deconz_integration
ENTRY1_HOST = "1.2.3.4" ENTRY1_HOST = "1.2.3.4"
ENTRY1_PORT = 80 ENTRY1_PORT = 80
@ -34,138 +35,83 @@ async def setup_entry(hass, entry):
async def test_setup_entry_fails(hass): async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available.""" """Test setup entry fails if deCONZ is not available."""
entry = Mock() data = deepcopy(DECONZ_WEB_REQUEST)
entry.data = {
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
}
with patch("pydeconz.DeconzSession.initialize", side_effect=Exception): with patch("pydeconz.DeconzSession.initialize", side_effect=Exception):
await deconz.async_setup_entry(hass, entry) await setup_deconz_integration(
hass, ENTRY_CONFIG, options={}, get_state_response=data
)
assert not hass.data[deconz.DOMAIN]
async def test_setup_entry_no_available_bridge(hass): async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available.""" """Test setup entry fails if deCONZ is not available."""
entry = Mock() data = deepcopy(DECONZ_WEB_REQUEST)
entry.data = {
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
}
with patch( with patch(
"pydeconz.DeconzSession.initialize", side_effect=asyncio.TimeoutError "pydeconz.DeconzSession.initialize", side_effect=asyncio.TimeoutError
), pytest.raises(ConfigEntryNotReady): ), pytest.raises(ConfigEntryNotReady):
await deconz.async_setup_entry(hass, entry) await setup_deconz_integration(
hass, ENTRY_CONFIG, options={}, get_state_response=data
)
async def test_setup_entry_successful(hass): async def test_setup_entry_successful(hass):
"""Test setup entry is successful.""" """Test setup entry is successful."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, gateway = await setup_deconz_integration(
data={ hass, ENTRY_CONFIG, options={}, get_state_response=data
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID,
deconz.CONF_UUID: ENTRY1_UUID,
},
) )
entry.add_to_hass(hass)
await setup_entry(hass, entry) assert hass.data[deconz.DOMAIN]
assert gateway.bridgeid in hass.data[deconz.DOMAIN]
assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN] assert hass.data[deconz.DOMAIN][gateway.bridgeid].master
assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
async def test_setup_entry_multiple_gateways(hass): async def test_setup_entry_multiple_gateways(hass):
"""Test setup entry is successful with multiple gateways.""" """Test setup entry is successful with multiple gateways."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, gateway = await setup_deconz_integration(
data={ hass, ENTRY_CONFIG, options={}, get_state_response=data
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID,
deconz.CONF_UUID: ENTRY1_UUID,
},
) )
entry.add_to_hass(hass)
entry2 = MockConfigEntry( data2 = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, data2["config"]["bridgeid"] = "01234E56789B"
data={ gateway2 = await setup_deconz_integration(
deconz.config_flow.CONF_HOST: ENTRY2_HOST, hass, ENTRY_CONFIG, options={}, get_state_response=data2
deconz.config_flow.CONF_PORT: ENTRY2_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID,
deconz.CONF_UUID: ENTRY2_UUID,
},
) )
entry2.add_to_hass(hass)
await setup_entry(hass, entry) assert len(hass.data[deconz.DOMAIN]) == 2
await setup_entry(hass, entry2) assert hass.data[deconz.DOMAIN][gateway.bridgeid].master
assert not hass.data[deconz.DOMAIN][gateway2.bridgeid].master
assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
assert not hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_unload_entry(hass): async def test_unload_entry(hass):
"""Test being able to unload an entry.""" """Test being able to unload an entry."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, gateway = await setup_deconz_integration(
data={ hass, ENTRY_CONFIG, options={}, get_state_response=data
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID,
deconz.CONF_UUID: ENTRY1_UUID,
},
) )
entry.add_to_hass(hass) assert hass.data[deconz.DOMAIN]
await setup_entry(hass, entry)
with patch.object(deconz.DeconzGateway, "async_reset", return_value=True):
assert await deconz.async_unload_entry(hass, entry)
assert await deconz.async_unload_entry(hass, gateway.config_entry)
assert not hass.data[deconz.DOMAIN] assert not hass.data[deconz.DOMAIN]
async def test_unload_entry_multiple_gateways(hass): async def test_unload_entry_multiple_gateways(hass):
"""Test being able to unload an entry and master gateway gets moved.""" """Test being able to unload an entry and master gateway gets moved."""
entry = MockConfigEntry( data = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, gateway = await setup_deconz_integration(
data={ hass, ENTRY_CONFIG, options={}, get_state_response=data
deconz.config_flow.CONF_HOST: ENTRY1_HOST,
deconz.config_flow.CONF_PORT: ENTRY1_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID,
deconz.CONF_UUID: ENTRY1_UUID,
},
) )
entry.add_to_hass(hass)
entry2 = MockConfigEntry( data2 = deepcopy(DECONZ_WEB_REQUEST)
domain=deconz.DOMAIN, data2["config"]["bridgeid"] = "01234E56789B"
data={ gateway2 = await setup_deconz_integration(
deconz.config_flow.CONF_HOST: ENTRY2_HOST, hass, ENTRY_CONFIG, options={}, get_state_response=data2
deconz.config_flow.CONF_PORT: ENTRY2_PORT,
deconz.config_flow.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID,
deconz.CONF_UUID: ENTRY2_UUID,
},
) )
entry2.add_to_hass(hass)
await setup_entry(hass, entry) assert len(hass.data[deconz.DOMAIN]) == 2
await setup_entry(hass, entry2)
with patch.object(deconz.DeconzGateway, "async_reset", return_value=True): assert await deconz.async_unload_entry(hass, gateway.config_entry)
assert await deconz.async_unload_entry(hass, entry)
assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN] assert len(hass.data[deconz.DOMAIN]) == 1
assert hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master assert hass.data[deconz.DOMAIN][gateway2.bridgeid].master

View File

@ -6,6 +6,7 @@ import pytest
import voluptuous as vol import voluptuous as vol
from homeassistant.components import deconz from homeassistant.components import deconz
from homeassistant.components.deconz.const import CONF_BRIDGEID
from .test_gateway import ( from .test_gateway import (
BRIDGEID, BRIDGEID,
@ -99,7 +100,7 @@ async def test_configure_service_with_field(hass):
data = { data = {
deconz.services.SERVICE_FIELD: "/light/2", deconz.services.SERVICE_FIELD: "/light/2",
deconz.CONF_BRIDGEID: BRIDGEID, CONF_BRIDGEID: BRIDGEID,
deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, deconz.services.SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20},
} }
@ -203,7 +204,7 @@ async def test_service_refresh_devices(hass):
hass, ENTRY_CONFIG, options={}, get_state_response=data hass, ENTRY_CONFIG, options={}, get_state_response=data
) )
data = {deconz.CONF_BRIDGEID: BRIDGEID} data = {CONF_BRIDGEID: BRIDGEID}
with patch( with patch(
"pydeconz.DeconzSession.request", "pydeconz.DeconzSession.request",