Support multiple deCONZ gateways (#22449)

* Store gateways inside a dict in deconz domain

* Make reachable events gateway specific

* Gateway shall always exist

* Adapt new device signalling to support multiple gateways

* Services follow gateway master

* Working on unload entry

* Make unload and master handover work
Improve tests for init

* Fix config flow

* Fix linting

* Clean up init tests

* Clean up hassio discovery to fit with the rest

* Store gateways inside a dict in deconz domain

* Make reachable events gateway specific

* Gateway shall always exist

* Adapt new device signalling to support multiple gateways

* Services follow gateway master

* Working on unload entry

* Make unload and master handover work
Improve tests for init

* Fix config flow

* Fix linting

* Clean up init tests

* Clean up hassio discovery to fit with the rest

* Add support for services to specify bridgeid
This commit is contained in:
Robert Svensson 2019-04-05 02:48:24 +02:00 committed by Jason Hu
parent b9ec623ad9
commit b50afec5f1
22 changed files with 535 additions and 426 deletions

View file

@ -11,56 +11,62 @@ from homeassistant.components import deconz
from tests.common import mock_coro, MockConfigEntry
ENTRY1_HOST = '1.2.3.4'
ENTRY1_PORT = 80
ENTRY1_API_KEY = '1234567890ABCDEF'
ENTRY1_BRIDGEID = '12345ABC'
CONFIG = {
"config": {
"bridgeid": "0123456789ABCDEF",
"mac": "12:34:56:78:90:ab",
"modelid": "deCONZ",
"name": "Phoscon",
"swversion": "2.05.35"
}
}
ENTRY2_HOST = '2.3.4.5'
ENTRY2_PORT = 80
ENTRY2_API_KEY = '1234567890ABCDEF'
ENTRY2_BRIDGEID = '23456DEF'
async def setup_entry(hass, entry):
"""Test that setup entry works."""
with patch.object(deconz.DeconzGateway, 'async_setup',
return_value=mock_coro(True)), \
patch.object(deconz.DeconzGateway, 'async_update_device_registry',
return_value=mock_coro(True)):
assert await deconz.async_setup_entry(hass, entry) is True
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=[]):
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4',
deconz.CONF_PORT: 80
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# Import flow started
assert len(mock_config_entries.flow.mock_calls) == 2
assert len(mock_config_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=[]):
MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {}
}) is True
# No flow started
assert len(mock_config_entries.flow.mock_calls) == 0
assert len(mock_config_flow.mock_calls) == 0
async def test_config_already_registered_not_passed_to_config_entry(hass):
async def test_config_import_entry_fails_when_entries_exist(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']):
MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4',
deconz.CONF_PORT: 80
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# No flow started
assert len(mock_config_entries.flow.mock_calls) == 0
assert len(mock_config_flow.mock_calls) == 0
async def test_config_discovery(hass):
@ -71,16 +77,14 @@ async def test_config_discovery(hass):
assert len(mock_config_entries.flow.mock_calls) == 0
async def test_setup_entry_already_registered_bridge(hass):
"""Test setup entry doesn't allow more than one instance of deCONZ."""
hass.data[deconz.DOMAIN] = True
assert await deconz.async_setup_entry(hass, {}) is False
async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
entry.data = {
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY
}
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=Exception):
await deconz.async_setup_entry(hass, entry)
@ -89,61 +93,121 @@ async def test_setup_entry_fails(hass):
async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
with patch(
'pydeconz.DeconzSession.async_load_parameters',
side_effect=asyncio.TimeoutError
), pytest.raises(ConfigEntryNotReady):
entry.data = {
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY
}
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=asyncio.TimeoutError),\
pytest.raises(ConfigEntryNotReady):
await deconz.async_setup_entry(hass, entry)
async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
assert hass.data[deconz.DOMAIN]
await setup_entry(hass, entry)
assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
async def test_setup_entry_multiple_gateways(hass):
"""Test setup entry is successful with multiple gateways."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY2_HOST,
deconz.CONF_PORT: ENTRY2_PORT,
deconz.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
})
entry2.add_to_hass(hass)
await setup_entry(hass, entry)
await setup_entry(hass, entry2)
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):
"""Test being able to unload an entry."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
mock_gateway.return_value.async_reset.return_value = mock_coro(True)
assert await deconz.async_unload_entry(hass, entry)
assert deconz.DOMAIN not in hass.data
await setup_entry(hass, entry)
with patch.object(deconz.DeconzGateway, 'async_reset',
return_value=mock_coro(True)):
assert await deconz.async_unload_entry(hass, entry)
assert not hass.data[deconz.DOMAIN]
async def test_unload_entry_multiple_gateways(hass):
"""Test being able to unload an entry and master gateway gets moved."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY2_HOST,
deconz.CONF_PORT: ENTRY2_PORT,
deconz.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
})
entry2.add_to_hass(hass)
await setup_entry(hass, entry)
await setup_entry(hass, entry2)
with patch.object(deconz.DeconzGateway, 'async_reset',
return_value=mock_coro(True)):
assert await deconz.async_unload_entry(hass, entry)
assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_service_configure(hass):
"""Test that service invokes pydeconz with the correct path and data."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
hass.data[deconz.DOMAIN].deconz_ids = {
await setup_entry(hass, entry)
hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].deconz_ids = {
'light.test': '/light/1'
}
data = {'on': True, 'attr1': 10, 'attr2': 20}
@ -191,25 +255,23 @@ async def test_service_configure(hass):
async def test_service_refresh_devices(hass):
"""Test that service can refresh devices."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
await setup_entry(hass, entry)
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
return_value=mock_coro(True)):
with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(True)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
return_value=mock_coro(False)):
with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(False)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()