Clean up Google Config (#24663)

* Clean up Google Config

* Lint

* pylint

* pylint2
pull/24776/head
Paulus Schoutsen 2019-06-21 02:17:21 -07:00
parent 0f5c9b4af3
commit 327fe63047
12 changed files with 460 additions and 387 deletions

View File

@ -0,0 +1,244 @@
"""Alexa configuration for Home Assistant Cloud."""
import asyncio
from datetime import timedelta
import logging
import aiohttp
import async_timeout
from hass_nabucasa import cloud_api
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers import entity_registry
from homeassistant.helpers.event import async_call_later
from homeassistant.util.dt import utcnow
from homeassistant.components.alexa import (
config as alexa_config,
errors as alexa_errors,
entities as alexa_entities,
state_report as alexa_state_report,
)
from .const import (
CONF_ENTITY_CONFIG, CONF_FILTER, PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
RequireRelink
)
_LOGGER = logging.getLogger(__name__)
# Time to wait when entity preferences have changed before syncing it to
# the cloud.
SYNC_DELAY = 1
class AlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration."""
def __init__(self, hass, config, prefs, cloud):
"""Initialize the Alexa config."""
super().__init__(hass)
self._config = config
self._prefs = prefs
self._cloud = cloud
self._token = None
self._token_valid = None
self._cur_entity_prefs = prefs.alexa_entity_configs
self._alexa_sync_unsub = None
self._endpoint = None
prefs.async_listen_updates(self._async_prefs_updated)
hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated
)
@property
def enabled(self):
"""Return if Alexa is enabled."""
return self._prefs.alexa_enabled
@property
def supports_auth(self):
"""Return if config supports auth."""
return True
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.alexa_report_state
@property
def endpoint(self):
"""Endpoint for report state."""
if self._endpoint is None:
raise ValueError("No endpoint available. Fetch access token first")
return self._endpoint
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG, {})
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)
entity_configs = self._prefs.alexa_entity_configs
entity_config = entity_configs.get(entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
async def async_get_access_token(self):
"""Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow():
return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud)
body = await resp.json()
if resp.status == 400:
if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'):
raise RequireRelink
raise alexa_errors.NoTokenAvailable
self._token = body['access_token']
self._endpoint = body['event_endpoint']
self._token_valid = utcnow() + timedelta(seconds=body['expires_in'])
return self._token
async def _async_prefs_updated(self, prefs):
"""Handle updated preferences."""
if self.should_report_state != self.is_reporting_states:
if self.should_report_state:
await self.async_enable_proactive_mode()
else:
await self.async_disable_proactive_mode()
# If entity prefs are the same or we have filter in config.yaml,
# don't sync.
if (self._cur_entity_prefs is prefs.alexa_entity_configs or
not self._config[CONF_FILTER].empty_filter):
return
if self._alexa_sync_unsub:
self._alexa_sync_unsub()
self._alexa_sync_unsub = async_call_later(
self.hass, SYNC_DELAY, self._sync_prefs)
async def _sync_prefs(self, _now):
"""Sync the updated preferences to Alexa."""
self._alexa_sync_unsub = None
old_prefs = self._cur_entity_prefs
new_prefs = self._prefs.alexa_entity_configs
seen = set()
to_update = []
to_remove = []
for entity_id, info in old_prefs.items():
seen.add(entity_id)
old_expose = info.get(PREF_SHOULD_EXPOSE)
if entity_id in new_prefs:
new_expose = new_prefs[entity_id].get(PREF_SHOULD_EXPOSE)
else:
new_expose = None
if old_expose == new_expose:
continue
if new_expose:
to_update.append(entity_id)
else:
to_remove.append(entity_id)
# Now all the ones that are in new prefs but never were in old prefs
for entity_id, info in new_prefs.items():
if entity_id in seen:
continue
new_expose = info.get(PREF_SHOULD_EXPOSE)
if new_expose is None:
continue
# Only test if we should expose. It can never be a remove action,
# as it didn't exist in old prefs object.
if new_expose:
to_update.append(entity_id)
# We only set the prefs when update is successful, that way we will
# retry when next change comes in.
if await self._sync_helper(to_update, to_remove):
self._cur_entity_prefs = new_prefs
async def async_sync_entities(self):
"""Sync all entities to Alexa."""
to_update = []
to_remove = []
for entity in alexa_entities.async_get_entities(self.hass, self):
if self.should_expose(entity.entity_id):
to_update.append(entity.entity_id)
else:
to_remove.append(entity.entity_id)
return await self._sync_helper(to_update, to_remove)
async def _sync_helper(self, to_update, to_remove) -> bool:
"""Sync entities to Alexa.
Return boolean if it was successful.
"""
if not to_update and not to_remove:
return True
tasks = []
if to_update:
tasks.append(alexa_state_report.async_send_add_or_update_message(
self.hass, self, to_update
))
if to_remove:
tasks.append(alexa_state_report.async_send_delete_message(
self.hass, self, to_remove
))
try:
with async_timeout.timeout(10):
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
return True
except asyncio.TimeoutError:
_LOGGER.warning("Timeout trying to sync entitites to Alexa")
return False
except aiohttp.ClientError as err:
_LOGGER.warning("Error trying to sync entities to Alexa: %s", err)
return False
async def _handle_entity_registry_updated(self, event):
"""Handle when entity registry updated."""
if not self.enabled or not self._cloud.is_logged_in:
return
action = event.data['action']
entity_id = event.data['entity_id']
to_update = []
to_remove = []
if action == 'create' and self.should_expose(entity_id):
to_update.append(entity_id)
elif action == 'remove' and self.should_expose(entity_id):
to_remove.append(entity_id)
await self._sync_helper(to_update, to_remove)

