diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 8e309d92c7f..2a2d383f362 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -1,5 +1,6 @@ """Interface implementation for cloud client.""" import asyncio +import logging from pathlib import Path from typing import Any, Dict @@ -14,6 +15,7 @@ from homeassistant.components.google_assistant import const as gc, smart_home as from homeassistant.const import HTTP_OK from homeassistant.core import Context, callback from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.aiohttp import MockRequest @@ -106,13 +108,22 @@ class CloudClient(Interface): """When user logs in.""" await self.prefs.async_set_username(self.cloud.username) - if self.alexa_config.enabled and self.alexa_config.should_report_state: + async def enable_alexa(_): + """Enable Alexa.""" try: await self.alexa_config.async_enable_proactive_mode() + except aiohttp.ClientError as err: # If no internet available yet + if self._hass.is_running: + logging.getLogger(__package__).warning( + "Unable to activate Alexa Report State: %s. Retrying in 30 seconds", + err, + ) + async_call_later(self._hass, 30, enable_alexa) except alexa_errors.NoTokenAvailable: pass - if self._prefs.google_enabled: + async def enable_google(_): + """Enable Google.""" gconf = await self.get_google_config() gconf.async_enable_local_sdk() @@ -120,6 +131,17 @@ class CloudClient(Interface): if gconf.should_report_state: gconf.async_enable_report_state() + tasks = [] + + if self.alexa_config.enabled and self.alexa_config.should_report_state: + tasks.append(enable_alexa) + + if self._prefs.google_enabled: + tasks.append(enable_google) + + if tasks: + await asyncio.gather(*[task(None) for task in tasks]) + async def cleanups(self) -> None: """Cleanup some stuff after logout.""" await self.prefs.async_set_username(None) diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index aaabca71885..752394b5d0f 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -1,4 +1,7 @@ """Test the cloud.iot module.""" +from datetime import timedelta + +import aiohttp from aiohttp import web import pytest @@ -8,10 +11,12 @@ from homeassistant.components.cloud.const import PREF_ENABLE_ALEXA, PREF_ENABLE_ from homeassistant.const import CONTENT_TYPE_JSON from homeassistant.core import State from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from . import mock_cloud, mock_cloud_prefs -from tests.async_mock import AsyncMock, MagicMock, patch +from tests.async_mock import AsyncMock, MagicMock, Mock, patch +from tests.common import async_fire_time_changed from tests.components.alexa import test_smart_home as test_alexa @@ -260,3 +265,25 @@ async def test_set_username(hass): assert len(prefs.async_set_username.mock_calls) == 1 assert prefs.async_set_username.mock_calls[0][1][0] == "mock-username" + + +async def test_login_recovers_bad_internet(hass, caplog): + """Test Alexa can recover bad auth.""" + prefs = Mock( + alexa_enabled=True, + google_enabled=False, + async_set_username=AsyncMock(return_value=None), + ) + client = CloudClient(hass, prefs, None, {}, {}) + client.cloud = Mock() + client._alexa_config = Mock( + async_enable_proactive_mode=Mock(side_effect=aiohttp.ClientError) + ) + await client.logged_in() + assert len(client._alexa_config.async_enable_proactive_mode.mock_calls) == 1 + assert "Unable to activate Alexa Report State" in caplog.text + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert len(client._alexa_config.async_enable_proactive_mode.mock_calls) == 2