Fix Alexa Report State (#26305)

* Fix Alexa Report State

* Forgot to save a file
This commit is contained in:
Paulus Schoutsen 2019-08-30 18:34:40 -07:00 committed by Andrew Sayre
parent 37a3d5fd85
commit 7b05ede297
6 changed files with 81 additions and 10 deletions

View file

@ -56,6 +56,11 @@ class Auth:
return await self._async_request_new_token(lwa_params) return await self._async_request_new_token(lwa_params)
@callback
def async_invalidate_access_token(self):
"""Invalidate access token."""
self._prefs[STORAGE_ACCESS_TOKEN] = None
async def async_get_access_token(self): async def async_get_access_token(self):
"""Perform access token or token refresh request.""" """Perform access token or token refresh request."""
async with self._get_token_lock: async with self._get_token_lock:

View file

@ -1,4 +1,6 @@
"""Config helpers for Alexa.""" """Config helpers for Alexa."""
from homeassistant.core import callback
from .state_report import async_enable_proactive_mode from .state_report import async_enable_proactive_mode
@ -55,11 +57,17 @@ class AbstractConfig:
unsub_func() unsub_func()
self._unsub_proactive_report = None self._unsub_proactive_report = None
@callback
def should_expose(self, entity_id): def should_expose(self, entity_id):
"""If an entity should be exposed.""" """If an entity should be exposed."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
return False return False
@callback
def async_invalidate_access_token(self):
"""Invalidate access token."""
raise NotImplementedError
async def async_get_access_token(self): async def async_get_access_token(self):
"""Get an access token.""" """Get an access token."""
raise NotImplementedError raise NotImplementedError

View file

@ -57,6 +57,11 @@ class AlexaConfig(AbstractConfig):
"""If an entity should be exposed.""" """If an entity should be exposed."""
return self._config[CONF_FILTER](entity_id) return self._config[CONF_FILTER](entity_id)
@core.callback
def async_invalidate_access_token(self):
"""Invalidate access token."""
self._auth.async_invalidate_access_token()
async def async_get_access_token(self): async def async_get_access_token(self):
"""Get an access token.""" """Get an access token."""
return await self._auth.async_get_access_token() return await self._auth.async_get_access_token()

View file

@ -51,7 +51,9 @@ async def async_enable_proactive_mode(hass, smart_home_config):
) )
async def async_send_changereport_message(hass, config, alexa_entity): async def async_send_changereport_message(
hass, config, alexa_entity, *, invalidate_access_token=True
):
"""Send a ChangeReport message for an Alexa entity. """Send a ChangeReport message for an Alexa entity.
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-with-changereport-events https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-with-changereport-events
@ -88,21 +90,30 @@ async def async_send_changereport_message(hass, config, alexa_entity):
except (asyncio.TimeoutError, aiohttp.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout sending report to Alexa.") _LOGGER.error("Timeout sending report to Alexa.")
return None return
response_text = await response.text() response_text = await response.text()
_LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Sent: %s", json.dumps(message_serialized))
_LOGGER.debug("Received (%s): %s", response.status, response_text) _LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status != 202: if response.status == 202 and not invalidate_access_token:
response_json = json.loads(response_text) return
_LOGGER.error(
"Error when sending ChangeReport to Alexa: %s: %s", response_json = json.loads(response_text)
response_json["payload"]["code"],
response_json["payload"]["description"], if response_json["payload"]["code"] == "INVALID_ACCESS_TOKEN_EXCEPTION":
config.async_invalidate_access_token()
return await async_send_changereport_message(
hass, config, alexa_entity, invalidate_access_token=False
) )
_LOGGER.error(
"Error when sending ChangeReport to Alexa: %s: %s",
response_json["payload"]["code"],
response_json["payload"]["description"],
)
async def async_send_add_or_update_message(hass, config, entity_ids): async def async_send_add_or_update_message(hass, config, entity_ids):
"""Send an AddOrUpdateReport message for entities. """Send an AddOrUpdateReport message for entities.

View file

@ -7,6 +7,7 @@ import aiohttp
import async_timeout import async_timeout
from hass_nabucasa import cloud_api from hass_nabucasa import cloud_api
from homeassistant.core import callback
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
@ -95,9 +96,14 @@ class AlexaConfig(alexa_config.AbstractConfig):
entity_config = entity_configs.get(entity_id, {}) entity_config = entity_configs.get(entity_id, {})
return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) return entity_config.get(PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
@callback
def async_invalidate_access_token(self):
"""Invalidate access token."""
self._token_valid = None
async def async_get_access_token(self): async def async_get_access_token(self):
"""Get an access token.""" """Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow(): if self._token_valid is not None and self._token_valid > utcnow():
return self._token return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud) resp = await cloud_api.async_alexa_access_token(self._cloud)

View file

@ -1,6 +1,6 @@
"""Test Alexa config.""" """Test Alexa config."""
import contextlib import contextlib
from unittest.mock import patch from unittest.mock import patch, Mock
from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -43,6 +43,42 @@ async def test_alexa_config_report_state(hass, cloud_prefs):
assert conf.is_reporting_states is False assert conf.is_reporting_states is False
async def test_alexa_config_invalidate_token(hass, cloud_prefs, aioclient_mock):
"""Test Alexa config should expose using prefs."""
aioclient_mock.post(
"http://example/alexa_token",
json={
"access_token": "mock-token",
"event_endpoint": "http://example.com/alexa_endpoint",
"expires_in": 30,
},
)
conf = alexa_config.AlexaConfig(
hass,
ALEXA_SCHEMA({}),
cloud_prefs,
Mock(
alexa_access_token_url="http://example/alexa_token",
run_executor=Mock(side_effect=mock_coro),
websession=hass.helpers.aiohttp_client.async_get_clientsession(),
),
)
token = await conf.async_get_access_token()
assert token == "mock-token"
assert len(aioclient_mock.mock_calls) == 1
token = await conf.async_get_access_token()
assert token == "mock-token"
assert len(aioclient_mock.mock_calls) == 1
assert conf._token_valid is not None
conf.async_invalidate_access_token()
assert conf._token_valid is None
token = await conf.async_get_access_token()
assert token == "mock-token"
assert len(aioclient_mock.mock_calls) == 2
@contextlib.contextmanager @contextlib.contextmanager
def patch_sync_helper(): def patch_sync_helper():
"""Patch sync helper. """Patch sync helper.