diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 27299a1efd6..7b3a9e19b4e 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -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 diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index b79ab59a98a..54f6400d763 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -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: diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 4daf37cac55..150c8f7327e 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -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): diff --git a/tests/components/smartthings/test_scene.py b/tests/components/smartthings/test_scene.py index 2d4990675f8..e3ce80bd1a0 100644 --- a/tests/components/smartthings/test_scene.py +++ b/tests/components/smartthings/test_scene.py @@ -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): diff --git a/tests/components/smartthings/test_smartapp.py b/tests/components/smartthings/test_smartapp.py index 46bd1f42f7f..0d9bb568475 100644 --- a/tests/components/smartthings/test_smartapp.py +++ b/tests/components/smartthings/test_smartapp.py @@ -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