mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Clean up Google Config (#24663)
* Clean up Google Config * Lint * pylint * pylint2
This commit is contained in:
parent
d468d0f71b
commit
78b7ed0ebe
244
homeassistant/components/cloud/alexa_config.py
Normal file
244
homeassistant/components/cloud/alexa_config.py
Normal 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)
|
@ -2,257 +2,24 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
|
||||||
from hass_nabucasa import cloud_api
|
|
||||||
from hass_nabucasa.client import CloudClient as Interface
|
from hass_nabucasa.client import CloudClient as Interface
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.components.alexa import (
|
from homeassistant.components.google_assistant import smart_home as ga
|
||||||
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.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers import entity_registry
|
|
||||||
from homeassistant.util.aiohttp import MockRequest
|
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 . import utils, alexa_config, google_config
|
||||||
from .const import (
|
from .const import DISPATCHER_REMOTE_UPDATE
|
||||||
CONF_ENTITY_CONFIG, CONF_FILTER, DOMAIN, DISPATCHER_REMOTE_UPDATE,
|
|
||||||
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
|
|
||||||
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA, RequireRelink)
|
|
||||||
from .prefs import CloudPreferences
|
from .prefs import CloudPreferences
|
||||||
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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):
|
class CloudClient(Interface):
|
||||||
@ -260,13 +27,14 @@ class CloudClient(Interface):
|
|||||||
|
|
||||||
def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences,
|
def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences,
|
||||||
websession: aiohttp.ClientSession,
|
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."""
|
"""Initialize client interface to Cloud."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._prefs = prefs
|
self._prefs = prefs
|
||||||
self._websession = websession
|
self._websession = websession
|
||||||
self.google_user_config = google_config
|
self.google_user_config = google_user_config
|
||||||
self.alexa_user_config = alexa_cfg
|
self.alexa_user_config = alexa_user_config
|
||||||
self._alexa_config = None
|
self._alexa_config = None
|
||||||
self._google_config = None
|
self._google_config = None
|
||||||
self.cloud = None
|
self.cloud = None
|
||||||
@ -307,53 +75,22 @@ class CloudClient(Interface):
|
|||||||
return self._prefs.remote_enabled
|
return self._prefs.remote_enabled
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alexa_config(self) -> AlexaConfig:
|
def alexa_config(self) -> alexa_config.AlexaConfig:
|
||||||
"""Return Alexa config."""
|
"""Return Alexa config."""
|
||||||
if self._alexa_config is None:
|
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)
|
self._hass, self.alexa_user_config, self._prefs, self.cloud)
|
||||||
|
|
||||||
return self._alexa_config
|
return self._alexa_config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def google_config(self) -> ga_h.Config:
|
def google_config(self) -> google_config.CloudGoogleConfig:
|
||||||
"""Return Google config."""
|
"""Return Google config."""
|
||||||
if not self._google_config:
|
if not self._google_config:
|
||||||
google_conf = self.google_user_config
|
assert self.cloud is not None
|
||||||
|
self._google_config = google_config.CloudGoogleConfig(
|
||||||
def should_expose(entity):
|
self.google_user_config, self._prefs, self.cloud)
|
||||||
"""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
|
|
||||||
|
|
||||||
return self._google_config
|
return self._google_config
|
||||||
|
|
||||||
|
52
homeassistant/components/cloud/google_config.py
Normal file
52
homeassistant/components/cloud/google_config.py
Normal 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)
|
@ -17,24 +17,32 @@ from .const import (
|
|||||||
from .error import SmartHomeError
|
from .error import SmartHomeError
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class AbstractConfig:
|
||||||
"""Hold the configuration for Google Assistant."""
|
"""Hold the configuration for Google Assistant."""
|
||||||
|
|
||||||
def __init__(self, should_expose,
|
@property
|
||||||
entity_config=None, secure_devices_pin=None,
|
def agent_user_id(self):
|
||||||
agent_user_id=None, should_2fa=None):
|
"""Return Agent User Id to use for query responses."""
|
||||||
"""Initialize the configuration."""
|
return None
|
||||||
self.should_expose = should_expose
|
|
||||||
self.entity_config = entity_config or {}
|
|
||||||
self.secure_devices_pin = secure_devices_pin
|
|
||||||
self._should_2fa = should_2fa
|
|
||||||
|
|
||||||
# Agent User Id to use for query responses
|
@property
|
||||||
self.agent_user_id = agent_user_id
|
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):
|
def should_2fa(self, state):
|
||||||
"""If an entity should have 2FA checked."""
|
"""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:
|
class RequestData:
|
||||||
|
@ -17,33 +17,50 @@ from .const import (
|
|||||||
CONF_SECURE_DEVICES_PIN,
|
CONF_SECURE_DEVICES_PIN,
|
||||||
)
|
)
|
||||||
from .smart_home import async_handle_message
|
from .smart_home import async_handle_message
|
||||||
from .helpers import Config
|
from .helpers import AbstractConfig
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
class GoogleConfig(AbstractConfig):
|
||||||
def async_register_http(hass, cfg):
|
"""Config for manual setup of Google."""
|
||||||
"""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)
|
|
||||||
|
|
||||||
def is_exposed(entity) -> bool:
|
def __init__(self, config):
|
||||||
"""Determine if an entity should be exposed to Google Assistant."""
|
"""Initialize the config."""
|
||||||
if entity.attributes.get('view') is not None:
|
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
|
# Ignore entities that are views
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
if state.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
explicit_expose = \
|
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 = \
|
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
|
# Expose an entity if the entity's domain is exposed by default and
|
||||||
# the configuration doesn't explicitly exclude it from being
|
# 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
|
return is_default_exposed or explicit_expose
|
||||||
|
|
||||||
config = Config(
|
def should_2fa(self, state):
|
||||||
should_expose=is_exposed,
|
"""If an entity should have 2FA checked."""
|
||||||
entity_config=entity_config,
|
return True
|
||||||
secure_devices_pin=secure_devices_pin
|
|
||||||
)
|
|
||||||
|
|
||||||
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):
|
class GoogleAssistantView(HomeAssistantView):
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
"""Tests for the cloud component."""
|
"""Tests for the cloud component."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components import cloud
|
from homeassistant.components import cloud
|
||||||
from homeassistant.components.cloud import const
|
from homeassistant.components.cloud import const
|
||||||
|
|
||||||
from jose import jwt
|
|
||||||
|
|
||||||
from tests.common import mock_coro
|
from tests.common import mock_coro
|
||||||
|
|
||||||
|
|
||||||
def mock_cloud(hass, config={}):
|
async def mock_cloud(hass, config=None):
|
||||||
"""Mock cloud."""
|
"""Mock cloud."""
|
||||||
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
|
assert await async_setup_component(
|
||||||
assert hass.loop.run_until_complete(async_setup_component(
|
|
||||||
hass, cloud.DOMAIN, {
|
hass, cloud.DOMAIN, {
|
||||||
'cloud': config
|
'cloud': config or {}
|
||||||
}))
|
})
|
||||||
|
cloud_inst = hass.data['cloud']
|
||||||
hass.data[cloud.DOMAIN]._decode_claims = \
|
with patch('hass_nabucasa.Cloud.run_executor', return_value=mock_coro()):
|
||||||
lambda token: jwt.get_unverified_claims(token)
|
await cloud_inst.start()
|
||||||
|
|
||||||
|
|
||||||
def mock_cloud_prefs(hass, prefs={}):
|
def mock_cloud_prefs(hass, prefs={}):
|
||||||
|
@ -18,7 +18,7 @@ def mock_user_data():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_cloud_fixture(hass):
|
def mock_cloud_fixture(hass):
|
||||||
"""Fixture for cloud component."""
|
"""Fixture for cloud component."""
|
||||||
mock_cloud(hass)
|
hass.loop.run_until_complete(mock_cloud(hass))
|
||||||
return mock_cloud_prefs(hass)
|
return mock_cloud_prefs(hass)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import pytest
|
|||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components.cloud import (
|
from homeassistant.components.cloud import (
|
||||||
DOMAIN, ALEXA_SCHEMA, client)
|
DOMAIN, ALEXA_SCHEMA, alexa_config)
|
||||||
from homeassistant.components.cloud.const import (
|
from homeassistant.components.cloud.const import (
|
||||||
PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE)
|
PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE)
|
||||||
from homeassistant.util.dt import utcnow
|
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.components.alexa import test_smart_home as test_alexa
|
||||||
from tests.common import mock_coro, async_fire_time_changed
|
from tests.common import mock_coro, async_fire_time_changed
|
||||||
|
|
||||||
from . import mock_cloud_prefs
|
from . import mock_cloud_prefs, mock_cloud
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_cloud():
|
def mock_cloud_inst():
|
||||||
"""Mock cloud class."""
|
"""Mock cloud class."""
|
||||||
return MagicMock(subscription_expired=False)
|
return MagicMock(subscription_expired=False)
|
||||||
|
|
||||||
@ -29,10 +29,7 @@ def mock_cloud():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def mock_cloud_setup(hass):
|
async def mock_cloud_setup(hass):
|
||||||
"""Set up the cloud."""
|
"""Set up the cloud."""
|
||||||
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
|
await mock_cloud(hass)
|
||||||
assert await async_setup_component(hass, 'cloud', {
|
|
||||||
'cloud': {}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -52,9 +49,7 @@ async def test_handler_alexa(hass):
|
|||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
'switch.test2', 'on', {'friendly_name': "Test switch 2"})
|
'switch.test2', 'on', {'friendly_name': "Test switch 2"})
|
||||||
|
|
||||||
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
|
await mock_cloud(hass, {
|
||||||
setup = await async_setup_component(hass, 'cloud', {
|
|
||||||
'cloud': {
|
|
||||||
'alexa': {
|
'alexa': {
|
||||||
'filter': {
|
'filter': {
|
||||||
'exclude_entities': 'switch.test2'
|
'exclude_entities': 'switch.test2'
|
||||||
@ -67,9 +62,7 @@ async def test_handler_alexa(hass):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
assert setup
|
|
||||||
|
|
||||||
mock_cloud_prefs(hass)
|
mock_cloud_prefs(hass)
|
||||||
cloud = hass.data['cloud']
|
cloud = hass.data['cloud']
|
||||||
@ -110,9 +103,7 @@ async def test_handler_google_actions(hass):
|
|||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
'group.all_locks', 'on', {'friendly_name': "Evil locks"})
|
'group.all_locks', 'on', {'friendly_name': "Evil locks"})
|
||||||
|
|
||||||
with patch('hass_nabucasa.Cloud.start', return_value=mock_coro()):
|
await mock_cloud(hass, {
|
||||||
setup = await async_setup_component(hass, 'cloud', {
|
|
||||||
'cloud': {
|
|
||||||
'google_actions': {
|
'google_actions': {
|
||||||
'filter': {
|
'filter': {
|
||||||
'exclude_entities': 'switch.test2'
|
'exclude_entities': 'switch.test2'
|
||||||
@ -125,9 +116,7 @@ async def test_handler_google_actions(hass):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
assert setup
|
|
||||||
|
|
||||||
mock_cloud_prefs(hass)
|
mock_cloud_prefs(hass)
|
||||||
cloud = hass.data['cloud']
|
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={
|
await cloud_prefs.async_update(alexa_entity_configs={
|
||||||
'light.kitchen': entity_conf
|
'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')
|
assert not conf.should_expose('light.kitchen')
|
||||||
entity_conf['should_expose'] = True
|
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):
|
async def test_alexa_config_report_state(hass, cloud_prefs):
|
||||||
"""Test Alexa config should expose using 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 cloud_prefs.alexa_report_state is False
|
||||||
assert conf.should_report_state is False
|
assert conf.should_report_state is False
|
||||||
@ -307,9 +296,9 @@ def patch_sync_helper():
|
|||||||
to_remove = []
|
to_remove = []
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'homeassistant.components.cloud.client.SYNC_DELAY', 0
|
'homeassistant.components.cloud.alexa_config.SYNC_DELAY', 0
|
||||||
), patch(
|
), patch(
|
||||||
'homeassistant.components.cloud.client.AlexaConfig._sync_helper',
|
'homeassistant.components.cloud.alexa_config.AlexaConfig._sync_helper',
|
||||||
side_effect=mock_coro
|
side_effect=mock_coro
|
||||||
) as mock_helper:
|
) as mock_helper:
|
||||||
yield to_update, to_remove
|
yield to_update, to_remove
|
||||||
@ -321,7 +310,7 @@ def patch_sync_helper():
|
|||||||
|
|
||||||
async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs):
|
async def test_alexa_update_expose_trigger_sync(hass, cloud_prefs):
|
||||||
"""Test Alexa config responds to updating exposed entities."""
|
"""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):
|
with patch_sync_helper() as (to_update, to_remove):
|
||||||
await cloud_prefs.async_update_alexa_entity_config(
|
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):
|
async def test_alexa_entity_registry_sync(hass, mock_cloud_login, cloud_prefs):
|
||||||
"""Test Alexa config responds to entity registry."""
|
"""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):
|
with patch_sync_helper() as (to_update, to_remove):
|
||||||
hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {
|
hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {
|
||||||
|
@ -14,10 +14,11 @@ from homeassistant.components.cloud.const import (
|
|||||||
PREF_ENABLE_GOOGLE, PREF_ENABLE_ALEXA, PREF_GOOGLE_SECURE_DEVICES_PIN,
|
PREF_ENABLE_GOOGLE, PREF_ENABLE_ALEXA, PREF_GOOGLE_SECURE_DEVICES_PIN,
|
||||||
DOMAIN)
|
DOMAIN)
|
||||||
from homeassistant.components.google_assistant.helpers import (
|
from homeassistant.components.google_assistant.helpers import (
|
||||||
GoogleEntity, Config)
|
GoogleEntity)
|
||||||
from homeassistant.components.alexa.entities import LightCapabilities
|
from homeassistant.components.alexa.entities import LightCapabilities
|
||||||
|
|
||||||
from tests.common import mock_coro
|
from tests.common import mock_coro
|
||||||
|
from tests.components.google_assistant import MockConfig
|
||||||
|
|
||||||
from . import mock_cloud, mock_cloud_prefs
|
from . import mock_cloud, mock_cloud_prefs
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ def mock_cloud_login(hass, setup_api):
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_api(hass, aioclient_mock):
|
def setup_api(hass, aioclient_mock):
|
||||||
"""Initialize HTTP API."""
|
"""Initialize HTTP API."""
|
||||||
mock_cloud(hass, {
|
hass.loop.run_until_complete(mock_cloud(hass, {
|
||||||
'mode': 'development',
|
'mode': 'development',
|
||||||
'cognito_client_id': 'cognito_client_id',
|
'cognito_client_id': 'cognito_client_id',
|
||||||
'user_pool_id': 'user_pool_id',
|
'user_pool_id': 'user_pool_id',
|
||||||
@ -63,7 +64,7 @@ def setup_api(hass, aioclient_mock):
|
|||||||
'include_entities': ['light.kitchen', 'switch.ac']
|
'include_entities': ['light.kitchen', 'switch.ac']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
return mock_cloud_prefs(hass)
|
return mock_cloud_prefs(hass)
|
||||||
|
|
||||||
|
|
||||||
@ -709,7 +710,8 @@ async def test_list_google_entities(
|
|||||||
hass, hass_ws_client, setup_api, mock_cloud_login):
|
hass, hass_ws_client, setup_api, mock_cloud_login):
|
||||||
"""Test that we can list Google entities."""
|
"""Test that we can list Google entities."""
|
||||||
client = await hass_ws_client(hass)
|
client = await hass_ws_client(hass)
|
||||||
entity = GoogleEntity(hass, Config(lambda *_: False), State(
|
entity = GoogleEntity(
|
||||||
|
hass, MockConfig(should_expose=lambda *_: False), State(
|
||||||
'light.kitchen', 'on'
|
'light.kitchen', 'on'
|
||||||
))
|
))
|
||||||
with patch('homeassistant.components.google_assistant.helpers'
|
with patch('homeassistant.components.google_assistant.helpers'
|
||||||
|
@ -1,6 +1,33 @@
|
|||||||
|
|
||||||
|
|
||||||
"""Tests for the Google Assistant integration."""
|
"""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 = [{
|
DEMO_DEVICES = [{
|
||||||
'id':
|
'id':
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.climate.const import (
|
|||||||
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
|
ATTR_MIN_TEMP, ATTR_MAX_TEMP, STATE_HEAT, SUPPORT_OPERATION_MODE
|
||||||
)
|
)
|
||||||
from homeassistant.components.google_assistant import (
|
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)
|
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
|
||||||
from homeassistant.components.demo.binary_sensor import DemoBinarySensor
|
from homeassistant.components.demo.binary_sensor import DemoBinarySensor
|
||||||
from homeassistant.components.demo.cover import DemoCover
|
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,
|
from tests.common import (mock_device_registry, mock_registry,
|
||||||
mock_area_registry, mock_coro)
|
mock_area_registry, mock_coro)
|
||||||
|
|
||||||
BASIC_CONFIG = helpers.Config(
|
from . import BASIC_CONFIG, MockConfig
|
||||||
should_expose=lambda state: True,
|
|
||||||
)
|
|
||||||
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
|
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ async def test_sync_message(hass):
|
|||||||
# Excluded via config
|
# Excluded via config
|
||||||
hass.states.async_set('light.not_expose', 'on')
|
hass.states.async_set('light.not_expose', 'on')
|
||||||
|
|
||||||
config = helpers.Config(
|
config = MockConfig(
|
||||||
should_expose=lambda state: state.entity_id != 'light.not_expose',
|
should_expose=lambda state: state.entity_id != 'light.not_expose',
|
||||||
entity_config={
|
entity_config={
|
||||||
'light.demo_light': {
|
'light.demo_light': {
|
||||||
@ -145,7 +144,7 @@ async def test_sync_in_area(hass, registries):
|
|||||||
light.entity_id = entity.entity_id
|
light.entity_id = entity.entity_id
|
||||||
await light.async_update_ha_state()
|
await light.async_update_ha_state()
|
||||||
|
|
||||||
config = helpers.Config(
|
config = MockConfig(
|
||||||
should_expose=lambda _: True,
|
should_expose=lambda _: True,
|
||||||
entity_config={}
|
entity_config={}
|
||||||
)
|
)
|
||||||
|
@ -29,10 +29,8 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE
|
from homeassistant.core import State, DOMAIN as HA_DOMAIN, EVENT_CALL_SERVICE
|
||||||
from homeassistant.util import color
|
from homeassistant.util import color
|
||||||
from tests.common import async_mock_service, mock_coro
|
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'
|
REQ_ID = 'ff36a3cc-ec34-11e6-b1a0-64510650abcf'
|
||||||
|
|
||||||
@ -42,8 +40,7 @@ BASIC_DATA = helpers.RequestData(
|
|||||||
REQ_ID,
|
REQ_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
PIN_CONFIG = helpers.Config(
|
PIN_CONFIG = MockConfig(
|
||||||
should_expose=lambda state: True,
|
|
||||||
secure_devices_pin='1234'
|
secure_devices_pin='1234'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -927,7 +924,7 @@ async def test_lock_unlock_unlock(hass):
|
|||||||
|
|
||||||
# Test with 2FA override
|
# Test with 2FA override
|
||||||
with patch('homeassistant.components.google_assistant.helpers'
|
with patch('homeassistant.components.google_assistant.helpers'
|
||||||
'.Config.should_2fa', return_value=False):
|
'.AbstractConfig.should_2fa', return_value=False):
|
||||||
await trt.execute(
|
await trt.execute(
|
||||||
trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': False}, {})
|
trait.COMMAND_LOCKUNLOCK, BASIC_DATA, {'lock': False}, {})
|
||||||
assert len(calls) == 2
|
assert len(calls) == 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user