From 19d1d748d447e425031d3b5da74e2121b472ceba Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Mon, 14 Aug 2017 01:37:50 -0400 Subject: [PATCH] Add support for Automatic OAuth2 authentication (#8962) * Add support for Automatic OAuth2 authentication * Fix async conversion of configurator * Rename method for async * Use hass.components to get configurator component * Fix typo * Move session data to hidden directory * Make configurator callback optional --- homeassistant/components/apple_tv.py | 2 +- homeassistant/components/axis.py | 2 +- homeassistant/components/configurator.py | 107 ++++++---- .../components/device_tracker/automatic.py | 182 +++++++++++++++--- .../components/device_tracker/icloud.py | 13 +- homeassistant/components/ecobee.py | 7 +- homeassistant/components/fan/insteon_local.py | 7 +- .../www_static/images/logo_automatic.png | Bin 0 -> 6244 bytes homeassistant/components/light/hue.py | 9 +- .../components/light/insteon_local.py | 7 +- .../components/media_player/braviatv.py | 7 +- .../components/media_player/gpmdp.py | 7 +- homeassistant/components/media_player/plex.py | 6 +- .../components/media_player/spotify.py | 7 +- .../components/media_player/webostv.py | 7 +- homeassistant/components/nest.py | 7 +- homeassistant/components/sensor/fitbit.py | 14 +- homeassistant/components/sensor/sabnzbd.py | 4 +- .../components/switch/insteon_local.py | 7 +- homeassistant/components/tradfri.py | 5 +- homeassistant/components/wink.py | 11 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../device_tracker/test_automatic.py | 33 +++- tests/components/test_configurator.py | 8 +- 25 files changed, 314 insertions(+), 149 deletions(-) create mode 100644 homeassistant/components/frontend/www_static/images/logo_automatic.png diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index c5f40ca5db8..7a2ff7610f7 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -91,7 +91,7 @@ def request_configuration(hass, config, atv, credentials): hass.async_add_job(configurator.request_done, instance) instance = configurator.request_config( - hass, 'Apple TV Authentication', configuration_callback, + 'Apple TV Authentication', configuration_callback, description='Please enter PIN code shown on screen.', submit_caption='Confirm', fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}] diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis.py index d83e07989e6..eaf85937658 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis.py @@ -110,7 +110,7 @@ def request_configuration(hass, name, host, serialnumber): title = '{} ({})'.format(name, host) request_id = configurator.request_config( - hass, title, configuration_callback, + title, configuration_callback, description='Functionality: ' + str(AXIS_INCLUDE), entity_picture="/static/images/logo_axis.png", link_name='Axis platform documentation', diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 660a62a5b89..2da8967bddf 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -7,19 +7,21 @@ A callback has to be provided to `request_config` which will be called when the user has submitted configuration information. """ import asyncio +import functools as ft import logging from homeassistant.core import callback as async_callback from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \ ATTR_ENTITY_PICTURE from homeassistant.loader import bind_hass -from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.util.async import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) -_REQUESTS = {} _KEY_INSTANCE = 'configurator' +DATA_REQUESTS = 'configurator_requests' + ATTR_CONFIGURE_ID = 'configure_id' ATTR_DESCRIPTION = 'description' ATTR_DESCRIPTION_IMAGE = 'description_image' @@ -39,63 +41,89 @@ STATE_CONFIGURED = 'configured' @bind_hass -def request_config( - hass, name, callback, description=None, description_image=None, +@async_callback +def async_request_config( + hass, name, callback=None, description=None, description_image=None, submit_caption=None, fields=None, link_name=None, link_url=None, entity_picture=None): """Create a new request for configuration. Will return an ID to be used for sequent calls. """ - instance = run_callback_threadsafe(hass.loop, - _async_get_instance, - hass).result() + instance = hass.data.get(_KEY_INSTANCE) - request_id = instance.request_config( + if instance is None: + instance = hass.data[_KEY_INSTANCE] = Configurator(hass) + + request_id = instance.async_request_config( name, callback, description, description_image, submit_caption, fields, link_name, link_url, entity_picture) - _REQUESTS[request_id] = instance + if DATA_REQUESTS not in hass.data: + hass.data[DATA_REQUESTS] = {} + + hass.data[DATA_REQUESTS][request_id] = instance return request_id -def notify_errors(request_id, error): +@bind_hass +def request_config(hass, *args, **kwargs): + """Create a new request for configuration. + + Will return an ID to be used for sequent calls. + """ + return run_callback_threadsafe( + hass.loop, ft.partial(async_request_config, hass, *args, **kwargs) + ).result() + + +@bind_hass +@async_callback +def async_notify_errors(hass, request_id, error): """Add errors to a config request.""" try: - _REQUESTS[request_id].notify_errors(request_id, error) + hass.data[DATA_REQUESTS][request_id].async_notify_errors( + request_id, error) except KeyError: # If request_id does not exist pass -def request_done(request_id): +@bind_hass +def notify_errors(hass, request_id, error): + """Add errors to a config request.""" + return run_callback_threadsafe( + hass.loop, async_notify_errors, hass, request_id, error + ).result() + + +@bind_hass +@async_callback +def async_request_done(hass, request_id): """Mark a configuration request as done.""" try: - _REQUESTS.pop(request_id).request_done(request_id) + hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id) except KeyError: # If request_id does not exist pass +@bind_hass +def request_done(hass, request_id): + """Mark a configuration request as done.""" + return run_callback_threadsafe( + hass.loop, async_request_done, hass, request_id + ).result() + + @asyncio.coroutine def async_setup(hass, config): """Set up the configurator component.""" return True -@async_callback -def _async_get_instance(hass): - """Get an instance per hass object.""" - instance = hass.data.get(_KEY_INSTANCE) - - if instance is None: - instance = hass.data[_KEY_INSTANCE] = Configurator(hass) - - return instance - - class Configurator(object): """The class to keep track of current configuration requests.""" @@ -105,14 +133,16 @@ class Configurator(object): self._cur_id = 0 self._requests = {} hass.services.async_register( - DOMAIN, SERVICE_CONFIGURE, self.handle_service_call) + DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call) - def request_config( + @async_callback + def async_request_config( self, name, callback, description, description_image, submit_caption, fields, link_name, link_url, entity_picture): """Set up a request for configuration.""" - entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) + entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, name, hass=self.hass) if fields is None: fields = [] @@ -138,11 +168,12 @@ class Configurator(object): ] if value is not None }) - self.hass.states.set(entity_id, STATE_CONFIGURE, data) + self.hass.states.async_set(entity_id, STATE_CONFIGURE, data) return request_id - def notify_errors(self, request_id, error): + @async_callback + def async_notify_errors(self, request_id, error): """Update the state with errors.""" if not self._validate_request_id(request_id): return @@ -154,9 +185,10 @@ class Configurator(object): new_data = dict(state.attributes) new_data[ATTR_ERRORS] = error - self.hass.states.set(entity_id, STATE_CONFIGURE, new_data) + self.hass.states.async_set(entity_id, STATE_CONFIGURE, new_data) - def request_done(self, request_id): + @async_callback + def async_request_done(self, request_id): """Remove the configuration request.""" if not self._validate_request_id(request_id): return @@ -167,15 +199,16 @@ class Configurator(object): # the result fo the service call (current design limitation). # Instead, we will set it to configured to give as feedback but delete # it shortly after so that it is deleted when the client updates. - self.hass.states.set(entity_id, STATE_CONFIGURED) + self.hass.states.async_set(entity_id, STATE_CONFIGURED) def deferred_remove(event): """Remove the request state.""" - self.hass.states.remove(entity_id) + self.hass.states.async_remove(entity_id) - self.hass.bus.listen_once(EVENT_TIME_CHANGED, deferred_remove) + self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove) - def handle_service_call(self, call): + @async_callback + def async_handle_service_call(self, call): """Handle a configure service call.""" request_id = call.data.get(ATTR_CONFIGURE_ID) @@ -186,8 +219,8 @@ class Configurator(object): entity_id, fields, callback = self._requests[request_id] # field validation goes here? - - self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) + if callback: + self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) def _generate_unique_id(self): """Generate a unique configurator ID.""" diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 891f1b22775..1cd0e84cd8f 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -6,28 +6,33 @@ https://home-assistant.io/components/device_tracker.automatic/ """ import asyncio from datetime import timedelta +import json import logging +import os +from aiohttp import web import voluptuous as vol from homeassistant.components.device_tracker import ( PLATFORM_SCHEMA, ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_MAC, ATTR_GPS, ATTR_GPS_ACCURACY) +from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, - EVENT_HOMEASSISTANT_START) + CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval -REQUIREMENTS = ['aioautomatic==0.4.0'] +REQUIREMENTS = ['aioautomatic==0.5.0'] +DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) CONF_CLIENT_ID = 'client_id' CONF_SECRET = 'secret' CONF_DEVICES = 'devices' +CONF_CURRENT_LOCATION = 'current_location' DEFAULT_TIMEOUT = 5 @@ -38,38 +43,76 @@ ATTR_FUEL_LEVEL = 'fuel_level' EVENT_AUTOMATIC_UPDATE = 'automatic_update' +AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json' + +DATA_CONFIGURING = 'automatic_configurator_clients' +DATA_REFRESH_TOKEN = 'refresh_token' + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_SECRET): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, + vol.Inclusive(CONF_USERNAME, 'auth'): cv.string, + vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string, + vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, vol.Optional(CONF_DEVICES, default=None): vol.All( cv.ensure_list, [cv.string]) }) +def _get_refresh_token_from_file(hass, filename): + """Attempt to load session data from file.""" + path = hass.config.path(filename) + + if not os.path.isfile(path): + return None + + try: + with open(path) as data_file: + data = json.load(data_file) + if data is None: + return None + + return data.get(DATA_REFRESH_TOKEN) + except ValueError: + return None + + +def _write_refresh_token_to_file(hass, filename, refresh_token): + """Attempt to store session data to file.""" + path = hass.config.path(filename) + + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w+') as data_file: + json.dump({ + DATA_REFRESH_TOKEN: refresh_token + }, data_file) + + @asyncio.coroutine def async_setup_scanner(hass, config, async_see, discovery_info=None): """Validate the configuration and return an Automatic scanner.""" import aioautomatic + hass.http.register_view(AutomaticAuthCallbackView()) + + scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE + client = aioautomatic.Client( client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_SECRET], client_session=async_get_clientsession(hass), request_kwargs={'timeout': DEFAULT_TIMEOUT}) - try: - try: - session = yield from client.create_session_from_password( - FULL_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD]) - except aioautomatic.exceptions.ForbiddenError as exc: - if not str(exc).startswith("invalid_scope"): - raise exc - _LOGGER.info("Client not authorized for current_location scope. " - "location:updated events will not be received.") - session = yield from client.create_session_from_password( - DEFAULT_SCOPE, config[CONF_USERNAME], config[CONF_PASSWORD]) + filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID]) + refresh_token = yield from hass.async_add_job( + _get_refresh_token_from_file, hass, filename) + + @asyncio.coroutine + def initialize_data(session): + """Initialize the AutomaticData object from the created session.""" + hass.async_add_job( + _write_refresh_token_to_file, hass, filename, + session.refresh_token) data = AutomaticData( hass, client, session, config[CONF_DEVICES], async_see) @@ -77,26 +120,105 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): vehicles = yield from session.get_vehicles() for vehicle in vehicles: hass.async_add_job(data.load_vehicle(vehicle)) - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - return False - @callback - def ws_connect(event): - """Open the websocket connection.""" - hass.async_add_job(data.ws_connect()) + # Create a task instead of adding a tracking job, since this task will + # run until the websocket connection is closed. + hass.loop.create_task(data.ws_connect()) - @callback - def ws_close(event): - """Close the websocket connection.""" - hass.async_add_job(data.ws_close()) + if refresh_token is not None: + try: + session = yield from client.create_session_from_refresh_token( + refresh_token) + yield from initialize_data(session) + return True + except aioautomatic.exceptions.BadRequestError as err: + if str(err) == 'err_invalid_refresh_token': + _LOGGER.error("Stored refresh token was invalid.") + yield from hass.async_add_job( + _write_refresh_token_to_file, hass, filename, None) + else: + _LOGGER.error(str(err)) + return False + except aioautomatic.exceptions.AutomaticError as err: + _LOGGER.error(str(err)) + return False - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, ws_connect) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, ws_close) + if CONF_USERNAME in config: + try: + session = yield from client.create_session_from_password( + scope, config[CONF_USERNAME], config[CONF_PASSWORD]) + yield from initialize_data(session) + return True + except aioautomatic.exceptions.AutomaticError as err: + _LOGGER.error(str(err)) + return False + configurator = hass.components.configurator + request_id = configurator.async_request_config( + "Automatic", description=( + "Authorization required for Automatic device tracker."), + link_name="Click here to authorize Home Assistant.", + link_url=client.generate_oauth_url(scope), + entity_picture="/static/images/logo_automatic.png", + ) + + @asyncio.coroutine + def initialize_callback(code, state): + """Callback after OAuth2 response is returned.""" + try: + session = yield from client.create_session_from_oauth_code( + code, state) + yield from initialize_data(session) + configurator.async_request_done(request_id) + except aioautomatic.exceptions.AutomaticError as err: + _LOGGER.error(str(err)) + configurator.async_notify_errors(request_id, str(err)) + return False + + if DATA_CONFIGURING not in hass.data: + hass.data[DATA_CONFIGURING] = {} + + hass.data[DATA_CONFIGURING][client.state] = initialize_callback return True +class AutomaticAuthCallbackView(HomeAssistantView): + """Handle OAuth finish callback requests.""" + + requires_auth = False + url = '/api/automatic/callback' + name = 'api:automatic:callback' + + @callback + def get(self, request): # pylint: disable=no-self-use + """Finish OAuth callback request.""" + hass = request.app['hass'] + params = request.query + response = web.HTTPFound('/states') + + if 'state' not in params or 'code' not in params: + if 'error' in params: + _LOGGER.error( + "Error authorizing Automatic: %s", params['error']) + return response + else: + _LOGGER.error( + "Error authorizing Automatic. Invalid response returned.") + return response + + if DATA_CONFIGURING not in hass.data or \ + params['state'] not in hass.data[DATA_CONFIGURING]: + _LOGGER.error("Automatic configuration request not found.") + return response + + code = params['code'] + state = params['state'] + initialize_callback = hass.data[DATA_CONFIGURING][state] + hass.async_add_job(initialize_callback(code, state)) + + return response + + class AutomaticData(object): """A class representing an Automatic cloud service connection.""" @@ -115,6 +237,8 @@ class AutomaticData(object): lambda name, event: self.hass.async_add_job( self.handle_event(name, event))) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close()) + @asyncio.coroutine def handle_event(self, name, event): """Coroutine to update state for a realtime event.""" diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index 194a2f4bfac..f20dad1fceb 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -19,7 +19,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify import homeassistant.util.dt as dt_util from homeassistant.util.location import distance -from homeassistant.loader import get_component _LOGGER = logging.getLogger(__name__) @@ -209,7 +208,7 @@ class Icloud(DeviceScanner): if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) - configurator = get_component('configurator') + configurator = self.hass.components.configurator configurator.request_done(request_id) # Trigger the next step immediately @@ -217,7 +216,7 @@ class Icloud(DeviceScanner): def icloud_need_trusted_device(self): """We need a trusted device.""" - configurator = get_component('configurator') + configurator = self.hass.components.configurator if self.accountname in _CONFIGURING: return @@ -229,7 +228,7 @@ class Icloud(DeviceScanner): devicesstring += "{}: {};".format(i, devicename) _CONFIGURING[self.accountname] = configurator.request_config( - self.hass, 'iCloud {}'.format(self.accountname), + 'iCloud {}'.format(self.accountname), self.icloud_trusted_device_callback, description=( 'Please choose your trusted device by entering' @@ -259,17 +258,17 @@ class Icloud(DeviceScanner): if self.accountname in _CONFIGURING: request_id = _CONFIGURING.pop(self.accountname) - configurator = get_component('configurator') + configurator = self.hass.components.configurator configurator.request_done(request_id) def icloud_need_verification_code(self): """Return the verification code.""" - configurator = get_component('configurator') + configurator = self.hass.components.configurator if self.accountname in _CONFIGURING: return _CONFIGURING[self.accountname] = configurator.request_config( - self.hass, 'iCloud {}'.format(self.accountname), + 'iCloud {}'.format(self.accountname), self.icloud_verification_callback, description=('Please enter the validation code:'), entity_picture="/static/images/config_icloud.png", diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index f0c95f7de3d..86261650685 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -13,7 +13,6 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.const import CONF_API_KEY -from homeassistant.loader import get_component from homeassistant.util import Throttle REQUIREMENTS = ['python-ecobee-api==0.0.7'] @@ -41,7 +40,7 @@ CONFIG_SCHEMA = vol.Schema({ def request_configuration(network, hass, config): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator if 'ecobee' in _CONFIGURING: configurator.notify_errors( _CONFIGURING['ecobee'], "Failed to register, please try again.") @@ -56,7 +55,7 @@ def request_configuration(network, hass, config): setup_ecobee(hass, network, config) _CONFIGURING['ecobee'] = configurator.request_config( - hass, "Ecobee", ecobee_configuration_callback, + "Ecobee", ecobee_configuration_callback, description=( 'Please authorize this app at https://www.ecobee.com/consumer' 'portal/index.html with pin code: ' + network.pin), @@ -73,7 +72,7 @@ def setup_ecobee(hass, network, config): return if 'ecobee' in _CONFIGURING: - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(_CONFIGURING.pop('ecobee')) hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP) diff --git a/homeassistant/components/fan/insteon_local.py b/homeassistant/components/fan/insteon_local.py index a18c173ecca..5bdfec08427 100644 --- a/homeassistant/components/fan/insteon_local.py +++ b/homeassistant/components/fan/insteon_local.py @@ -13,7 +13,6 @@ from homeassistant.components.fan import ( ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, FanEntity) from homeassistant.helpers.entity import ToggleEntity -from homeassistant.loader import get_component import homeassistant.util as util _CONFIGURING = {} @@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def request_configuration(device_id, insteonhub, model, hass, add_devices_callback): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if device_id in _CONFIGURING: @@ -72,7 +71,7 @@ def request_configuration(device_id, insteonhub, model, hass, add_devices_callback) _CONFIGURING[device_id] = configurator.request_config( - hass, 'Insteon ' + model + ' addr: ' + device_id, + 'Insteon ' + model + ' addr: ' + device_id, insteon_fan_config_callback, description=('Enter a name for ' + model + ' Fan addr: ' + device_id), entity_picture='/static/images/config_insteon.png', @@ -85,7 +84,7 @@ def setup_fan(device_id, name, insteonhub, hass, add_devices_callback): """Set up the fan.""" if device_id in _CONFIGURING: request_id = _CONFIGURING.pop(device_id) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) _LOGGER.info("Device configuration done!") diff --git a/homeassistant/components/frontend/www_static/images/logo_automatic.png b/homeassistant/components/frontend/www_static/images/logo_automatic.png new file mode 100644 index 0000000000000000000000000000000000000000..ab03fa93b4c6cb196be42623c5052446cb76d3e6 GIT binary patch literal 6244 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 Dtn3JZ literal 0 HcmV?d00001 diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 27c3b43e926..746c6489c9e 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -23,7 +23,6 @@ from homeassistant.components.light import ( SUPPORT_XY_COLOR, Light, PLATFORM_SCHEMA) from homeassistant.config import load_yaml_config_file from homeassistant.const import (CONF_FILENAME, CONF_HOST, DEVICE_DEFAULT_NAME) -from homeassistant.loader import get_component from homeassistant.components.emulated_hue import ATTR_EMULATED_HUE import homeassistant.helpers.config_validation as cv @@ -164,9 +163,7 @@ def setup_bridge(host, hass, add_devices, filename, allow_unreachable, # If we came here and configuring this host, mark as done if host in _CONFIGURING: request_id = _CONFIGURING.pop(host) - - configurator = get_component('configurator') - + configurator = hass.components.configurator configurator.request_done(request_id) lights = {} @@ -268,7 +265,7 @@ def request_configuration(host, hass, add_devices, filename, allow_unreachable, allow_in_emulated_hue, allow_hue_groups): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if host in _CONFIGURING: @@ -284,7 +281,7 @@ def request_configuration(host, hass, add_devices, filename, allow_in_emulated_hue, allow_hue_groups) _CONFIGURING[host] = configurator.request_config( - hass, "Philips Hue", hue_configuration_callback, + "Philips Hue", hue_configuration_callback, description=("Press the button on the bridge to register Philips Hue " "with Home Assistant."), entity_picture="/static/images/logo_philips_hue.png", diff --git a/homeassistant/components/light/insteon_local.py b/homeassistant/components/light/insteon_local.py index e5b99ca1cb2..ebd6ab92d0f 100644 --- a/homeassistant/components/light/insteon_local.py +++ b/homeassistant/components/light/insteon_local.py @@ -11,7 +11,6 @@ from datetime import timedelta from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -from homeassistant.loader import get_component import homeassistant.util as util _CONFIGURING = {} @@ -54,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def request_configuration(device_id, insteonhub, model, hass, add_devices_callback): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if device_id in _CONFIGURING: @@ -69,7 +68,7 @@ def request_configuration(device_id, insteonhub, model, hass, add_devices_callback) _CONFIGURING[device_id] = configurator.request_config( - hass, 'Insteon ' + model + ' addr: ' + device_id, + 'Insteon ' + model + ' addr: ' + device_id, insteon_light_config_callback, description=('Enter a name for ' + model + ' addr: ' + device_id), entity_picture='/static/images/config_insteon.png', @@ -82,7 +81,7 @@ def setup_light(device_id, name, insteonhub, hass, add_devices_callback): """Set up the light.""" if device_id in _CONFIGURING: request_id = _CONFIGURING.pop(device_id) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) _LOGGER.debug("Device configuration done") diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py index 93071b9840f..399052611c1 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -11,7 +11,6 @@ import re import voluptuous as vol -from homeassistant.loader import get_component from homeassistant.components.media_player import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY, @@ -132,7 +131,7 @@ def setup_bravia(config, pin, hass, add_devices): # If we came here and configuring this host, mark as done if host in _CONFIGURING: request_id = _CONFIGURING.pop(host) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) _LOGGER.info("Discovery configuration done") @@ -150,7 +149,7 @@ def request_configuration(config, hass, add_devices): host = config.get(CONF_HOST) name = config.get(CONF_NAME) - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if host in _CONFIGURING: @@ -171,7 +170,7 @@ def request_configuration(config, hass, add_devices): request_configuration(config, hass, add_devices) _CONFIGURING[host] = configurator.request_config( - hass, name, bravia_configuration_callback, + name, bravia_configuration_callback, description='Enter the Pin shown on your Sony Bravia TV.' + 'If no Pin is shown, enter 0000 to let TV show you a Pin.', description_image="/static/images/smart-tv.png", diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/media_player/gpmdp.py index 269964ea6c7..4090f420855 100644 --- a/homeassistant/components/media_player/gpmdp.py +++ b/homeassistant/components/media_player/gpmdp.py @@ -18,7 +18,6 @@ from homeassistant.components.media_player import ( MediaPlayerDevice, PLATFORM_SCHEMA) from homeassistant.const import ( STATE_PLAYING, STATE_PAUSED, STATE_OFF, CONF_HOST, CONF_PORT, CONF_NAME) -from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['websocket-client==0.37.0'] @@ -48,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def request_configuration(hass, config, url, add_devices_callback): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator if 'gpmdp' in _CONFIGURING: configurator.notify_errors( _CONFIGURING['gpmdp'], "Failed to register, please try again.") @@ -96,7 +95,7 @@ def request_configuration(hass, config, url, add_devices_callback): break _CONFIGURING['gpmdp'] = configurator.request_config( - hass, DEFAULT_NAME, gpmdp_configuration_callback, + DEFAULT_NAME, gpmdp_configuration_callback, description=( 'Enter the pin that is displayed in the ' 'Google Play Music Desktop Player.'), @@ -117,7 +116,7 @@ def setup_gpmdp(hass, config, code, add_devices): return if 'gpmdp' in _CONFIGURING: - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(_CONFIGURING.pop('gpmdp')) add_devices([GPMDP(name, url, code)], True) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index f4c69ba1fe6..a901cd1d569 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -23,7 +23,6 @@ from homeassistant.const import ( DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import track_utc_time_change -from homeassistant.loader import get_component REQUIREMENTS = ['plexapi==2.0.2'] @@ -143,7 +142,7 @@ def setup_plexserver( # If we came here and configuring this host, mark as done if host in _CONFIGURING: request_id = _CONFIGURING.pop(host) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) _LOGGER.info("Discovery configuration done") @@ -236,7 +235,7 @@ def setup_plexserver( def request_configuration(host, hass, config, add_devices_callback): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if host in _CONFIGURING: configurator.notify_errors(_CONFIGURING[host], @@ -254,7 +253,6 @@ def request_configuration(host, hass, config, add_devices_callback): ) _CONFIGURING[host] = configurator.request_config( - hass, 'Plex Media Server', plex_configuration_callback, description=('Enter the X-Plex-Token'), diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index bc0728c7ff2..80d18b8eea8 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -10,7 +10,6 @@ from datetime import timedelta import voluptuous as vol from homeassistant.core import callback -from homeassistant.loader import get_component from homeassistant.components.http import HomeAssistantView from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_VOLUME_SET, @@ -62,9 +61,9 @@ SCAN_INTERVAL = timedelta(seconds=30) def request_configuration(hass, config, add_devices, oauth): """Request Spotify authorization.""" - configurator = get_component('configurator') + configurator = hass.components.configurator hass.data[DOMAIN] = configurator.request_config( - hass, DEFAULT_NAME, lambda _: None, + DEFAULT_NAME, lambda _: None, link_name=CONFIGURATOR_LINK_NAME, link_url=oauth.get_authorize_url(), description=CONFIGURATOR_DESCRIPTION, @@ -88,7 +87,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): request_configuration(hass, config, add_devices, oauth) return if hass.data.get(DOMAIN): - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(hass.data.get(DOMAIN)) del hass.data[DOMAIN] player = SpotifyMediaPlayer(oauth, config.get(CONF_NAME, DEFAULT_NAME), diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py index 112b84ec5f0..f783a2e9fe2 100644 --- a/homeassistant/components/media_player/webostv.py +++ b/homeassistant/components/media_player/webostv.py @@ -22,7 +22,6 @@ from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_CUSTOMIZE, STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN, CONF_NAME, CONF_FILENAME) -from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pylgtv==0.1.7', @@ -114,7 +113,7 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices): # If we came here and configuring this host, mark as done. if client.is_registered() and host in _CONFIGURING: request_id = _CONFIGURING.pop(host) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) add_devices([LgWebOSDevice(host, mac, name, customize, config)], True) @@ -123,7 +122,7 @@ def setup_tv(host, mac, name, customize, config, hass, add_devices): def request_configuration( host, mac, name, customize, config, hass, add_devices): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if host in _CONFIGURING: @@ -137,7 +136,7 @@ def request_configuration( setup_tv(host, mac, name, customize, config, hass, add_devices) _CONFIGURING[host] = configurator.request_config( - hass, name, lgtv_configuration_callback, + name, lgtv_configuration_callback, description='Click start and accept the pairing request on your TV.', description_image='/static/images/config_webos.png', submit_caption='Start pairing request' diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index 6443fc47a85..512819b7e74 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -14,7 +14,6 @@ from homeassistant.helpers import discovery from homeassistant.const import ( CONF_STRUCTURE, CONF_FILENAME, CONF_BINARY_SENSORS, CONF_SENSORS, CONF_MONITORED_CONDITIONS) -from homeassistant.loader import get_component REQUIREMENTS = ['python-nest==3.1.0'] @@ -54,7 +53,7 @@ CONFIG_SCHEMA = vol.Schema({ def request_configuration(nest, hass, config): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator if 'nest' in _CONFIGURING: _LOGGER.debug("configurator failed") configurator.notify_errors( @@ -68,7 +67,7 @@ def request_configuration(nest, hass, config): setup_nest(hass, nest, config, pin=pin) _CONFIGURING['nest'] = configurator.request_config( - hass, "Nest", nest_configuration_callback, + "Nest", nest_configuration_callback, description=('To configure Nest, click Request Authorization below, ' 'log into your Nest account, ' 'and then enter the resulting PIN'), @@ -92,7 +91,7 @@ def setup_nest(hass, nest, config, pin=None): if 'nest' in _CONFIGURING: _LOGGER.debug("configuration done") - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(_CONFIGURING.pop('nest')) _LOGGER.debug("proceeding with setup") diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py index c0256e3a88b..23bf93fde2a 100644 --- a/homeassistant/components/sensor/fitbit.py +++ b/homeassistant/components/sensor/fitbit.py @@ -17,7 +17,6 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.entity import Entity -from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['fitbit==0.2.3'] @@ -155,7 +154,7 @@ def config_from_file(filename, config=None): def request_app_setup(hass, config, add_devices, config_path, discovery_info=None): """Assist user with configuring the Fitbit dev application.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # pylint: disable=unused-argument def fitbit_configuration_callback(callback_data): @@ -166,7 +165,8 @@ def request_app_setup(hass, config, add_devices, config_path, if config_file == DEFAULT_CONFIG: error_msg = ("You didn't correctly modify fitbit.conf", " please try again") - configurator.notify_errors(_CONFIGURING['fitbit'], error_msg) + configurator.notify_errors(_CONFIGURING['fitbit'], + error_msg) else: setup_platform(hass, config, add_devices, discovery_info) else: @@ -187,7 +187,7 @@ def request_app_setup(hass, config, add_devices, config_path, submit = "I have saved my Client ID and Client Secret into fitbit.conf." _CONFIGURING['fitbit'] = configurator.request_config( - hass, 'Fitbit', fitbit_configuration_callback, + 'Fitbit', fitbit_configuration_callback, description=description, submit_caption=submit, description_image="/static/images/config_fitbit_app.png" ) @@ -195,7 +195,7 @@ def request_app_setup(hass, config, add_devices, config_path, def request_oauth_completion(hass): """Request user complete Fitbit OAuth2 flow.""" - configurator = get_component('configurator') + configurator = hass.components.configurator if "fitbit" in _CONFIGURING: configurator.notify_errors( _CONFIGURING['fitbit'], "Failed to register, please try again.") @@ -211,7 +211,7 @@ def request_oauth_completion(hass): description = "Please authorize Fitbit by visiting {}".format(start_url) _CONFIGURING['fitbit'] = configurator.request_config( - hass, 'Fitbit', fitbit_configuration_callback, + 'Fitbit', fitbit_configuration_callback, description=description, submit_caption="I have authorized Fitbit." ) @@ -233,7 +233,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False if "fitbit" in _CONFIGURING: - get_component('configurator').request_done(_CONFIGURING.pop("fitbit")) + hass.components.configurator.request_done(_CONFIGURING.pop("fitbit")) import fitbit diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index e2b7584d865..928e855915a 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -18,7 +18,6 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -from homeassistant.loader import get_component REQUIREMENTS = ['https://github.com/jamespcole/home-assistant-nzb-clients/' 'archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip' @@ -88,7 +87,7 @@ def setup_sabnzbd(base_url, apikey, name, hass, config, add_devices, sab_api): def request_configuration(host, name, hass, config, add_devices, sab_api): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if host in _CONFIGURING: configurator.notify_errors(_CONFIGURING[host], @@ -114,7 +113,6 @@ def request_configuration(host, name, hass, config, add_devices, sab_api): hass.async_add_job(success) _CONFIGURING[host] = configurator.request_config( - hass, DEFAULT_NAME, sabnzbd_configuration_callback, description=('Enter the API Key'), diff --git a/homeassistant/components/switch/insteon_local.py b/homeassistant/components/switch/insteon_local.py index e6e34f6de27..94259b8bb80 100644 --- a/homeassistant/components/switch/insteon_local.py +++ b/homeassistant/components/switch/insteon_local.py @@ -10,7 +10,6 @@ import os from datetime import timedelta from homeassistant.components.switch import SwitchDevice -from homeassistant.loader import get_component import homeassistant.util as util _CONFIGURING = {} @@ -51,7 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def request_configuration( device_id, insteonhub, model, hass, add_devices_callback): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator # We got an error if this method is called while we are configuring if device_id in _CONFIGURING: @@ -66,7 +65,7 @@ def request_configuration( add_devices_callback) _CONFIGURING[device_id] = configurator.request_config( - hass, 'Insteon Switch ' + model + ' addr: ' + device_id, + 'Insteon Switch ' + model + ' addr: ' + device_id, insteon_switch_config_callback, description=('Enter a name for ' + model + ' addr: ' + device_id), entity_picture='/static/images/config_insteon.png', @@ -79,7 +78,7 @@ def setup_switch(device_id, name, insteonhub, hass, add_devices_callback): """Set up the switch.""" if device_id in _CONFIGURING: request_id = _CONFIGURING.pop(device_id) - configurator = get_component('configurator') + configurator = hass.components.configurator configurator.request_done(request_id) _LOGGER.info("Device configuration done") diff --git a/homeassistant/components/tradfri.py b/homeassistant/components/tradfri.py index cd83f81afd1..31938cd15ff 100644 --- a/homeassistant/components/tradfri.py +++ b/homeassistant/components/tradfri.py @@ -14,7 +14,6 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.const import CONF_HOST, CONF_API_KEY -from homeassistant.loader import get_component from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI REQUIREMENTS = ['pytradfri==1.1'] @@ -41,7 +40,7 @@ _LOGGER = logging.getLogger(__name__) def request_configuration(hass, config, host): """Request configuration steps from the user.""" - configurator = get_component('configurator') + configurator = hass.components.configurator hass.data.setdefault(KEY_CONFIG, {}) instance = hass.data[KEY_CONFIG].get(host) @@ -70,7 +69,7 @@ def request_configuration(hass, config, host): hass.async_add_job(success) instance = configurator.request_config( - hass, "IKEA Trådfri", configuration_callback, + "IKEA Trådfri", configuration_callback, description='Please enter the security code written at the bottom of ' 'your IKEA Trådfri Gateway.', submit_caption="Confirm", diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 8d40f5dad48..23eb90daa89 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -13,7 +13,6 @@ from datetime import timedelta import voluptuous as vol import requests -from homeassistant.loader import get_component from homeassistant.core import callback from homeassistant.components.http import HomeAssistantView from homeassistant.helpers import discovery @@ -103,7 +102,7 @@ def _read_config_file(file_path): def _request_app_setup(hass, config): """Assist user with configuring the Wink dev application.""" hass.data[DOMAIN]['configurator'] = True - configurator = get_component('configurator') + configurator = hass.components.configurator # pylint: disable=unused-argument def wink_configuration_callback(callback_data): @@ -138,7 +137,7 @@ def _request_app_setup(hass, config): """.format(start_url) hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config( - hass, DOMAIN, wink_configuration_callback, + DOMAIN, wink_configuration_callback, description=description, submit_caption="submit", description_image="/static/images/config_wink.png", fields=[{'id': 'client_id', 'name': 'Client ID', 'type': 'string'}, @@ -151,7 +150,7 @@ def _request_app_setup(hass, config): def _request_oauth_completion(hass, config): """Request user complete Wink OAuth2 flow.""" hass.data[DOMAIN]['configurator'] = True - configurator = get_component('configurator') + configurator = hass.components.configurator if DOMAIN in hass.data[DOMAIN]['configuring']: configurator.notify_errors( hass.data[DOMAIN]['configuring'][DOMAIN], @@ -168,7 +167,7 @@ def _request_oauth_completion(hass, config): description = "Please authorize Wink by visiting {}".format(start_url) hass.data[DOMAIN]['configuring'][DOMAIN] = configurator.request_config( - hass, DOMAIN, wink_configuration_callback, + DOMAIN, wink_configuration_callback, description=description ) @@ -248,7 +247,7 @@ def setup(hass, config): if DOMAIN in hass.data[DOMAIN]['configuring']: _configurator = hass.data[DOMAIN]['configuring'] - get_component('configurator').request_done(_configurator.pop( + hass.components.configurator.request_done(_configurator.pop( DOMAIN)) # Using oauth diff --git a/requirements_all.txt b/requirements_all.txt index 07bb6846c61..390242c5990 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -39,7 +39,7 @@ SoCo==0.12 TwitterAPI==2.4.6 # homeassistant.components.device_tracker.automatic -aioautomatic==0.4.0 +aioautomatic==0.5.0 # homeassistant.components.sensor.dnsip aiodns==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3577584cfc2..7127ef9bb5f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -27,7 +27,7 @@ PyJWT==1.5.2 SoCo==0.12 # homeassistant.components.device_tracker.automatic -aioautomatic==0.4.0 +aioautomatic==0.5.0 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/tests/components/device_tracker/test_automatic.py b/tests/components/device_tracker/test_automatic.py index f823f3c3262..3355f0d1f1b 100644 --- a/tests/components/device_tracker/test_automatic.py +++ b/tests/components/device_tracker/test_automatic.py @@ -7,12 +7,16 @@ import aioautomatic from homeassistant.components.device_tracker.automatic import ( async_setup_scanner) +from tests.common import mock_http_component + _LOGGER = logging.getLogger(__name__) @patch('aioautomatic.Client.create_session_from_password') def test_invalid_credentials(mock_create_session, hass): """Test with invalid credentials.""" + mock_http_component(hass) + @asyncio.coroutine def get_session(*args, **kwargs): """Return the test session.""" @@ -34,8 +38,15 @@ def test_invalid_credentials(mock_create_session, hass): @patch('aioautomatic.Client.create_session_from_password') -def test_valid_credentials(mock_create_session, hass): +@patch('aioautomatic.Client.ws_connect') +@patch('json.dump') +@patch('os.makedirs') +@patch('homeassistant.components.device_tracker.automatic.open', create=True) +def test_valid_credentials(mock_open, mock_os_makedirs, mock_json_dump, + mock_ws_connect, mock_create_session, hass): """Test with valid credentials.""" + mock_http_component(hass) + session = MagicMock() vehicle = MagicMock() trip = MagicMock() @@ -66,13 +77,21 @@ def test_valid_credentials(mock_create_session, hass): return [trip] mock_create_session.side_effect = get_session + session.ws_connect = MagicMock() session.get_vehicles.side_effect = get_vehicles session.get_trips.side_effect = get_trips + session.refresh_token = 'mock_refresh_token' + + @asyncio.coroutine + def ws_connect(): + return asyncio.Future(loop=hass.loop) + + mock_ws_connect.side_effect = ws_connect config = { 'platform': 'automatic', - 'username': 'bad_username', - 'password': 'bad_password', + 'username': 'good_username', + 'password': 'good_password', 'client_id': 'client_id', 'secret': 'client_secret', 'devices': None, @@ -80,6 +99,8 @@ def test_valid_credentials(mock_create_session, hass): result = hass.loop.run_until_complete( async_setup_scanner(hass, config, mock_see)) + hass.async_block_till_done() + assert result assert mock_see.called assert len(mock_see.mock_calls) == 2 @@ -89,3 +110,9 @@ def test_valid_credentials(mock_create_session, hass): assert mock_see.mock_calls[0][2]['attributes'] == {'fuel_level': 45.6} assert mock_see.mock_calls[0][2]['gps'] == (45.567, 34.345) assert mock_see.mock_calls[0][2]['gps_accuracy'] == 5.6 + + assert mock_json_dump.called + assert len(mock_json_dump.mock_calls) == 1 + assert mock_json_dump.mock_calls[0][1][0] == { + 'refresh_token': 'mock_refresh_token' + } diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index 66466656835..a289f58db5a 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -90,20 +90,20 @@ class TestConfigurator(unittest.TestCase): request_id = configurator.request_config( self.hass, "Test Request", lambda _: None) error = "Oh no bad bad bad" - configurator.notify_errors(request_id, error) + configurator.notify_errors(self.hass, request_id, error) state = self.hass.states.all()[0] self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS)) def test_notify_errors_fail_silently_on_bad_request_id(self): """Test if notify errors fails silently with a bad request id.""" - configurator.notify_errors(2015, "Try this error") + configurator.notify_errors(self.hass, 2015, "Try this error") def test_request_done_works(self): """Test if calling request done works.""" request_id = configurator.request_config( self.hass, "Test Request", lambda _: None) - configurator.request_done(request_id) + configurator.request_done(self.hass, request_id) self.assertEqual(1, len(self.hass.states.all())) self.hass.bus.fire(EVENT_TIME_CHANGED) @@ -112,4 +112,4 @@ class TestConfigurator(unittest.TestCase): def test_request_done_fail_silently_on_bad_request_id(self): """Test that request_done fails silently with a bad request id.""" - configurator.request_done(2016) + configurator.request_done(self.hass, 2016)