diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 038bc227fcd..33b5e248561 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -228,7 +228,7 @@ async def async_setup(hass, config): cloud.iot.register_on_connect(_on_connect) - await cloud.start() + await cloud.initialize() await http_api.async_setup(hass) account_link.async_setup(hass) diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 93c6fcd9086..4c039f3888c 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -108,8 +108,8 @@ class CloudClient(Interface): return self._google_config - async def logged_in(self) -> None: - """When user logs in.""" + async def cloud_started(self) -> None: + """When cloud is started.""" is_new_user = await self.prefs.async_set_username(self.cloud.username) async def enable_alexa(_): @@ -150,7 +150,10 @@ class CloudClient(Interface): if tasks: await asyncio.gather(*(task(None) for task in tasks)) - async def cleanups(self) -> None: + async def cloud_stopped(self) -> None: + """When the cloud is stopped.""" + + async def logout_cleanups(self) -> None: """Cleanup some stuff after logout.""" await self.prefs.async_set_username(None) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 65cbe8bb342..aed66ae179d 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -62,7 +62,7 @@ class CloudGoogleConfig(AbstractConfig): @property def should_report_state(self): """Return if states should be proactively reported.""" - return self._cloud.is_logged_in and self._prefs.google_report_state + return self.enabled and self._prefs.google_report_state @property def local_sdk_webhook_id(self): diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 129b9f83819..d5e93a2a370 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.46.0"], + "requirements": ["hass-nabucasa==0.47.1"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index ebbed89347e..4e3ade38e39 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -15,10 +15,10 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, - EVENT_HOMEASSISTANT_STARTED, STATE_UNAVAILABLE, ) -from homeassistant.core import Context, CoreState, HomeAssistant, State, callback +from homeassistant.core import Context, HomeAssistant, State, callback +from homeassistant.helpers import start from homeassistant.helpers.area_registry import AreaEntry from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import RegistryEntry @@ -105,15 +105,14 @@ class AbstractConfig(ABC): self._store = GoogleConfigStore(self.hass) await self._store.async_load() - if self.hass.state == CoreState.running: - await self.async_sync_entities_all() + if not self.enabled: return async def sync_google(_): """Sync entities to Google.""" await self.async_sync_entities_all() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) + start.async_at_start(self.hass, sync_google) @property def enabled(self): diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index 6dd2d9adb8a..4cc330a85ed 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -4,6 +4,8 @@ from __future__ import annotations from collections.abc import Awaitable, Callable from ipaddress import ip_address import logging +from types import ModuleType +from typing import Literal from aiohttp.hdrs import X_FORWARDED_FOR, X_FORWARDED_HOST, X_FORWARDED_PROTO from aiohttp.web import Application, HTTPBadRequest, Request, StreamResponse, middleware @@ -63,23 +65,30 @@ def async_setup_forwarded( an HTTP 400 status code is thrown. """ - try: - from hass_nabucasa import remote # pylint: disable=import-outside-toplevel - - # venv users might have already loaded it before it got upgraded so guard for this - # This can only happen when people upgrade from before 2021.8.5. - if not hasattr(remote, "is_cloud_request"): - remote = None - except ImportError: - remote = None + remote: Literal[False] | None | ModuleType = None @middleware async def forwarded_middleware( request: Request, handler: Callable[[Request], Awaitable[StreamResponse]] ) -> StreamResponse: """Process forwarded data by a reverse proxy.""" + nonlocal remote + + if remote is None: + # Initialize remote method + try: + from hass_nabucasa import ( # pylint: disable=import-outside-toplevel + remote, + ) + + # venv users might have an old version installed if they don't have cloud around anymore + if not hasattr(remote, "is_cloud_request"): + remote = False + except ImportError: + remote = False + # Skip requests from Remote UI - if remote is not None and remote.is_cloud_request.get(): + if remote and remote.is_cloud_request.get(): # type: ignore return await handler(request) # Handle X-Forwarded-For diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cb6d10e4084..c59fdafdc7a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ ciso8601==2.1.3 cryptography==3.3.2 defusedxml==0.7.1 emoji==1.2.0 -hass-nabucasa==0.46.0 +hass-nabucasa==0.47.1 home-assistant-frontend==20210830.0 httpx==0.19.0 ifaddr==0.1.7 diff --git a/requirements_all.txt b/requirements_all.txt index 179017806ea..2e2c63a9d25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ habitipy==0.2.0 hangups==0.4.14 # homeassistant.components.cloud -hass-nabucasa==0.46.0 +hass-nabucasa==0.47.1 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 22f16c40b79..d48136e74b7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -441,7 +441,7 @@ habitipy==0.2.0 hangups==0.4.14 # homeassistant.components.cloud -hass-nabucasa==0.46.0 +hass-nabucasa==0.47.1 # homeassistant.components.tasmota hatasmota==0.2.20 diff --git a/tests/components/cloud/__init__.py b/tests/components/cloud/__init__.py index 8613c6408fe..40809d2759c 100644 --- a/tests/components/cloud/__init__.py +++ b/tests/components/cloud/__init__.py @@ -12,7 +12,7 @@ async def mock_cloud(hass, config=None): assert await async_setup_component(hass, cloud.DOMAIN, {"cloud": config or {}}) cloud_inst = hass.data["cloud"] with patch("hass_nabucasa.Cloud.run_executor", AsyncMock(return_value=None)): - await cloud_inst.start() + await cloud_inst.initialize() def mock_cloud_prefs(hass, prefs={}): diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 75276a9f2e2..baa1dd6bae8 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -48,6 +48,8 @@ def mock_cloud_login(hass, mock_cloud_setup): }, "test", ) + with patch.object(hass.data[const.DOMAIN].auth, "async_check_token"): + yield @pytest.fixture diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index dfea8f80cee..c3890fb17ec 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -134,7 +134,7 @@ async def test_handler_google_actions_disabled(hass, mock_cloud_fixture): """Test handler Google Actions when user has disabled it.""" mock_cloud_fixture._prefs[PREF_ENABLE_GOOGLE] = False - with patch("hass_nabucasa.Cloud.start"): + with patch("hass_nabucasa.Cloud.initialize"): assert await async_setup_component(hass, "cloud", {}) reqid = "5711642932632160983" @@ -149,7 +149,7 @@ async def test_handler_google_actions_disabled(hass, mock_cloud_fixture): async def test_webhook_msg(hass, caplog): """Test webhook msg.""" - with patch("hass_nabucasa.Cloud.start"): + with patch("hass_nabucasa.Cloud.initialize"): setup = await async_setup_component(hass, "cloud", {"cloud": {}}) assert setup cloud = hass.data["cloud"] @@ -261,7 +261,7 @@ async def test_set_username(hass): ) client = CloudClient(hass, prefs, None, {}, {}) client.cloud = MagicMock(is_logged_in=True, username="mock-username") - await client.logged_in() + await client.cloud_started() assert len(prefs.async_set_username.mock_calls) == 1 assert prefs.async_set_username.mock_calls[0][1][0] == "mock-username" @@ -279,7 +279,7 @@ async def test_login_recovers_bad_internet(hass, caplog): client._alexa_config = Mock( async_enable_proactive_mode=Mock(side_effect=aiohttp.ClientError) ) - await client.logged_in() + await client.cloud_started() assert len(client._alexa_config.async_enable_proactive_mode.mock_calls) == 1 assert "Unable to activate Alexa Report State" in caplog.text diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 64d50250259..1f513dbf53e 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -31,6 +31,8 @@ async def test_google_update_report_state(mock_conf, hass, cloud_prefs): await mock_conf.async_initialize() await mock_conf.async_connect_agent_user("mock-user-id") + mock_conf._cloud.subscription_expired = False + with patch.object(mock_conf, "async_sync_entities") as mock_sync, patch( "homeassistant.components.google_assistant.report_state.async_enable_report_state" ) as mock_report_state: @@ -41,6 +43,25 @@ async def test_google_update_report_state(mock_conf, hass, cloud_prefs): assert len(mock_report_state.mock_calls) == 1 +async def test_google_update_report_state_subscription_expired( + mock_conf, hass, cloud_prefs +): + """Test Google config not reporting state when subscription has expired.""" + await mock_conf.async_initialize() + await mock_conf.async_connect_agent_user("mock-user-id") + + assert mock_conf._cloud.subscription_expired + + with patch.object(mock_conf, "async_sync_entities") as mock_sync, patch( + "homeassistant.components.google_assistant.report_state.async_enable_report_state" + ) as mock_report_state: + await cloud_prefs.async_update(google_report_state=True) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 0 + assert len(mock_report_state.mock_calls) == 0 + + async def test_sync_entities(mock_conf, hass, cloud_prefs): """Test sync devices.""" await mock_conf.async_initialize() @@ -172,6 +193,7 @@ async def test_sync_google_when_started(hass, mock_cloud_login, cloud_prefs): with patch.object(config, "async_sync_entities_all") as mock_sync: await config.async_initialize() await config.async_connect_agent_user("mock-user-id") + await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 1 diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 7202c8a0b39..e478849d3ef 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -15,7 +15,7 @@ from homeassistant.setup import async_setup_component async def test_constructor_loads_info_from_config(hass): """Test non-dev mode loads info from SERVERS constant.""" - with patch("hass_nabucasa.Cloud.start"): + with patch("hass_nabucasa.Cloud.initialize"): result = await async_setup_component( hass, "cloud", @@ -109,7 +109,7 @@ async def test_setup_existing_cloud_user(hass, hass_storage): """Test setup with API push default data.""" user = await hass.auth.async_create_system_user("Cloud test") hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": user.id}} - with patch("hass_nabucasa.Cloud.start"): + with patch("hass_nabucasa.Cloud.initialize"): result = await async_setup_component( hass, "cloud",