2018-03-17 03:27:05 +00:00
|
|
|
"""Tests for Philips Hue config flow."""
|
|
|
|
import asyncio
|
2018-03-30 03:15:40 +00:00
|
|
|
from unittest.mock import Mock, patch
|
2018-03-17 03:27:05 +00:00
|
|
|
|
|
|
|
import aiohue
|
|
|
|
import pytest
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-03-30 03:15:40 +00:00
|
|
|
from homeassistant.components.hue import config_flow, const, errors
|
2018-03-17 03:27:05 +00:00
|
|
|
|
|
|
|
from tests.common import MockConfigEntry, mock_coro
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_works(hass, aioclient_mock):
|
|
|
|
"""Test config flow ."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[
|
2018-03-17 03:27:05 +00:00
|
|
|
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
|
|
|
|
])
|
|
|
|
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
await flow.async_step_init()
|
|
|
|
|
|
|
|
with patch('aiohue.Bridge') as mock_bridge:
|
2018-03-30 03:15:40 +00:00
|
|
|
def mock_constructor(host, websession, username=None):
|
|
|
|
"""Fake the bridge constructor."""
|
2018-03-17 03:27:05 +00:00
|
|
|
mock_bridge.host = host
|
|
|
|
return mock_bridge
|
|
|
|
|
|
|
|
mock_bridge.side_effect = mock_constructor
|
|
|
|
mock_bridge.username = 'username-abc'
|
|
|
|
mock_bridge.config.name = 'Mock Bridge'
|
|
|
|
mock_bridge.config.bridgeid = 'bridge-id-1234'
|
|
|
|
mock_bridge.create_user.return_value = mock_coro()
|
|
|
|
mock_bridge.initialize.return_value = mock_coro()
|
|
|
|
|
|
|
|
result = await flow.async_step_link(user_input={})
|
|
|
|
|
|
|
|
assert mock_bridge.host == '1.2.3.4'
|
|
|
|
assert len(mock_bridge.create_user.mock_calls) == 1
|
|
|
|
assert len(mock_bridge.initialize.mock_calls) == 1
|
|
|
|
|
|
|
|
assert result['type'] == 'create_entry'
|
|
|
|
assert result['title'] == 'Mock Bridge'
|
|
|
|
assert result['data'] == {
|
|
|
|
'host': '1.2.3.4',
|
|
|
|
'bridge_id': 'bridge-id-1234',
|
|
|
|
'username': 'username-abc'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_no_discovered_bridges(hass, aioclient_mock):
|
|
|
|
"""Test config flow discovers no bridges."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[])
|
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
result = await flow.async_step_init()
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_all_discovered_bridges_exist(hass, aioclient_mock):
|
|
|
|
"""Test config flow discovers only already configured bridges."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[
|
2018-03-17 03:27:05 +00:00
|
|
|
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
|
|
|
|
])
|
|
|
|
MockConfigEntry(domain='hue', data={
|
|
|
|
'host': '1.2.3.4'
|
|
|
|
}).add_to_hass(hass)
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
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."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[
|
2018-03-17 03:27:05 +00:00
|
|
|
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
|
|
|
|
])
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
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."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[
|
2018-03-17 03:27:05 +00:00
|
|
|
{'internalipaddress': '1.2.3.4', 'id': 'bla'},
|
|
|
|
{'internalipaddress': '5.6.7.8', 'id': 'beer'}
|
|
|
|
])
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
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_two_bridges_discovered_one_new(hass, aioclient_mock):
|
|
|
|
"""Test config flow discovers two bridges."""
|
2018-03-30 03:15:40 +00:00
|
|
|
aioclient_mock.get(const.API_NUPNP, json=[
|
2018-03-17 03:27:05 +00:00
|
|
|
{'internalipaddress': '1.2.3.4', 'id': 'bla'},
|
|
|
|
{'internalipaddress': '5.6.7.8', 'id': 'beer'}
|
|
|
|
])
|
|
|
|
MockConfigEntry(domain='hue', data={
|
|
|
|
'host': '1.2.3.4'
|
|
|
|
}).add_to_hass(hass)
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
result = await flow.async_step_init()
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
assert flow.host == '5.6.7.8'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_timeout_discovery(hass):
|
|
|
|
"""Test config flow ."""
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
2019-05-11 17:29:30 +00:00
|
|
|
with patch('homeassistant.components.hue.config_flow.discover_nupnp',
|
2018-03-17 03:27:05 +00:00
|
|
|
side_effect=asyncio.TimeoutError):
|
|
|
|
result = await flow.async_step_init()
|
|
|
|
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_link_timeout(hass):
|
|
|
|
"""Test config flow ."""
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch('aiohue.Bridge.create_user',
|
|
|
|
side_effect=asyncio.TimeoutError):
|
|
|
|
result = await flow.async_step_link({})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
assert result['errors'] == {
|
2018-03-30 03:15:40 +00:00
|
|
|
'base': 'linking'
|
2018-03-17 03:27:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_link_button_not_pressed(hass):
|
|
|
|
"""Test config flow ."""
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch('aiohue.Bridge.create_user',
|
|
|
|
side_effect=aiohue.LinkButtonNotPressed):
|
|
|
|
result = await flow.async_step_link({})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
assert result['errors'] == {
|
|
|
|
'base': 'register_failed'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def test_flow_link_unknown_host(hass):
|
|
|
|
"""Test config flow ."""
|
2018-03-30 03:15:40 +00:00
|
|
|
flow = config_flow.HueFlowHandler()
|
2018-03-17 03:27:05 +00:00
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch('aiohue.Bridge.create_user',
|
|
|
|
side_effect=aiohue.RequestError):
|
|
|
|
result = await flow.async_step_link({})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
assert result['errors'] == {
|
2018-03-30 03:15:40 +00:00
|
|
|
'base': 'linking'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-27 02:48:27 +00:00
|
|
|
async def test_bridge_ssdp(hass):
|
2018-03-30 03:15:40 +00:00
|
|
|
"""Test a bridge being discovered."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
2019-05-27 02:48:27 +00:00
|
|
|
flow.context = {}
|
2018-03-30 03:15:40 +00:00
|
|
|
|
|
|
|
with patch.object(config_flow, 'get_bridge',
|
|
|
|
side_effect=errors.AuthenticationRequired):
|
2019-05-27 02:48:27 +00:00
|
|
|
result = await flow.async_step_ssdp({
|
2018-03-30 03:15:40 +00:00
|
|
|
'host': '0.0.0.0',
|
2019-06-03 16:26:01 +00:00
|
|
|
'serial': '1234',
|
|
|
|
'manufacturerURL': config_flow.HUE_MANUFACTURERURL
|
2018-03-30 03:15:40 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
|
|
|
|
|
2019-06-03 16:26:01 +00:00
|
|
|
async def test_bridge_ssdp_discover_other_bridge(hass):
|
|
|
|
"""Test that discovery ignores other bridges."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
result = await flow.async_step_ssdp({
|
|
|
|
'manufacturerURL': 'http://www.notphilips.com'
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
|
|
|
|
|
2019-05-27 02:48:27 +00:00
|
|
|
async def test_bridge_ssdp_emulated_hue(hass):
|
2018-03-30 03:15:40 +00:00
|
|
|
"""Test if discovery info is from an emulated hue instance."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
2019-05-27 02:48:27 +00:00
|
|
|
flow.context = {}
|
2018-03-30 03:15:40 +00:00
|
|
|
|
2019-05-27 02:48:27 +00:00
|
|
|
result = await flow.async_step_ssdp({
|
2018-03-30 03:15:40 +00:00
|
|
|
'name': 'HASS Bridge',
|
|
|
|
'host': '0.0.0.0',
|
2019-06-03 16:26:01 +00:00
|
|
|
'serial': '1234',
|
|
|
|
'manufacturerURL': config_flow.HUE_MANUFACTURERURL
|
2018-03-30 03:15:40 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
|
|
|
|
|
2019-05-27 02:48:27 +00:00
|
|
|
async def test_bridge_ssdp_already_configured(hass):
|
2018-03-30 03:15:40 +00:00
|
|
|
"""Test if a discovered bridge has already been configured."""
|
|
|
|
MockConfigEntry(domain='hue', data={
|
|
|
|
'host': '0.0.0.0'
|
|
|
|
}).add_to_hass(hass)
|
|
|
|
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
2019-05-27 02:48:27 +00:00
|
|
|
flow.context = {}
|
2018-03-30 03:15:40 +00:00
|
|
|
|
2019-05-27 02:48:27 +00:00
|
|
|
result = await flow.async_step_ssdp({
|
2018-03-30 03:15:40 +00:00
|
|
|
'host': '0.0.0.0',
|
2019-06-03 16:26:01 +00:00
|
|
|
'serial': '1234',
|
|
|
|
'manufacturerURL': config_flow.HUE_MANUFACTURERURL
|
2018-03-30 03:15:40 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_import_with_existing_config(hass):
|
|
|
|
"""Test importing a host with an existing config file."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
bridge = Mock()
|
|
|
|
bridge.username = 'username-abc'
|
|
|
|
bridge.config.bridgeid = 'bridge-id-1234'
|
|
|
|
bridge.config.name = 'Mock Bridge'
|
|
|
|
bridge.host = '0.0.0.0'
|
|
|
|
|
|
|
|
with patch.object(config_flow, '_find_username_from_config',
|
|
|
|
return_value='mock-user'), \
|
|
|
|
patch.object(config_flow, 'get_bridge',
|
|
|
|
return_value=mock_coro(bridge)):
|
|
|
|
result = await flow.async_step_import({
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
'path': 'bla.conf'
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'create_entry'
|
|
|
|
assert result['title'] == 'Mock Bridge'
|
|
|
|
assert result['data'] == {
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
'bridge_id': 'bridge-id-1234',
|
|
|
|
'username': 'username-abc'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def test_import_with_no_config(hass):
|
|
|
|
"""Test importing a host without an existing config file."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch.object(config_flow, 'get_bridge',
|
|
|
|
side_effect=errors.AuthenticationRequired):
|
|
|
|
result = await flow.async_step_import({
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_import_with_existing_but_invalid_config(hass):
|
|
|
|
"""Test importing a host with a config file with invalid username."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch.object(config_flow, '_find_username_from_config',
|
|
|
|
return_value='mock-user'), \
|
|
|
|
patch.object(config_flow, 'get_bridge',
|
|
|
|
side_effect=errors.AuthenticationRequired):
|
|
|
|
result = await flow.async_step_import({
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
'path': 'bla.conf'
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'form'
|
|
|
|
assert result['step_id'] == 'link'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_import_cannot_connect(hass):
|
|
|
|
"""Test importing a host that we cannot conncet to."""
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
with patch.object(config_flow, 'get_bridge',
|
|
|
|
side_effect=errors.CannotConnect):
|
|
|
|
result = await flow.async_step_import({
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'abort'
|
|
|
|
assert result['reason'] == 'cannot_connect'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass):
|
|
|
|
"""Test that we clean up entries for same host and bridge.
|
|
|
|
|
|
|
|
An IP can only hold a single bridge and a single bridge can only be
|
|
|
|
accessible via a single IP. So when we create a new entry, we'll remove
|
|
|
|
all existing entries that either have same IP or same bridge_id.
|
|
|
|
"""
|
|
|
|
MockConfigEntry(domain='hue', data={
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
'bridge_id': 'id-1234'
|
|
|
|
}).add_to_hass(hass)
|
|
|
|
|
|
|
|
MockConfigEntry(domain='hue', data={
|
|
|
|
'host': '1.2.3.4',
|
|
|
|
'bridge_id': 'id-1234'
|
|
|
|
}).add_to_hass(hass)
|
|
|
|
|
|
|
|
assert len(hass.config_entries.async_entries('hue')) == 2
|
|
|
|
|
|
|
|
flow = config_flow.HueFlowHandler()
|
|
|
|
flow.hass = hass
|
|
|
|
|
|
|
|
bridge = Mock()
|
|
|
|
bridge.username = 'username-abc'
|
|
|
|
bridge.config.bridgeid = 'id-1234'
|
|
|
|
bridge.config.name = 'Mock Bridge'
|
|
|
|
bridge.host = '0.0.0.0'
|
|
|
|
|
|
|
|
with patch.object(config_flow, 'get_bridge',
|
|
|
|
return_value=mock_coro(bridge)):
|
|
|
|
result = await flow.async_step_import({
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
})
|
|
|
|
|
|
|
|
assert result['type'] == 'create_entry'
|
|
|
|
assert result['title'] == 'Mock Bridge'
|
|
|
|
assert result['data'] == {
|
|
|
|
'host': '0.0.0.0',
|
|
|
|
'bridge_id': 'id-1234',
|
|
|
|
'username': 'username-abc'
|
2018-03-17 03:27:05 +00:00
|
|
|
}
|
2018-03-30 03:15:40 +00:00
|
|
|
# We did not process the result of this entry but already removed the old
|
|
|
|
# ones. So we should have 0 entries.
|
|
|
|
assert len(hass.config_entries.async_entries('hue')) == 0
|