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:
1 parent
d9712027e8
commit
8b38b82e73
14 files changed
+529
-275
No files matched your search
@@ -5,7 +5,9 @@ from uuid import uuid4
|
||||
from pysmartthings import AppEntity, Capability
|
||||
|
||||
from homeassistant.components.smartthings import smartapp
|
||||
from homeassistant.components.smartthings.const import DATA_MANAGER, DOMAIN
|
||||
from homeassistant.components.smartthings.const import (
|
||||
CONF_INSTALLED_APP_ID, CONF_INSTALLED_APPS, CONF_LOCATION_ID,
|
||||
CONF_REFRESH_TOKEN, DATA_MANAGER, DOMAIN)
|
||||
|
||||
from tests.common import mock_coro
|
||||
|
||||
@@ -35,31 +37,26 @@ async def test_update_app_updated_needed(hass, app):
|
||||
assert mock_app.classifications == app.classifications
|
||||
|
||||
|
||||
async def test_smartapp_install_abort_if_no_other(
|
||||
async def test_smartapp_install_store_if_no_other(
|
||||
hass, smartthings_mock, device_factory):
|
||||
"""Test aborts if no other app was configured already."""
|
||||
# Arrange
|
||||
api = smartthings_mock.return_value
|
||||
api.create_subscription.return_value = mock_coro()
|
||||
app = Mock()
|
||||
app.app_id = uuid4()
|
||||
request = Mock()
|
||||
request.installed_app_id = uuid4()
|
||||
request.auth_token = uuid4()
|
||||
request.location_id = uuid4()
|
||||
devices = [
|
||||
device_factory('', [Capability.battery, 'ping']),
|
||||
device_factory('', [Capability.switch, Capability.switch_level]),
|
||||
device_factory('', [Capability.switch])
|
||||
]
|
||||
api.devices = Mock()
|
||||
api.devices.return_value = mock_coro(return_value=devices)
|
||||
request.installed_app_id = str(uuid4())
|
||||
request.auth_token = str(uuid4())
|
||||
request.location_id = str(uuid4())
|
||||
request.refresh_token = str(uuid4())
|
||||
# Act
|
||||
await smartapp.smartapp_install(hass, request, None, app)
|
||||
# Assert
|
||||
entries = hass.config_entries.async_entries('smartthings')
|
||||
assert not entries
|
||||
assert api.create_subscription.call_count == 3
|
||||
data = hass.data[DOMAIN][CONF_INSTALLED_APPS][0]
|
||||
assert data[CONF_REFRESH_TOKEN] == request.refresh_token
|
||||
assert data[CONF_LOCATION_ID] == request.location_id
|
||||
assert data[CONF_INSTALLED_APP_ID] == request.installed_app_id
|
||||
|
||||
|
||||
async def test_smartapp_install_creates_flow(
|
||||
@@ -68,12 +65,12 @@ async def test_smartapp_install_creates_flow(
|
||||
# Arrange
|
||||
setattr(hass.config_entries, '_entries', [config_entry])
|
||||
api = smartthings_mock.return_value
|
||||
api.create_subscription.return_value = mock_coro()
|
||||
app = Mock()
|
||||
app.app_id = config_entry.data['app_id']
|
||||
request = Mock()
|
||||
request.installed_app_id = str(uuid4())
|
||||
request.auth_token = str(uuid4())
|
||||
request.refresh_token = str(uuid4())
|
||||
request.location_id = location.location_id
|
||||
devices = [
|
||||
device_factory('', [Capability.battery, 'ping']),
|
||||
@@ -88,42 +85,42 @@ async def test_smartapp_install_creates_flow(
|
||||
await hass.async_block_till_done()
|
||||
entries = hass.config_entries.async_entries('smartthings')
|
||||
assert len(entries) == 2
|
||||
assert api.create_subscription.call_count == 3
|
||||
assert entries[1].data['app_id'] == app.app_id
|
||||
assert entries[1].data['installed_app_id'] == request.installed_app_id
|
||||
assert entries[1].data['location_id'] == request.location_id
|
||||
assert entries[1].data['access_token'] == \
|
||||
config_entry.data['access_token']
|
||||
assert entries[1].data['refresh_token'] == request.refresh_token
|
||||
assert entries[1].data['client_secret'] == \
|
||||
config_entry.data['client_secret']
|
||||
assert entries[1].data['client_id'] == config_entry.data['client_id']
|
||||
assert entries[1].title == location.name
|
||||
|
||||
|
||||
async def test_smartapp_update_syncs_subs(
|
||||
hass, smartthings_mock, config_entry, location, device_factory):
|
||||
"""Test update synchronizes subscriptions."""
|
||||
async def test_smartapp_update_saves_token(
|
||||
hass, smartthings_mock, location, device_factory):
|
||||
"""Test update saves token."""
|
||||
# Arrange
|
||||
setattr(hass.config_entries, '_entries', [config_entry])
|
||||
entry = Mock()
|
||||
entry.data = {
|
||||
'installed_app_id': str(uuid4()),
|
||||
'app_id': str(uuid4())
|
||||
}
|
||||
entry.domain = DOMAIN
|
||||
|
||||
setattr(hass.config_entries, '_entries', [entry])
|
||||
app = Mock()
|
||||
app.app_id = config_entry.data['app_id']
|
||||
api = smartthings_mock.return_value
|
||||
api.delete_subscriptions = Mock()
|
||||
api.delete_subscriptions.return_value = mock_coro()
|
||||
api.create_subscription.return_value = mock_coro()
|
||||
app.app_id = entry.data['app_id']
|
||||
request = Mock()
|
||||
request.installed_app_id = str(uuid4())
|
||||
request.installed_app_id = entry.data['installed_app_id']
|
||||
request.auth_token = str(uuid4())
|
||||
request.refresh_token = str(uuid4())
|
||||
request.location_id = location.location_id
|
||||
devices = [
|
||||
device_factory('', [Capability.battery, 'ping']),
|
||||
device_factory('', [Capability.switch, Capability.switch_level]),
|
||||
device_factory('', [Capability.switch])
|
||||
]
|
||||
api.devices = Mock()
|
||||
api.devices.return_value = mock_coro(return_value=devices)
|
||||
|
||||
# Act
|
||||
await smartapp.smartapp_update(hass, request, None, app)
|
||||
# Assert
|
||||
assert api.create_subscription.call_count == 3
|
||||
assert api.delete_subscriptions.call_count == 1
|
||||
assert entry.data[CONF_REFRESH_TOKEN] == request.refresh_token
|
||||
|
||||
|
||||
async def test_smartapp_uninstall(hass, config_entry):
|
||||
@@ -152,3 +149,83 @@ async def test_smartapp_webhook(hass):
|
||||
result = await smartapp.smartapp_webhook(hass, '', request)
|
||||
|
||||
assert result.body == b'{}'
|
||||
|
||||
|
||||
async def test_smartapp_sync_subscriptions(
|
||||
hass, smartthings_mock, device_factory, subscription_factory):
|
||||
"""Test synchronization adds and removes."""
|
||||
api = smartthings_mock.return_value
|
||||
api.delete_subscription.side_effect = lambda loc_id, sub_id: mock_coro()
|
||||
api.create_subscription.side_effect = lambda sub: mock_coro()
|
||||
subscriptions = [
|
||||
subscription_factory(Capability.thermostat),
|
||||
subscription_factory(Capability.switch),
|
||||
subscription_factory(Capability.switch_level)
|
||||
]
|
||||
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
|
||||
devices = [
|
||||
device_factory('', [Capability.battery, 'ping']),
|
||||
device_factory('', [Capability.switch, Capability.switch_level]),
|
||||
device_factory('', [Capability.switch])
|
||||
]
|
||||
|
||||
await smartapp.smartapp_sync_subscriptions(
|
||||
hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)
|
||||
|
||||
assert api.subscriptions.call_count == 1
|
||||
assert api.delete_subscription.call_count == 1
|
||||
assert api.create_subscription.call_count == 1
|
||||
|
||||
|
||||
async def test_smartapp_sync_subscriptions_up_to_date(
|
||||
hass, smartthings_mock, device_factory, subscription_factory):
|
||||
"""Test synchronization does nothing when current."""
|
||||
api = smartthings_mock.return_value
|
||||
api.delete_subscription.side_effect = lambda loc_id, sub_id: mock_coro()
|
||||
api.create_subscription.side_effect = lambda sub: mock_coro()
|
||||
subscriptions = [
|
||||
subscription_factory(Capability.battery),
|
||||
subscription_factory(Capability.switch),
|
||||
subscription_factory(Capability.switch_level)
|
||||
]
|
||||
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
|
||||
devices = [
|
||||
device_factory('', [Capability.battery, 'ping']),
|
||||
device_factory('', [Capability.switch, Capability.switch_level]),
|
||||
device_factory('', [Capability.switch])
|
||||
]
|
||||
|
||||
await smartapp.smartapp_sync_subscriptions(
|
||||
hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)
|
||||
|
||||
assert api.subscriptions.call_count == 1
|
||||
assert api.delete_subscription.call_count == 0
|
||||
assert api.create_subscription.call_count == 0
|
||||
|
||||
|
||||
async def test_smartapp_sync_subscriptions_handles_exceptions(
|
||||
hass, smartthings_mock, device_factory, subscription_factory):
|
||||
"""Test synchronization does nothing when current."""
|
||||
api = smartthings_mock.return_value
|
||||
api.delete_subscription.side_effect = \
|
||||
lambda loc_id, sub_id: mock_coro(exception=Exception)
|
||||
api.create_subscription.side_effect = \
|
||||
lambda sub: mock_coro(exception=Exception)
|
||||
subscriptions = [
|
||||
subscription_factory(Capability.battery),
|
||||
subscription_factory(Capability.switch),
|
||||
subscription_factory(Capability.switch_level)
|
||||
]
|
||||
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
|
||||
devices = [
|
||||
device_factory('', [Capability.thermostat, 'ping']),
|
||||
device_factory('', [Capability.switch, Capability.switch_level]),
|
||||
device_factory('', [Capability.switch])
|
||||
]
|
||||
|
||||
await smartapp.smartapp_sync_subscriptions(
|
||||
hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)
|
||||
|
||||
assert api.subscriptions.call_count == 1
|
||||
assert api.delete_subscription.call_count == 1
|
||||
assert api.create_subscription.call_count == 1
|
||||
Reference in new issue
Block a user