View File

@ -2,257 +2,24 @@
import asyncio
from pathlib import Path
from typing import Any, Dict
from datetime import timedelta
import logging
import aiohttp
import async_timeout
from hass_nabucasa import cloud_api
from hass_nabucasa.client import CloudClient as Interface
from homeassistant.core import callback
from homeassistant.components.alexa import (
config as alexa_config,
errors as alexa_errors,
smart_home as alexa_sh,
entities as alexa_entities,
state_report as alexa_state_report,
)
from homeassistant.components.google_assistant import (
helpers as ga_h, smart_home as ga)
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers.event import async_call_later
from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers import entity_registry
from homeassistant.util.aiohttp import MockRequest
from homeassistant.util.dt import utcnow
from homeassistant.components.alexa import smart_home as alexa_sh
from . import utils
from .const import (
CONF_ENTITY_CONFIG, CONF_FILTER, DOMAIN, DISPATCHER_REMOTE_UPDATE,
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA, RequireRelink)
from . import utils, alexa_config, google_config
from .const import DISPATCHER_REMOTE_UPDATE
from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__)
# Time to wait when entity preferences have changed before syncing it to
# the cloud.
SYNC_DELAY = 1
class AlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration."""
def __init__(self, hass, config, prefs, cloud):
"""Initialize the Alexa config."""
super().__init__(hass)
self._config = config
self._prefs = prefs
self._cloud = cloud
self._token = None
self._token_valid = None
self._cur_entity_prefs = prefs.alexa_entity_configs
self._alexa_sync_unsub = None
self._endpoint = None
prefs.async_listen_updates(self._async_prefs_updated)
hass.bus.async_listen(
entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
self._handle_entity_registry_updated
)
@property
def enabled(self):
"""Return if Alexa is enabled."""
return self._prefs.alexa_enabled
@property
def supports_auth(self):
"""Return if config supports auth."""
return True
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.alexa_report_state
@property
def endpoint(self):
"""Endpoint for report state."""
if self._endpoint is None:
raise ValueError("No endpoint available. Fetch access token first")
return self._endpoint
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG, {})
def should_expose(self, entity_id):
"""If an entity should be exposed."""
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)
entity_configs = self._prefs.alexa_entity_configs
entity_config = entity_configs.get(entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
async def async_get_access_token(self):
"""Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow():
return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud)
body = await resp.json()
if resp.status == 400:
if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'):
raise RequireRelink
raise alexa_errors.NoTokenAvailable
self._token = body['access_token']
self._endpoint = body['event_endpoint']
self._token_valid = utcnow() + timedelta(seconds=body['expires_in'])
return self._token
async def _async_prefs_updated(self, prefs):
"""Handle updated preferences."""
if self.should_report_state != self.is_reporting_states:
if self.should_report_state:
await self.async_enable_proactive_mode()
else:
await self.async_disable_proactive_mode()
# If entity prefs are the same or we have filter in config.yaml,
# don't sync.
if (self._cur_entity_prefs is prefs.alexa_entity_configs or
not self._config[CONF_FILTER].empty_filter):
return
if self._alexa_sync_unsub:
self._alexa_sync_unsub()
self._alexa_sync_unsub = async_call_later(
self.hass, SYNC_DELAY, self._sync_prefs)
async def _sync_prefs(self, _now):
"""Sync the updated preferences to Alexa."""
self._alexa_sync_unsub = None
old_prefs = self._cur_entity_prefs
new_prefs = self._prefs.alexa_entity_configs
seen = set()
to_update = []
to_remove = []
for entity_id, info in old_prefs.items():
seen.add(entity_id)
old_expose = info.get(PREF_SHOULD_EXPOSE)
if entity_id in new_prefs:
new_expose = new_prefs[entity_id].get(PREF_SHOULD_EXPOSE)
else:
new_expose = None
if old_expose == new_expose:
continue
if new_expose:
to_update.append(entity_id)
else:
to_remove.append(entity_id)
# Now all the ones that are in new prefs but never were in old prefs
for entity_id, info in new_prefs.items():
if entity_id in seen:
continue
new_expose = info.get(PREF_SHOULD_EXPOSE)
if new_expose is None:
continue
# Only test if we should expose. It can never be a remove action,
# as it didn't exist in old prefs object.
if new_expose:
to_update.append(entity_id)
# We only set the prefs when update is successful, that way we will
# retry when next change comes in.
if await self._sync_helper(to_update, to_remove):
self._cur_entity_prefs = new_prefs
async def async_sync_entities(self):
"""Sync all entities to Alexa."""
to_update = []
to_remove = []
for entity in alexa_entities.async_get_entities(self.hass, self):
if self.should_expose(entity.entity_id):
to_update.append(entity.entity_id)
else:
to_remove.append(entity.entity_id)
return await self._sync_helper(to_update, to_remove)
async def _sync_helper(self, to_update, to_remove) -> bool:
"""Sync entities to Alexa.
Return boolean if it was successful.
"""
if not to_update and not to_remove:
return True
tasks = []
if to_update:
tasks.append(alexa_state_report.async_send_add_or_update_message(
self.hass, self, to_update
))
if to_remove:
tasks.append(alexa_state_report.async_send_delete_message(
self.hass, self, to_remove
))
try:
with async_timeout.timeout(10):
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
return True
except asyncio.TimeoutError:
_LOGGER.warning("Timeout trying to sync entitites to Alexa")
return False
except aiohttp.ClientError as err:
_LOGGER.warning("Error trying to sync entities to Alexa: %s", err)
return False
async def _handle_entity_registry_updated(self, event):
"""Handle when entity registry updated."""
if not self.enabled or not self._cloud.is_logged_in:
return
action = event.data['action']
entity_id = event.data['entity_id']
to_update = []
to_remove = []
if action == 'create' and self.should_expose(entity_id):
to_update.append(entity_id)
elif action == 'remove' and self.should_expose(entity_id):
to_remove.append(entity_id)
await self._sync_helper(to_update, to_remove)
class CloudClient(Interface):
@ -260,13 +27,14 @@ class CloudClient(Interface):
def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences,
websession: aiohttp.ClientSession,
alexa_cfg: Dict[str, Any], google_config: Dict[str, Any]):
alexa_user_config: Dict[str, Any],
google_user_config: Dict[str, Any]):
"""Initialize client interface to Cloud."""
self._hass = hass
self._prefs = prefs
self._websession = websession
self.google_user_config = google_config
self.alexa_user_config = alexa_cfg
self.google_user_config = google_user_config
self.alexa_user_config = alexa_user_config
self._alexa_config = None
self._google_config = None
self.cloud = None
@ -307,53 +75,22 @@ class CloudClient(Interface):
return self._prefs.remote_enabled
@property
def alexa_config(self) -> AlexaConfig:
def alexa_config(self) -> alexa_config.AlexaConfig:
"""Return Alexa config."""
if self._alexa_config is None:
self._alexa_config = AlexaConfig(
assert self.cloud is not None
self._alexa_config = alexa_config.AlexaConfig(
self._hass, self.alexa_user_config, self._prefs, self.cloud)
return self._alexa_config
@property
def google_config(self) -> ga_h.Config:
def google_config(self) -> google_config.CloudGoogleConfig:
"""Return Google config."""
if not self._google_config:
google_conf = self.google_user_config
def should_expose(entity):
"""If an entity should be exposed."""
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not google_conf['filter'].empty_filter:
return google_conf['filter'](entity.entity_id)
entity_configs = self.prefs.google_entity_configs
entity_config = entity_configs.get(entity.entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
def should_2fa(entity):
"""If an entity should be checked for 2FA."""
entity_configs = self.prefs.google_entity_configs
entity_config = entity_configs.get(entity.entity_id, {})
return not entity_config.get(
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
username = self._hass.data[DOMAIN].claims["cognito:username"]
self._google_config = ga_h.Config(
should_expose=should_expose,
should_2fa=should_2fa,
secure_devices_pin=self._prefs.google_secure_devices_pin,
entity_config=google_conf.get(CONF_ENTITY_CONFIG),
agent_user_id=username,
)
# Set it to the latest.
self._google_config.secure_devices_pin = \
self._prefs.google_secure_devices_pin
assert self.cloud is not None
self._google_config = google_config.CloudGoogleConfig(
self.google_user_config, self._prefs, self.cloud)
return self._google_config

View File

@ -0,0 +1,52 @@
"""Google config for Cloud."""
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.components.google_assistant.helpers import AbstractConfig
from .const import (
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, CONF_ENTITY_CONFIG,
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)
class CloudGoogleConfig(AbstractConfig):
"""HA Cloud Configuration for Google Assistant."""
def __init__(self, config, prefs, cloud):
"""Initialize the Alexa config."""
self._config = config
self._prefs = prefs
self._cloud = cloud
@property
def agent_user_id(self):
"""Return Agent User Id to use for query responses."""
return self._cloud.claims["cognito:username"]
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG)
@property
def secure_devices_pin(self):
"""Return entity config."""
return self._prefs.google_secure_devices_pin
def should_expose(self, state):
"""If an entity should be exposed."""
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
if not self._config['filter'].empty_filter:
return self._config['filter'](state.entity_id)
entity_configs = self._prefs.google_entity_configs
entity_config = entity_configs.get(state.entity_id, {})
return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
def should_2fa(self, state):
"""If an entity should be checked for 2FA."""
entity_configs = self._prefs.google_entity_configs
entity_config = entity_configs.get(state.entity_id, {})
return not entity_config.get(
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA)

View File

@ -17,24 +17,32 @@ from .const import (
from .error import SmartHomeError
class Config:
class AbstractConfig:
"""Hold the configuration for Google Assistant."""
def __init__(self, should_expose,
entity_config=None, secure_devices_pin=None,
agent_user_id=None, should_2fa=None):
"""Initialize the configuration."""
self.should_expose = should_expose
self.entity_config = entity_config or {}
self.secure_devices_pin = secure_devices_pin
self._should_2fa = should_2fa
@property
def agent_user_id(self):
"""Return Agent User Id to use for query responses."""
return None
# Agent User Id to use for query responses
self.agent_user_id = agent_user_id
@property
def entity_config(self):
"""Return entity config."""
return {}
@property
def secure_devices_pin(self):
"""Return entity config."""
return None
def should_expose(self, state) -> bool:
"""Return if entity should be exposed."""
raise NotImplementedError
def should_2fa(self, state):
"""If an entity should have 2FA checked."""
return self._should_2fa is None or self._should_2fa(state)
# pylint: disable=no-self-use
return True
class RequestData:

View File

@ -17,33 +17,50 @@ from .const import (
CONF_SECURE_DEVICES_PIN,
)
from .smart_home import async_handle_message
from .helpers import Config
from .helpers import AbstractConfig
_LOGGER = logging.getLogger(__name__)
@callback
def async_register_http(hass, cfg):
"""Register HTTP views for Google Assistant."""
expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS)
entity_config = cfg.get(CONF_ENTITY_CONFIG) or {}
secure_devices_pin = cfg.get(CONF_SECURE_DEVICES_PIN)
class GoogleConfig(AbstractConfig):
"""Config for manual setup of Google."""
def is_exposed(entity) -> bool:
"""Determine if an entity should be exposed to Google Assistant."""
if entity.attributes.get('view') is not None:
def __init__(self, config):
"""Initialize the config."""
self._config = config
@property
def agent_user_id(self):
"""Return Agent User Id to use for query responses."""
return None
@property
def entity_config(self):
"""Return entity config."""
return self._config.get(CONF_ENTITY_CONFIG, {})
@property
def secure_devices_pin(self):
"""Return entity config."""
return self._config.get(CONF_SECURE_DEVICES_PIN)
def should_expose(self, state) -> bool:
"""Return if entity should be exposed."""
expose_by_default = self._config.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = self._config.get(CONF_EXPOSED_DOMAINS)
if state.attributes.get('view') is not None:
# Ignore entities that are views
return False
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
explicit_expose = \
entity_config.get(entity.entity_id, {}).get(CONF_EXPOSE)
self.entity_config.get(state.entity_id, {}).get(CONF_EXPOSE)
domain_exposed_by_default = \
expose_by_default and entity.domain in exposed_domains
expose_by_default and state.domain in exposed_domains
# Expose an entity if the entity's domain is exposed by default and
# the configuration doesn't explicitly exclude it from being
@ -53,13 +70,15 @@ def async_register_http(hass, cfg):
return is_default_exposed or explicit_expose
config = Config(
should_expose=is_exposed,
entity_config=entity_config,
secure_devices_pin=secure_devices_pin
)
def should_2fa(self, state):
"""If an entity should have 2FA checked."""
return True
hass.http.register_view(GoogleAssistantView(config))
@callback
def async_register_http(hass, cfg):
"""Register HTTP views for Google Assistant."""
hass.http.register_view(GoogleAssistantView(GoogleConfig(cfg)))
class GoogleAssistantView(HomeAssistantView):

View File

@ -1,24 +1,22 @@
"""Tests for the cloud component."""
from unittest.mock import patch
from homeassistant.setup import async_setup_component
from homeassistant.components import cloud
from homeassistant.components.cloud import const
from jose import jwt
from tests.common import mock_coro
def mock_cloud(hass, config={}):
async def mock_cloud(hass, config=None):
"""Mock cloud."""
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
assert hass.loop.run_until_complete(async_setup_component(
hass, cloud.DOMAIN, {
'cloud': config
}))
hass.data[cloud.DOMAIN]._decode_claims = \
lambda token: jwt.get_unverified_claims(token)
assert await async_setup_component(
hass, cloud.DOMAIN, {
'cloud': config or {}
})
cloud_inst = hass.data['cloud']
with patch('hass_nabucasa.Cloud.run_executor', return_value=mock_coro()):
await cloud_inst.start()
def mock_cloud_prefs(hass, prefs={}):

View File

@ -18,7 +18,7 @@ def mock_user_data():
@pytest.fixture
def mock_cloud_fixture(hass):
"""Fixture for cloud component."""
mock_cloud(hass)
hass.loop.run_until_complete(mock_cloud(hass))
return mock_cloud_prefs(hass)

View File

@ -9,7 +9,7 @@ import pytest
from homeassistant.core import State
from homeassistant.setup import async_setup_component
from homeassistant.components.cloud import (
DOMAIN, ALEXA_SCHEMA, client)
DOMAIN, ALEXA_SCHEMA, alexa_config)
from homeassistant.components.cloud.const import (
PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE)
from homeassistant.util.dt import utcnow
@ -17,11 +17,11 @@ from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
from tests.components.alexa import test_smart_home as test_alexa
from tests.common import mock_coro, async_fire_time_changed
from . import mock_cloud_prefs
from . import mock_cloud_prefs, mock_cloud
@pytest.fixture
def mock_cloud():
def mock_cloud_inst():
"""Mock cloud class."""
return MagicMock(subscription_expired=False)
@ -29,10 +29,7 @@ def mock_cloud():
@pytest.fixture
async def mock_cloud_setup(hass):
"""Set up the cloud."""
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
assert await async_setup_component(hass, 'cloud', {
'cloud': {}
})
await mock_cloud(hass)
@pytest.fixture
@ -52,24 +49,20 @@ async def test_handler_alexa(hass):
hass.states.async_set(
'switch.test2', 'on', {'friendly_name': "Test switch 2"})
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
setup = await async_setup_component(hass, 'cloud', {
'cloud': {
'alexa': {
'filter': {
'exclude_entities': 'switch.test2'
},
'entity_config': {
'switch.test': {
'name': 'Config name',
'description': 'Config description',
'display_categories': 'LIGHT'
}
}
await mock_cloud(hass, {
'alexa': {
'filter': {
'exclude_entities': 'switch.test2'
},
'entity_config': {
'switch.test': {
'name': 'Config name',
'description': 'Config description',
'display_categories': 'LIGHT'
}
}
})
assert setup
}
})
mock_cloud_prefs(hass)
cloud = hass.data['cloud']
@ -110,24 +103,20 @@ async def test_handler_google_actions(hass):
hass.states.async_set(
'group.all_locks', 'on', {'friendly_name': "Evil locks"})
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
setup = await async_setup_component(hass, 'cloud', {
'cloud': {
'google_actions': {
'filter': {
'exclude_entities': 'switch.test2'
},
'entity_config': {
'switch.test': {
'name': 'Config name',
'aliases': 'Config alias',
'room': 'living room'
}
}
await mock_cloud(hass, {
'google_actions': {
'filter': {
'exclude_entities': 'switch.test2'
},
'entity_config': {
'switch.test': {
'name': 'Config name',
'aliases': 'Config alias',
'room': 'living room'
}
}
})
assert setup
}
})
mock_cloud_prefs(hass)
cloud = hass.data['cloud']
@ -265,7 +254,7 @@ async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs):
await cloud_prefs.async_update(alexa_entity_configs={
'light.kitchen': entity_conf
})
conf = client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
conf = alexa_config.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
assert not conf.should_expose('light.kitchen')
entity_conf['should_expose'] = True
@ -274,7 +263,7 @@ async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs):
async def test_alexa_config_report_state(hass, cloud_prefs):
"""Test Alexa config should expose using prefs."""
conf = client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
conf = alexa_config.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
assert cloud_prefs.alexa_report_state is False
assert conf.should_report_state is False
@ -307,9 +296,9 @@ def patch_sync_helper():
to_remove = []
with patch(
'homeassistant.components.cloud.client.SYNC_DELAY', 0
'homeassistant.components.cloud.alexa_config.SYNC_DELAY', 0
), patch(
'homeassistant.components.cloud.client.AlexaConfig._sync_helper',
'homeassistant.components.cloud.alexa_config.AlexaConfig._sync_helper',
side_effect=mock_coro
) as mock_helper:
yield to_update, to_remove
@ -321,7 +310,7 @@ def patch_sync_helper():
async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs):
"""Test Alexa config responds to updating exposed entities."""
client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
alexa_config.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
with patch_sync_helper() as (to_update, to_remove):
await cloud_prefs.async_update_alexa_entity_config(
@ -354,7 +343,8 @@ async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs):
async def test_alexa_entity_registry_sync(hass, mock_cloud_login, cloud_prefs):
"""Test Alexa config responds to entity registry."""
client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, hass.data['cloud'])
alexa_config.AlexaConfig(
hass, ALEXA_SCHEMA({}), cloud_prefs, hass.data['cloud'])
with patch_sync_helper() as (to_update, to_remove):
hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {

View File

@ -14,10 +14,11 @@ from homeassistant.components.cloud.const import (
PREF_ENABLE_GOOGLE, PREF_ENABLE_ALEXA, PREF_GOOGLE_SECURE_DEVICES_PIN,
DOMAIN)
from homeassistant.components.google_assistant.helpers import (
GoogleEntity, Config)
GoogleEntity)
from homeassistant.components.alexa.entities import LightCapabilities
from tests.common import mock_coro
from tests.components.google_assistant import MockConfig
from . import mock_cloud, mock_cloud_prefs
@ -45,7 +46,7 @@ def mock_cloud_login(hass, setup_api):
@pytest.fixture(autouse=True)
def setup_api(hass, aioclient_mock):
"""Initialize HTTP API."""
mock_cloud(hass, {
hass.loop.run_until_complete(mock_cloud(hass, {
'mode': 'development',
'cognito_client_id': 'cognito_client_id',
'user_pool_id': 'user_pool_id',
@ -63,7 +64,7 @@ def setup_api(hass, aioclient_mock):
'include_entities': ['light.kitchen', 'switch.ac']
}
}
})
}))
return mock_cloud_prefs(hass)
@ -709,9 +710,10 @@ async def test_list_google_entities(
hass, hass_ws_client, setup_api, mock_cloud_login):
"""Test that we can list Google entities."""
client = await hass_ws_client(hass)
entity = GoogleEntity(hass, Config(lambda *_: False), State(
'light.kitchen', 'on'
))
entity = GoogleEntity(
hass, MockConfig(should_expose=lambda *_: False), State(
'light.kitchen', 'on'
))
with patch('homeassistant.components.google_assistant.helpers'
'.async_get_entities', return_value=[entity]):
await client.send_json({

View File

@ -1,6 +1,33 @@
"""Tests for the Google Assistant integration."""
from homeassistant.components.google_assistant import helpers
class MockConfig(helpers.AbstractConfig):
"""Fake config that always exposes everything."""
def __init__(self, *, secure_devices_pin=None, should_expose=None,
entity_config=None):
"""Initialize config."""
self._should_expose = should_expose
self._secure_devices_pin = secure_devices_pin
self._entity_config = entity_config or {}
@property
def secure_devices_pin(self):
"""Return secure devices pin."""
return self._secure_devices_pin
@property
def entity_config(self):
"""Return secure devices pin."""
return self._entity_config
def should_expose(self, state):
"""Expose it all."""
return self._should_expose is None or self._should_expose(state)
BASIC_CONFIG = MockConfig()
DEMO_DEVICES = [{
'id':

View File

@ -11,7 +11,7 @@ from homeassistant.components.climate.const import (
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
)
from homeassistant.components.google_assistant import (
const, trait, helpers, smart_home as sh,
const, trait, smart_home as sh,
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
from homeassistant.components.demo.binary_sensor import DemoBinarySensor
from homeassistant.components.demo.cover import DemoCover
@ -23,9 +23,8 @@ from homeassistant.helpers import device_registry
from tests.common import (mock_device_registry, mock_registry,
mock_area_registry, mock_coro)
BASIC_CONFIG = helpers.Config(
should_expose=lambda state: True,
)
from . import BASIC_CONFIG, MockConfig
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
@ -57,7 +56,7 @@ async def test_sync_message(hass):
# Excluded via config
hass.states.async_set('light.not_expose', 'on')
config = helpers.Config(
config = MockConfig(
should_expose=lambda state: state.entity_id != 'light.not_expose',
entity_config={
'light.demo_light': {
@ -145,7 +144,7 @@ async def test_sync_in_area(hass, registries):
light.entity_id = entity.entity_id
await light.async_update_ha_state()
config = helpers.Config(
config = MockConfig(
should_expose=lambda _: True,
entity_config={}
)

View File

@ -29,10 +29,8 @@ from homeassistant.const import (
from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE
from homeassistant.util import color
from tests.common import async_mock_service, mock_coro
from . import BASIC_CONFIG, MockConfig
BASIC_CONFIG = helpers.Config(
should_expose=lambda state: True,
)
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
@ -42,8 +40,7 @@ BASIC_DATA = helpers.RequestData(
REQ_ID,
)
PIN_CONFIG = helpers.Config(
should_expose=lambda state: True,
PIN_CONFIG = MockConfig(
secure_devices_pin='1234'
)
@ -927,7 +924,7 @@ async def test_lock_unlock_unlock(hass):
# Test with 2FA override
with patch('homeassistant.components.google_assistant.helpers'
'.Config.should_2fa', return_value=False):
'.AbstractConfig.should_2fa', return_value=False):
await trt.execute(
trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': False}, {})
assert len(calls) == 2