Improve SmartThings test mocking (#25028)

* Migrate to asynctest

* Simplify mock access

* Use mocks
This commit is contained in:
Andrew Sayre 2019-07-08 22:39:55 -04:00 committed by GitHub
parent 2fbbcafaed
commit a31e49c857
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 336 deletions

View file

@ -1,12 +1,11 @@
"""Test configuration and mocks for the SmartThings component."""
from collections import defaultdict
from unittest.mock import Mock, patch
from uuid import uuid4
from asynctest import Mock, patch
from pysmartthings import (
CLASSIFICATION_AUTOMATION, AppEntity, AppOAuthClient, AppSettings,
DeviceEntity, InstalledApp, Location, SceneEntity, SmartThings,
Subscription)
DeviceEntity, DeviceStatus, InstalledApp, InstalledAppStatus,
InstalledAppType, Location, SceneEntity, SmartThings, Subscription)
from pysmartthings.api import Api
import pytest
@ -22,8 +21,6 @@ from homeassistant.config_entries import (
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_WEBHOOK_ID
from homeassistant.setup import async_setup_component
from tests.common import mock_coro
COMPONENT_PREFIX = "homeassistant.components.smartthings."
@ -58,11 +55,9 @@ async def setup_component(hass, config_file, hass_storage):
def _create_location():
loc = Location()
loc.apply_data({
'name': 'Test Location',
'locationId': str(uuid4())
})
loc = Mock(Location)
loc.name = 'Test Location'
loc.location_id = str(uuid4())
return loc
@ -81,58 +76,50 @@ def locations_fixture(location):
@pytest.fixture(name="app")
def app_fixture(hass, config_file):
"""Fixture for a single app."""
app = AppEntity(Mock())
app.apply_data({
'appName': APP_NAME_PREFIX + str(uuid4()),
'appId': str(uuid4()),
'appType': 'WEBHOOK_SMART_APP',
'classifications': [CLASSIFICATION_AUTOMATION],
'displayName': 'Home Assistant',
'description':
hass.config.location_name + " at " + hass.config.api.base_url,
'singleInstance': True,
'webhookSmartApp': {
'targetUrl': webhook.async_generate_url(
hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]),
'publicKey': ''}
})
app.refresh = Mock()
app.refresh.return_value = mock_coro()
app.save = Mock()
app.save.return_value = mock_coro()
settings = AppSettings(app.app_id)
settings.settings[SETTINGS_INSTANCE_ID] = config_file[CONF_INSTANCE_ID]
app.settings = Mock()
app.settings.return_value = mock_coro(return_value=settings)
app = Mock(AppEntity)
app.app_name = APP_NAME_PREFIX + str(uuid4())
app.app_id = str(uuid4())
app.app_type = 'WEBHOOK_SMART_APP'
app.classifications = [CLASSIFICATION_AUTOMATION]
app.display_name = 'Home Assistant'
app.description = hass.config.location_name + " at " + \
hass.config.api.base_url
app.single_instance = True
app.webhook_target_url = webhook.async_generate_url(
hass, hass.data[DOMAIN][CONF_WEBHOOK_ID])
settings = Mock(AppSettings)
settings.app_id = app.app_id
settings.settings = {SETTINGS_INSTANCE_ID: config_file[CONF_INSTANCE_ID]}
app.settings.return_value = settings
return app
@pytest.fixture(name="app_oauth_client")
def app_oauth_client_fixture():
"""Fixture for a single app's oauth."""
return AppOAuthClient({
'oauthClientId': str(uuid4()),
'oauthClientSecret': str(uuid4())
})
client = Mock(AppOAuthClient)
client.client_id = str(uuid4())
client.client_secret = str(uuid4())
return client
@pytest.fixture(name='app_settings')
def app_settings_fixture(app, config_file):
"""Fixture for an app settings."""
settings = AppSettings(app.app_id)
settings.settings[SETTINGS_INSTANCE_ID] = config_file[CONF_INSTANCE_ID]
settings = Mock(AppSettings)
settings.app_id = app.app_id
settings.settings = {SETTINGS_INSTANCE_ID: config_file[CONF_INSTANCE_ID]}
return settings
def _create_installed_app(location_id, app_id):
item = InstalledApp()
item.apply_data(defaultdict(str, {
'installedAppId': str(uuid4()),
'installedAppStatus': 'AUTHORIZED',
'installedAppType': 'UNKNOWN',
'appId': app_id,
'locationId': location_id
}))
item = Mock(InstalledApp)
item.installed_app_id = str(uuid4())
item.installed_app_status = InstalledAppStatus.AUTHORIZED
item.installed_app_type = InstalledAppType.WEBHOOK_SMART_APP
item.app_id = app_id
item.location_id = location_id
return item
@ -161,10 +148,9 @@ def config_file_fixture():
@pytest.fixture(name='smartthings_mock')
def smartthings_mock_fixture(locations):
"""Fixture to mock smartthings API calls."""
def _location(location_id):
return mock_coro(
return_value=next(location for location in locations
if location.location_id == location_id))
async def _location(location_id):
return next(location for location in locations
if location.location_id == location_id)
smartthings_mock = Mock(SmartThings)
smartthings_mock.location.side_effect = _location
@ -172,71 +158,23 @@ def smartthings_mock_fixture(locations):
with patch(COMPONENT_PREFIX + "SmartThings", new=mock), \
patch(COMPONENT_PREFIX + "config_flow.SmartThings", new=mock), \
patch(COMPONENT_PREFIX + "smartapp.SmartThings", new=mock):
yield mock
yield smartthings_mock
@pytest.fixture(name='device')
def device_fixture(location):
"""Fixture representing devices loaded."""
item = DeviceEntity(None)
item.status.refresh = Mock()
item.status.refresh.return_value = mock_coro()
item.apply_data({
"deviceId": "743de49f-036f-4e9c-839a-2f89d57607db",
"name": "GE In-Wall Smart Dimmer",
"label": "Front Porch Lights",
"deviceManufacturerCode": "0063-4944-3038",
"locationId": location.location_id,
"deviceTypeId": "8a9d4b1e3b9b1fe3013b9b206a7f000d",
"deviceTypeName": "Dimmer Switch",
"deviceNetworkType": "ZWAVE",
"components": [
{
"id": "main",
"capabilities": [
{
"id": "switch",
"version": 1
},
{
"id": "switchLevel",
"version": 1
},
{
"id": "refresh",
"version": 1
},
{
"id": "indicator",
"version": 1
},
{
"id": "sensor",
"version": 1
},
{
"id": "actuator",
"version": 1
},
{
"id": "healthCheck",
"version": 1
},
{
"id": "light",
"version": 1
}
]
}
],
"dth": {
"deviceTypeId": "8a9d4b1e3b9b1fe3013b9b206a7f000d",
"deviceTypeName": "Dimmer Switch",
"deviceNetworkType": "ZWAVE",
"completedSetup": False
},
"type": "DTH"
})
item = Mock(DeviceEntity)
item.device_id = "743de49f-036f-4e9c-839a-2f89d57607db"
item.name = "GE In-Wall Smart Dimmer"
item.label = "Front Porch Lights"
item.location_id = location.location_id
item.capabilities = [
"switch", "switchLevel", "refresh", "indicator", "sensor", "actuator",
"healthCheck", "light"
]
item.components = {"main": item.capabilities}
item.status = Mock(DeviceStatus)
return item
@ -269,9 +207,8 @@ def subscription_factory_fixture():
@pytest.fixture(name="device_factory")
def device_factory_fixture():
"""Fixture for creating mock devices."""
api = Mock(spec=Api)
api.post_device_command.side_effect = \
lambda *args, **kwargs: mock_coro(return_value={})
api = Mock(Api)
api.post_device_command.return_value = {}
def _factory(label, capabilities, status: dict = None):
device_data = {
@ -308,19 +245,12 @@ def device_factory_fixture():
@pytest.fixture(name="scene_factory")
def scene_factory_fixture(location):
"""Fixture for creating mock devices."""
api = Mock(spec=Api)
api.execute_scene.side_effect = \
lambda *args, **kwargs: mock_coro(return_value={})
def _factory(name):
scene_data = {
'sceneId': str(uuid4()),
'sceneName': name,
'sceneIcon': '',
'sceneColor': '',
'locationId': location.location_id
}
return SceneEntity(api, scene_data)
scene = Mock(SceneEntity)
scene.scene_id = str(uuid4())
scene.name = name
scene.location_id = location.location_id
return scene
return _factory

View file

@ -1,8 +1,8 @@
"""Tests for the SmartThings config flow module."""
from unittest.mock import Mock, patch
from uuid import uuid4
from aiohttp import ClientResponseError
from asynctest import Mock, patch
from pysmartthings import APIResponseError
from homeassistant import data_entry_flow
@ -15,8 +15,6 @@ from homeassistant.components.smartthings.const import (
CONF_REFRESH_TOKEN, DOMAIN)
from homeassistant.config_entries import ConfigEntry
from tests.common import mock_coro
async def test_step_user(hass):
"""Test the access token form is shown for a user initiated flow."""
@ -84,8 +82,8 @@ async def test_token_unauthorized(hass, smartthings_mock):
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=401))
smartthings_mock.apps.side_effect = \
ClientResponseError(None, None, status=401)
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -99,8 +97,8 @@ async def test_token_forbidden(hass, smartthings_mock):
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=403))
smartthings_mock.apps.side_effect = \
ClientResponseError(None, None, status=403)
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -118,8 +116,7 @@ async def test_webhook_error(hass, smartthings_mock):
error = APIResponseError(None, None, data=data, status=422)
error.is_target_error = Mock(return_value=True)
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=error)
smartthings_mock.apps.side_effect = error
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -136,8 +133,7 @@ async def test_api_error(hass, smartthings_mock):
data = {'error': {}}
error = APIResponseError(None, None, data=data, status=400)
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=error)
smartthings_mock.apps.side_effect = error
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -151,8 +147,8 @@ async def test_unknown_api_error(hass, smartthings_mock):
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=ClientResponseError(None, None, status=404))
smartthings_mock.apps.side_effect = \
ClientResponseError(None, None, status=404)
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -166,8 +162,7 @@ async def test_unknown_error(hass, smartthings_mock):
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings_mock.return_value.apps.return_value = mock_coro(
exception=Exception('Unknown error'))
smartthings_mock.apps.side_effect = Exception('Unknown error')
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -182,12 +177,8 @@ async def test_app_created_then_show_wait_form(
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings = smartthings_mock.return_value
smartthings.apps.return_value = mock_coro(return_value=[])
smartthings.create_app.return_value = \
mock_coro(return_value=(app, app_oauth_client))
smartthings.update_app_settings.return_value = mock_coro()
smartthings.update_app_oauth.return_value = mock_coro()
smartthings_mock.apps.return_value = []
smartthings_mock.create_app.return_value = (app, app_oauth_client)
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -201,24 +192,17 @@ async def test_cloudhook_app_created_then_show_wait_form(
# Unload the endpoint so we can reload it under the cloud.
await smartapp.unload_smartapp_endpoint(hass)
mock_async_active_subscription = Mock(return_value=True)
mock_create_cloudhook = Mock(return_value=mock_coro(
return_value="http://cloud.test"))
with patch.object(cloud, 'async_active_subscription',
new=mock_async_active_subscription), \
patch.object(cloud, 'async_create_cloudhook',
new=mock_create_cloudhook):
with patch.object(cloud, 'async_active_subscription', return_value=True), \
patch.object(
cloud, 'async_create_cloudhook',
return_value='http://cloud.test') as mock_create_cloudhook:
await smartapp.setup_smartapp_endpoint(hass)
flow = SmartThingsFlowHandler()
flow.hass = hass
smartthings = smartthings_mock.return_value
smartthings.apps.return_value = mock_coro(return_value=[])
smartthings.create_app.return_value = \
mock_coro(return_value=(app, app_oauth_client))
smartthings.update_app_settings.return_value = mock_coro()
smartthings.update_app_oauth.return_value = mock_coro()
smartthings_mock.apps.return_value = []
smartthings_mock.create_app.return_value = (app, app_oauth_client)
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -233,10 +217,8 @@ async def test_app_updated_then_show_wait_form(
flow = SmartThingsFlowHandler()
flow.hass = hass
api = smartthings_mock.return_value
api.apps.return_value = mock_coro(return_value=[app])
api.generate_app_oauth.return_value = \
mock_coro(return_value=app_oauth_client)
smartthings_mock.apps.return_value = [app]
smartthings_mock.generate_app_oauth.return_value = app_oauth_client
result = await flow.async_step_user({'access_token': str(uuid4())})
@ -275,7 +257,7 @@ async def test_config_entry_created_when_installed(
flow.hass = hass
flow.access_token = str(uuid4())
flow.app_id = installed_app.app_id
flow.api = smartthings_mock.return_value
flow.api = smartthings_mock
flow.oauth_client_id = str(uuid4())
flow.oauth_client_secret = str(uuid4())
data = {
@ -307,7 +289,7 @@ async def test_multiple_config_entry_created_when_installed(
flow.hass = hass
flow.access_token = str(uuid4())
flow.app_id = app.app_id
flow.api = smartthings_mock.return_value
flow.api = smartthings_mock
flow.oauth_client_id = str(uuid4())
flow.oauth_client_secret = str(uuid4())
for installed_app in installed_apps:

View file

@ -1,9 +1,9 @@
"""Tests for the SmartThings component init module."""
from unittest.mock import Mock, patch
from uuid import uuid4
from aiohttp import ClientConnectionError, ClientResponseError
from pysmartthings import InstalledAppStatus
from asynctest import Mock, patch
from pysmartthings import InstalledAppStatus, OAuthToken
import pytest
from homeassistant.components import cloud, smartthings
@ -14,7 +14,7 @@ from homeassistant.components.smartthings.const import (
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from tests.common import MockConfigEntry, mock_coro
from tests.common import MockConfigEntry
async def test_migration_creates_new_flow(
@ -22,15 +22,12 @@ async def test_migration_creates_new_flow(
"""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.side_effect = lambda _: mock_coro()
api.delete_app.side_effect = lambda _: mock_coro()
await smartthings.async_migrate_entry(hass, config_entry)
await hass.async_block_till_done()
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
assert not hass.config_entries.async_entries(DOMAIN)
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
@ -47,34 +44,30 @@ async def test_unrecoverable_api_errors_create_new_flow(
403 (forbidden/not found): Occurs when the app or installed app could
not be retrieved/found (likely deleted?)
"""
api = smartthings_mock.return_value
for error_status in (401, 403):
setattr(hass.config_entries, '_entries', [config_entry])
api.app.return_value = mock_coro(
exception=ClientResponseError(None, None,
status=error_status))
setattr(hass.config_entries, '_entries', [config_entry])
smartthings_mock.app.side_effect = \
ClientResponseError(None, None, status=401)
# Assert setup returns false
result = await smartthings.async_setup_entry(hass, config_entry)
assert not result
# Assert setup returns false
result = await smartthings.async_setup_entry(hass, config_entry)
assert not result
# Assert entry was removed and new flow created
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'}
hass.config_entries.flow.async_abort(flows[0]['flow_id'])
# Assert entry was removed and new flow created
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'}
hass.config_entries.flow.async_abort(flows[0]['flow_id'])
async def test_recoverable_api_errors_raise_not_ready(
hass, config_entry, smartthings_mock):
"""Test config entry not ready raised for recoverable API errors."""
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(
exception=ClientResponseError(None, None, status=500))
smartthings_mock.app.side_effect = \
ClientResponseError(None, None, status=500)
with pytest.raises(ConfigEntryNotReady):
await smartthings.async_setup_entry(hass, config_entry)
@ -84,11 +77,10 @@ async def test_scenes_api_errors_raise_not_ready(
hass, config_entry, app, installed_app, smartthings_mock):
"""Test if scenes are unauthorized we continue to load 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.scenes.return_value = mock_coro(
exception=ClientResponseError(None, None, status=500))
smartthings_mock.app.return_value = app
smartthings_mock.installed_app.return_value = installed_app
smartthings_mock.scenes.side_effect = \
ClientResponseError(None, None, status=500)
with pytest.raises(ConfigEntryNotReady):
await smartthings.async_setup_entry(hass, config_entry)
@ -97,9 +89,7 @@ async def test_connection_errors_raise_not_ready(
hass, config_entry, smartthings_mock):
"""Test config entry not ready raised for connection errors."""
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(
exception=ClientConnectionError())
smartthings_mock.app.side_effect = ClientConnectionError()
with pytest.raises(ConfigEntryNotReady):
await smartthings.async_setup_entry(hass, config_entry)
@ -110,8 +100,7 @@ async def test_base_url_no_longer_https_does_not_load(
"""Test base_url no longer valid creates a new flow."""
hass.config.api.base_url = 'http://0.0.0.0'
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(return_value=app)
smartthings_mock.app.return_value = app
# Assert setup returns false
result = await smartthings.async_setup_entry(hass, config_entry)
@ -123,12 +112,10 @@ async def test_unauthorized_installed_app_raises_not_ready(
smartthings_mock):
"""Test config entry not ready raised when the app isn't authorized."""
setattr(hass.config_entries, '_entries', [config_entry])
setattr(installed_app, '_installed_app_status',
InstalledAppStatus.PENDING)
installed_app.installed_app_status = InstalledAppStatus.PENDING
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)
smartthings_mock.app.return_value = app
smartthings_mock.installed_app.return_value = installed_app
with pytest.raises(ConfigEntryNotReady):
await smartthings.async_setup_entry(hass, config_entry)
@ -139,23 +126,21 @@ async def test_scenes_unauthorized_loads_platforms(
device, smartthings_mock, subscription_factory):
"""Test if scenes are unauthorized we continue to load 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.side_effect = \
lambda *args, **kwargs: mock_coro(return_value=[device])
api.scenes.return_value = mock_coro(
exception=ClientResponseError(None, None, status=403))
smartthings_mock.app.return_value = app
smartthings_mock.installed_app.return_value = installed_app
smartthings_mock.devices.return_value = [device]
smartthings_mock.scenes.side_effect = \
ClientResponseError(None, None, status=403)
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)
smartthings_mock.generate_tokens.return_value = mock_token
subscriptions = [subscription_factory(capability)
for capability in device.capabilities]
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(hass.config_entries, 'async_forward_entry_setup',
return_value=mock_coro()) as forward_mock:
with patch.object(hass.config_entries,
'async_forward_entry_setup') as forward_mock:
assert await smartthings.async_setup_entry(hass, config_entry)
# Assert platforms loaded
await hass.async_block_till_done()
@ -167,22 +152,20 @@ async def test_config_entry_loads_platforms(
device, smartthings_mock, subscription_factory, scene):
"""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.side_effect = \
lambda *args, **kwargs: mock_coro(return_value=[device])
api.scenes.return_value = mock_coro(return_value=[scene])
smartthings_mock.app.return_value = app
smartthings_mock.installed_app.return_value = installed_app
smartthings_mock.devices.return_value = [device]
smartthings_mock.scenes.return_value = [scene]
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)
smartthings_mock.generate_tokens.return_value = mock_token
subscriptions = [subscription_factory(capability)
for capability in device.capabilities]
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(hass.config_entries, 'async_forward_entry_setup',
return_value=mock_coro()) as forward_mock:
with patch.object(hass.config_entries,
'async_forward_entry_setup') as forward_mock:
assert await smartthings.async_setup_entry(hass, config_entry)
# Assert platforms loaded
await hass.async_block_till_done()
@ -196,21 +179,19 @@ async def test_config_entry_loads_unconnected_cloud(
setattr(hass.config_entries, '_entries', [config_entry])
hass.data[DOMAIN][CONF_CLOUDHOOK_URL] = "https://test.cloud"
hass.config.api.base_url = 'http://0.0.0.0'
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.side_effect = \
lambda *args, **kwargs: mock_coro(return_value=[device])
api.scenes.return_value = mock_coro(return_value=[scene])
smartthings_mock.app.return_value = app
smartthings_mock.installed_app.return_value = installed_app
smartthings_mock.devices.return_value = [device]
smartthings_mock.scenes.return_value = [scene]
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)
smartthings_mock.generate_tokens.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:
smartthings_mock.subscriptions.return_value = subscriptions
with patch.object(
hass.config_entries, 'async_forward_entry_setup') as forward_mock:
assert await smartthings.async_setup_entry(hass, config_entry)
await hass.async_block_till_done()
assert forward_mock.call_count == len(SUPPORTED_PLATFORMS)
@ -227,9 +208,7 @@ async def test_unload_entry(hass, config_entry):
hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] = broker
with patch.object(hass.config_entries, 'async_forward_entry_unload',
return_value=mock_coro(
return_value=True
)) as forward_mock:
return_value=True) as forward_mock:
assert await smartthings.async_unload_entry(hass, config_entry)
assert connect_disconnect.call_count == 1
@ -241,15 +220,11 @@ async def test_unload_entry(hass, config_entry):
async def test_remove_entry(hass, config_entry, smartthings_mock):
"""Test that the installed app and app are removed up."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro()
api.delete_app.side_effect = lambda _: mock_coro()
# Act
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
async def test_remove_entry_cloudhook(hass, config_entry, smartthings_mock):
@ -257,20 +232,15 @@ async def test_remove_entry_cloudhook(hass, config_entry, smartthings_mock):
# Arrange
setattr(hass.config_entries, '_entries', [config_entry])
hass.data[DOMAIN][CONF_CLOUDHOOK_URL] = "https://test.cloud"
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro()
api.delete_app.side_effect = lambda _: mock_coro()
mock_async_is_logged_in = Mock(return_value=True)
mock_async_delete_cloudhook = Mock(return_value=mock_coro())
# Act
with patch.object(cloud, 'async_is_logged_in',
new=mock_async_is_logged_in), \
patch.object(cloud, 'async_delete_cloudhook',
new=mock_async_delete_cloudhook):
return_value=True) as mock_async_is_logged_in, \
patch.object(cloud, 'async_delete_cloudhook') \
as mock_async_delete_cloudhook:
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
assert mock_async_is_logged_in.call_count == 1
assert mock_async_delete_cloudhook.call_count == 1
@ -282,99 +252,87 @@ async def test_remove_entry_app_in_use(hass, config_entry, smartthings_mock):
data[CONF_INSTALLED_APP_ID] = str(uuid4())
entry2 = MockConfigEntry(version=2, domain=DOMAIN, data=data)
setattr(hass.config_entries, '_entries', [config_entry, entry2])
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro()
# Act
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 0
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 0
async def test_remove_entry_already_deleted(
hass, config_entry, smartthings_mock):
"""Test handles when the apps have already been removed."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro(
exception=ClientResponseError(None, None, status=403))
api.delete_app.side_effect = lambda _: mock_coro(
exception=ClientResponseError(None, None, status=403))
smartthings_mock.delete_installed_app.side_effect = ClientResponseError(
None, None, status=403)
smartthings_mock.delete_app.side_effect = ClientResponseError(
None, None, status=403)
# Act
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
async def test_remove_entry_installedapp_api_error(
hass, config_entry, smartthings_mock):
"""Test raises exceptions removing the installed app."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro(
exception=ClientResponseError(None, None, status=500))
smartthings_mock.delete_installed_app.side_effect = \
ClientResponseError(None, None, status=500)
# Act
with pytest.raises(ClientResponseError):
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 0
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 0
async def test_remove_entry_installedapp_unknown_error(
hass, config_entry, smartthings_mock):
"""Test raises exceptions removing the installed app."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro(
exception=Exception)
smartthings_mock.delete_installed_app.side_effect = Exception
# Act
with pytest.raises(Exception):
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 0
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 0
async def test_remove_entry_app_api_error(
hass, config_entry, smartthings_mock):
"""Test raises exceptions removing the app."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro()
api.delete_app.side_effect = lambda _: mock_coro(
exception=ClientResponseError(None, None, status=500))
smartthings_mock.delete_app.side_effect = \
ClientResponseError(None, None, status=500)
# Act
with pytest.raises(ClientResponseError):
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
async def test_remove_entry_app_unknown_error(
hass, config_entry, smartthings_mock):
"""Test raises exceptions removing the app."""
# Arrange
api = smartthings_mock.return_value
api.delete_installed_app.side_effect = lambda _: mock_coro()
api.delete_app.side_effect = lambda _: mock_coro(
exception=Exception)
smartthings_mock.delete_app.side_effect = Exception
# Act
with pytest.raises(Exception):
await smartthings.async_remove_entry(hass, config_entry)
# Assert
assert api.delete_installed_app.call_count == 1
assert api.delete_app.call_count == 1
assert smartthings_mock.delete_installed_app.call_count == 1
assert smartthings_mock.delete_app.call_count == 1
async def test_broker_regenerates_token(
hass, config_entry):
"""Test the device broker regenerates the refresh token."""
token = Mock()
token = Mock(OAuthToken)
token.refresh_token = str(uuid4())
token.refresh.return_value = mock_coro()
stored_action = None
def async_track_time_interval(hass, action, interval):

View file

@ -40,7 +40,7 @@ async def test_scene_activate(hass, scene):
assert state.attributes['color'] == scene.color
assert state.attributes['location_id'] == scene.location_id
# pylint: disable=protected-access
assert scene._api.execute_scene.call_count == 1 # type: ignore
assert scene.execute.call_count == 1 # type: ignore
async def test_unload_config_entry(hass, scene):

View file

@ -1,7 +1,7 @@
"""Tests for the smartapp module."""
from unittest.mock import Mock, patch
from uuid import uuid4
from asynctest import CoroutineMock, Mock, patch
from pysmartthings import AppEntity, Capability
from homeassistant.components.smartthings import smartapp
@ -9,8 +9,6 @@ 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
async def test_update_app(hass, app):
"""Test update_app does not save if app is current."""
@ -20,10 +18,8 @@ async def test_update_app(hass, app):
async def test_update_app_updated_needed(hass, app):
"""Test update_app updates when an app is needed."""
mock_app = Mock(spec=AppEntity)
mock_app = Mock(AppEntity)
mock_app.app_name = 'Test'
mock_app.refresh.return_value = mock_coro()
mock_app.save.return_value = mock_coro()
await smartapp.update_app(hass, mock_app)
@ -64,7 +60,6 @@ async def test_smartapp_install_creates_flow(
"""Test installation creates flow."""
# Arrange
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
app = Mock()
app.app_id = config_entry.data['app_id']
request = Mock()
@ -77,8 +72,7 @@ async def test_smartapp_install_creates_flow(
device_factory('', [Capability.switch, Capability.switch_level]),
device_factory('', [Capability.switch])
]
api.devices = Mock()
api.devices.return_value = mock_coro(return_value=devices)
smartthings_mock.devices.return_value = devices
# Act
await smartapp.smartapp_install(hass, request, None, app)
# Assert
@ -131,8 +125,7 @@ async def test_smartapp_uninstall(hass, config_entry):
request = Mock()
request.installed_app_id = config_entry.data['installed_app_id']
with patch.object(hass.config_entries, 'async_remove',
return_value=mock_coro()) as remove:
with patch.object(hass.config_entries, 'async_remove') as remove:
await smartapp.smartapp_uninstall(hass, request, None, app)
assert remove.call_count == 1
@ -140,12 +133,11 @@ async def test_smartapp_uninstall(hass, config_entry):
async def test_smartapp_webhook(hass):
"""Test the smartapp webhook calls the manager."""
manager = Mock()
manager.handle_request = Mock()
manager.handle_request.return_value = mock_coro(return_value={})
manager.handle_request = CoroutineMock(return_value={})
hass.data[DOMAIN][DATA_MANAGER] = manager
request = Mock()
request.headers = []
request.json.return_value = mock_coro(return_value={})
request.json = CoroutineMock(return_value={})
result = await smartapp.smartapp_webhook(hass, '', request)
assert result.body == b'{}'
@ -154,15 +146,11 @@ async def test_smartapp_webhook(hass):
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 = [
smartthings_mock.subscriptions.return_value = [
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]),
@ -172,23 +160,19 @@ async def test_smartapp_sync_subscriptions(
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
assert smartthings_mock.subscriptions.call_count == 1
assert smartthings_mock.delete_subscription.call_count == 1
assert smartthings_mock.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 = [
smartthings_mock.subscriptions.return_value = [
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]),
@ -198,25 +182,21 @@ async def test_smartapp_sync_subscriptions_up_to_date(
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
assert smartthings_mock.subscriptions.call_count == 1
assert smartthings_mock.delete_subscription.call_count == 0
assert smartthings_mock.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 = [
smartthings_mock.delete_subscription.side_effect = Exception
smartthings_mock.create_subscription.side_effect = Exception
smartthings_mock.subscriptions.return_value = [
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]),
@ -226,6 +206,6 @@ async def test_smartapp_sync_subscriptions_handles_exceptions(
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
assert smartthings_mock.subscriptions.call_count == 1
assert smartthings_mock.delete_subscription.call_count == 1
assert smartthings_mock.create_subscription.call_count == 1