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:
parent
b9ec623ad9
commit
b50afec5f1
22 changed files with 535 additions and 426 deletions
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue