Refactor Hue: phue -> aiohue (#13043)

* phue -> aiohue

* Clean up

* Fix config

* Address comments

* Typo

* Fix rebase error

* Mark light as unavailable when bridge is disconnected

* Tests

* Make Throttle work with double delay and async

* Rework update logic

* Don't resolve host to IP

* Clarify comment

* No longer do unnecessary updates

* Add more doc

* Another comment update

* Wrap up tests

* Lint

* Fix tests

* PyLint does not like mix 'n match async and coroutine

* Lint

* Update aiohue to 1.2

* Lint

* Fix await MagicMock
This commit is contained in:
Paulus Schoutsen 2018-03-16 20:27:05 -07:00 committed by GitHub
parent d78e75db66
commit 5a9013cda5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1289 additions and 1485 deletions

View file

@ -0,0 +1 @@
"""Tests for the Hue component."""

View file

@ -0,0 +1,17 @@
"""Fixtures for Hue tests."""
from unittest.mock import patch
import pytest
from tests.common import mock_coro_func
@pytest.fixture
def mock_bridge():
"""Mock the HueBridge from initializing."""
with patch('homeassistant.components.hue._find_username_from_config',
return_value=None), \
patch('homeassistant.components.hue.HueBridge') as mock_bridge:
mock_bridge().async_setup = mock_coro_func()
mock_bridge.reset_mock()
yield mock_bridge

View file

@ -0,0 +1,98 @@
"""Test Hue bridge."""
import asyncio
from unittest.mock import Mock, patch
import aiohue
import pytest
from homeassistant.components import hue
from tests.common import mock_coro
class MockBridge(hue.HueBridge):
"""Class that sets default for constructor."""
def __init__(self, hass, host='1.2.3.4', filename='mock-bridge.conf',
username=None, **kwargs):
"""Initialize a mock bridge."""
super().__init__(host, hass, filename, username, **kwargs)
@pytest.fixture
def mock_request():
"""Mock configurator.async_request_config."""
with patch('homeassistant.components.configurator.'
'async_request_config') as mock_request:
yield mock_request
async def test_setup_request_config_button_not_pressed(hass, mock_request):
"""Test we request config if link button has not been pressed."""
with patch('aiohue.Bridge.create_user',
side_effect=aiohue.LinkButtonNotPressed):
await MockBridge(hass).async_setup()
assert len(mock_request.mock_calls) == 1
async def test_setup_request_config_invalid_username(hass, mock_request):
"""Test we request config if username is no longer whitelisted."""
with patch('aiohue.Bridge.create_user',
side_effect=aiohue.Unauthorized):
await MockBridge(hass).async_setup()
assert len(mock_request.mock_calls) == 1
async def test_setup_timeout(hass, mock_request):
"""Test we give up when there is a timeout."""
with patch('aiohue.Bridge.create_user',
side_effect=asyncio.TimeoutError):
await MockBridge(hass).async_setup()
assert len(mock_request.mock_calls) == 0
async def test_only_create_no_username(hass):
"""."""
with patch('aiohue.Bridge.create_user') as mock_create, \
patch('aiohue.Bridge.initialize') as mock_init:
await MockBridge(hass, username='bla').async_setup()
assert len(mock_create.mock_calls) == 0
assert len(mock_init.mock_calls) == 1
async def test_configurator_callback(hass, mock_request):
"""."""
with patch('aiohue.Bridge.create_user',
side_effect=aiohue.LinkButtonNotPressed):
await MockBridge(hass).async_setup()
assert len(mock_request.mock_calls) == 1
callback = mock_request.mock_calls[0][1][2]
mock_init = Mock(return_value=mock_coro())
mock_create = Mock(return_value=mock_coro())
with patch('aiohue.Bridge') as mock_bridge, \
patch('homeassistant.helpers.discovery.async_load_platform',
return_value=mock_coro()) as mock_load_platform, \
patch('homeassistant.components.hue.save_json') as mock_save:
inst = mock_bridge()
inst.username = 'mock-user'
inst.create_user = mock_create
inst.initialize = mock_init
await callback(None)
assert len(mock_create.mock_calls) == 1
assert len(mock_init.mock_calls) == 1
assert len(mock_save.mock_calls) == 1
assert mock_save.mock_calls[0][1][1] == {
'1.2.3.4': {
'username': 'mock-user'
}
}
assert len(mock_load_platform.mock_calls) == 1

View file

@ -0,0 +1,184 @@
"""Tests for Philips Hue config flow."""
import asyncio
from unittest.mock import patch
import aiohue
import pytest
import voluptuous as vol
from homeassistant.components import hue
from tests.common import MockConfigEntry, mock_coro
async def test_flow_works(hass, aioclient_mock):
"""Test config flow ."""
aioclient_mock.get(hue.API_NUPNP, json=[
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
])
flow = hue.HueFlowHandler()
flow.hass = hass
await flow.async_step_init()
with patch('aiohue.Bridge') as mock_bridge:
def mock_constructor(host, websession):
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."""
aioclient_mock.get(hue.API_NUPNP, json=[])
flow = hue.HueFlowHandler()
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."""
aioclient_mock.get(hue.API_NUPNP, json=[
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
])
MockConfigEntry(domain='hue', data={
'host': '1.2.3.4'
}).add_to_hass(hass)
flow = hue.HueFlowHandler()
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(hue.API_NUPNP, json=[
{'internalipaddress': '1.2.3.4', 'id': 'bla'}
])
flow = hue.HueFlowHandler()
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(hue.API_NUPNP, json=[
{'internalipaddress': '1.2.3.4', 'id': 'bla'},
{'internalipaddress': '5.6.7.8', 'id': 'beer'}
])
flow = hue.HueFlowHandler()
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."""
aioclient_mock.get(hue.API_NUPNP, json=[
{'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)
flow = hue.HueFlowHandler()
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 ."""
flow = hue.HueFlowHandler()
flow.hass = hass
with patch('aiohue.discovery.discover_nupnp',
side_effect=asyncio.TimeoutError):
result = await flow.async_step_init()
assert result['type'] == 'abort'
async def test_flow_link_timeout(hass):
"""Test config flow ."""
flow = hue.HueFlowHandler()
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'] == {
'base': 'register_failed'
}
async def test_flow_link_button_not_pressed(hass):
"""Test config flow ."""
flow = hue.HueFlowHandler()
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 ."""
flow = hue.HueFlowHandler()
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'] == {
'base': 'register_failed'
}

View file

@ -0,0 +1,74 @@
"""Test Hue setup process."""
from homeassistant.setup import async_setup_component
from homeassistant.components import hue
from homeassistant.components.discovery import SERVICE_HUE
async def test_setup_with_multiple_hosts(hass, mock_bridge):
"""Multiple hosts specified in the config file."""
assert await async_setup_component(hass, hue.DOMAIN, {
hue.DOMAIN: {
hue.CONF_BRIDGES: [
{hue.CONF_HOST: '127.0.0.1'},
{hue.CONF_HOST: '192.168.1.10'},
]
}
})
assert len(mock_bridge.mock_calls) == 2
hosts = sorted(mock_call[1][0] for mock_call in mock_bridge.mock_calls)
assert hosts == ['127.0.0.1', '192.168.1.10']
assert len(hass.data[hue.DOMAIN]) == 2
async def test_bridge_discovered(hass, mock_bridge):
"""Bridge discovery."""
assert await async_setup_component(hass, hue.DOMAIN, {})
await hass.helpers.discovery.async_discover(SERVICE_HUE, {
'host': '192.168.1.10',
'serial': '1234567',
})
await hass.async_block_till_done()
assert len(mock_bridge.mock_calls) == 1
assert mock_bridge.mock_calls[0][1][0] == '192.168.1.10'
assert len(hass.data[hue.DOMAIN]) == 1
async def test_bridge_configure_and_discovered(hass, mock_bridge):
"""Bridge is in the config file, then we discover it."""
assert await async_setup_component(hass, hue.DOMAIN, {
hue.DOMAIN: {
hue.CONF_BRIDGES: {
hue.CONF_HOST: '192.168.1.10'
}
}
})
assert len(mock_bridge.mock_calls) == 1
assert mock_bridge.mock_calls[0][1][0] == '192.168.1.10'
assert len(hass.data[hue.DOMAIN]) == 1
mock_bridge.reset_mock()
await hass.helpers.discovery.async_discover(SERVICE_HUE, {
'host': '192.168.1.10',
'serial': '1234567',
})
await hass.async_block_till_done()
assert len(mock_bridge.mock_calls) == 0
assert len(hass.data[hue.DOMAIN]) == 1
async def test_setup_no_host(hass, aioclient_mock):
"""Check we call discovery if domain specified but no bridges."""
aioclient_mock.get(hue.API_NUPNP, json=[])
result = await async_setup_component(
hass, hue.DOMAIN, {hue.DOMAIN: {}})
assert result
assert len(aioclient_mock.mock_calls) == 1
assert len(hass.data[hue.DOMAIN]) == 0