Enhance SmartThings component subscription (#21124)
* Move to config v2 to store SmartApp oauth keys * Add migration functionality. * Regenerate refresh token on periodic basis * Fix regenerate and misc. optimizations * Review feedback * Subscription sync logic now performs a difference operation * Removed config entry reloading.
This commit is contained in:
parent
d9712027e8
commit
8b38b82e73
14 changed files with 529 additions and 275 deletions
|
@ -8,14 +8,33 @@ import pytest
|
|||
|
||||
from homeassistant.components import smartthings
|
||||
from homeassistant.components.smartthings.const import (
|
||||
DATA_BROKERS, DOMAIN, EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE,
|
||||
SUPPORTED_PLATFORMS)
|
||||
CONF_INSTALLED_APP_ID, CONF_REFRESH_TOKEN, DATA_BROKERS, DOMAIN,
|
||||
EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from tests.common import mock_coro
|
||||
|
||||
|
||||
async def test_migration_creates_new_flow(
|
||||
hass, smartthings_mock, config_entry):
|
||||
"""Test migration deletes app and creates new flow."""
|
||||
config_entry.version = 1
|
||||
setattr(hass.config_entries, '_entries', [config_entry])
|
||||
api = smartthings_mock.return_value
|
||||
api.delete_installed_app.return_value = mock_coro()
|
||||
|
||||
await smartthings.async_migrate_entry(hass, config_entry)
|
||||
|
||||
assert api.delete_installed_app.call_count == 1
|
||||
await hass.async_block_till_done()
|
||||
assert not hass.config_entries.async_entries(DOMAIN)
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]['handler'] == 'smartthings'
|
||||
assert flows[0]['context'] == {'source': 'import'}
|
||||
|
||||
|
||||
async def test_unrecoverable_api_errors_create_new_flow(
|
||||
hass, config_entry, smartthings_mock):
|
||||
"""
|
||||
|
@ -101,14 +120,22 @@ async def test_unauthorized_installed_app_raises_not_ready(
|
|||
|
||||
async def test_config_entry_loads_platforms(
|
||||
hass, config_entry, app, installed_app,
|
||||
device, smartthings_mock):
|
||||
device, smartthings_mock, subscription_factory):
|
||||
"""Test config entry loads properly and proxies to platforms."""
|
||||
setattr(hass.config_entries, '_entries', [config_entry])
|
||||
|
||||
api = smartthings_mock.return_value
|
||||
api.app.return_value = mock_coro(return_value=app)
|
||||
api.installed_app.return_value = mock_coro(return_value=installed_app)
|
||||
api.devices.return_value = mock_coro(return_value=[device])
|
||||
api.devices.side_effect = \
|
||||
lambda *args, **kwargs: mock_coro(return_value=[device])
|
||||
mock_token = Mock()
|
||||
mock_token.access_token.return_value = str(uuid4())
|
||||
mock_token.refresh_token.return_value = str(uuid4())
|
||||
api.generate_tokens.return_value = mock_coro(return_value=mock_token)
|
||||
subscriptions = [subscription_factory(capability)
|
||||
for capability in device.capabilities]
|
||||
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
|
||||
|
||||
with patch.object(hass.config_entries, 'async_forward_entry_setup',
|
||||
return_value=mock_coro()) as forward_mock:
|
||||
|
@ -120,8 +147,12 @@ async def test_config_entry_loads_platforms(
|
|||
|
||||
async def test_unload_entry(hass, config_entry):
|
||||
"""Test entries are unloaded correctly."""
|
||||
broker = Mock()
|
||||
broker.event_handler_disconnect = Mock()
|
||||
connect_disconnect = Mock()
|
||||
smart_app = Mock()
|
||||
smart_app.connect_event.return_value = connect_disconnect
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, config_entry, Mock(), smart_app, [])
|
||||
broker.connect()
|
||||
hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] = broker
|
||||
|
||||
with patch.object(hass.config_entries, 'async_forward_entry_unload',
|
||||
|
@ -129,15 +160,41 @@ async def test_unload_entry(hass, config_entry):
|
|||
return_value=True
|
||||
)) as forward_mock:
|
||||
assert await smartthings.async_unload_entry(hass, config_entry)
|
||||
assert broker.event_handler_disconnect.call_count == 1
|
||||
|
||||
assert connect_disconnect.call_count == 1
|
||||
assert config_entry.entry_id not in hass.data[DOMAIN][DATA_BROKERS]
|
||||
# Assert platforms unloaded
|
||||
await hass.async_block_till_done()
|
||||
assert forward_mock.call_count == len(SUPPORTED_PLATFORMS)
|
||||
|
||||
|
||||
async def test_broker_regenerates_token(
|
||||
hass, config_entry):
|
||||
"""Test the device broker regenerates the refresh token."""
|
||||
token = Mock()
|
||||
token.refresh_token = str(uuid4())
|
||||
token.refresh.return_value = mock_coro()
|
||||
stored_action = None
|
||||
|
||||
def async_track_time_interval(hass, action, interval):
|
||||
nonlocal stored_action
|
||||
stored_action = action
|
||||
|
||||
with patch('homeassistant.components.smartthings'
|
||||
'.async_track_time_interval',
|
||||
new=async_track_time_interval):
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, config_entry, token, Mock(), [])
|
||||
broker.connect()
|
||||
|
||||
assert stored_action
|
||||
await stored_action(None) # pylint:disable=not-callable
|
||||
assert token.refresh.call_count == 1
|
||||
assert config_entry.data[CONF_REFRESH_TOKEN] == token.refresh_token
|
||||
|
||||
|
||||
async def test_event_handler_dispatches_updated_devices(
|
||||
hass, device_factory, event_request_factory):
|
||||
hass, config_entry, device_factory, event_request_factory):
|
||||
"""Test the event handler dispatches updated devices."""
|
||||
devices = [
|
||||
device_factory('Bedroom 1 Switch', ['switch']),
|
||||
|
@ -147,6 +204,7 @@ async def test_event_handler_dispatches_updated_devices(
|
|||
device_ids = [devices[0].device_id, devices[1].device_id,
|
||||
devices[2].device_id]
|
||||
request = event_request_factory(device_ids)
|
||||
config_entry.data[CONF_INSTALLED_APP_ID] = request.installed_app_id
|
||||
called = False
|
||||
|
||||
def signal(ids):
|
||||
|
@ -154,10 +212,13 @@ async def test_event_handler_dispatches_updated_devices(
|
|||
called = True
|
||||
assert device_ids == ids
|
||||
async_dispatcher_connect(hass, SIGNAL_SMARTTHINGS_UPDATE, signal)
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, devices, request.installed_app_id)
|
||||
|
||||
await broker.event_handler(request, None, None)
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, config_entry, Mock(), Mock(), devices)
|
||||
broker.connect()
|
||||
|
||||
# pylint:disable=protected-access
|
||||
await broker._event_handler(request, None, None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called
|
||||
|
@ -166,7 +227,7 @@ async def test_event_handler_dispatches_updated_devices(
|
|||
|
||||
|
||||
async def test_event_handler_ignores_other_installed_app(
|
||||
hass, device_factory, event_request_factory):
|
||||
hass, config_entry, device_factory, event_request_factory):
|
||||
"""Test the event handler dispatches updated devices."""
|
||||
device = device_factory('Bedroom 1 Switch', ['switch'])
|
||||
request = event_request_factory([device.device_id])
|
||||
|
@ -176,21 +237,26 @@ async def test_event_handler_ignores_other_installed_app(
|
|||
nonlocal called
|
||||
called = True
|
||||
async_dispatcher_connect(hass, SIGNAL_SMARTTHINGS_UPDATE, signal)
|
||||
broker = smartthings.DeviceBroker(hass, [device], str(uuid4()))
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, config_entry, Mock(), Mock(), [device])
|
||||
broker.connect()
|
||||
|
||||
await broker.event_handler(request, None, None)
|
||||
# pylint:disable=protected-access
|
||||
await broker._event_handler(request, None, None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not called
|
||||
|
||||
|
||||
async def test_event_handler_fires_button_events(
|
||||
hass, device_factory, event_factory, event_request_factory):
|
||||
hass, config_entry, device_factory, event_factory,
|
||||
event_request_factory):
|
||||
"""Test the event handler fires button events."""
|
||||
device = device_factory('Button 1', ['button'])
|
||||
event = event_factory(device.device_id, capability='button',
|
||||
attribute='button', value='pushed')
|
||||
request = event_request_factory(events=[event])
|
||||
config_entry.data[CONF_INSTALLED_APP_ID] = request.installed_app_id
|
||||
called = False
|
||||
|
||||
def handler(evt):
|
||||
|
@ -205,8 +271,11 @@ async def test_event_handler_fires_button_events(
|
|||
}
|
||||
hass.bus.async_listen(EVENT_BUTTON, handler)
|
||||
broker = smartthings.DeviceBroker(
|
||||
hass, [device], request.installed_app_id)
|
||||
await broker.event_handler(request, None, None)
|
||||
hass, config_entry, Mock(), Mock(), [device])
|
||||
broker.connect()
|
||||
|
||||
# pylint:disable=protected-access
|
||||
await broker._event_handler(request, None, None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert called
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue