From b7284b92acf3af18efd59344a1bff087084dd7c3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 5 Feb 2024 23:58:34 +0100 Subject: [PATCH] Clean up Alexa when logging out from cloud (#109738) * Clean up Alexa when logging out from cloud * Add test --- homeassistant/components/alexa/config.py | 8 +++++++ .../components/cloud/alexa_config.py | 22 ++++++++++++------- homeassistant/components/cloud/client.py | 4 ++++ tests/components/cloud/test_alexa_config.py | 8 +++++++ tests/components/cloud/test_client.py | 5 +++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/alexa/config.py b/homeassistant/components/alexa/config.py index a1ab1d77081..02aaed25742 100644 --- a/homeassistant/components/alexa/config.py +++ b/homeassistant/components/alexa/config.py @@ -29,12 +29,20 @@ class AbstractConfig(ABC): """Initialize abstract config.""" self.hass = hass self._enable_proactive_mode_lock = asyncio.Lock() + self._on_deinitialize: list[CALLBACK_TYPE] = [] async def async_initialize(self) -> None: """Perform async initialization of config.""" self._store = AlexaConfigStore(self.hass) await self._store.async_load() + @callback + def async_deinitialize(self) -> None: + """Remove listeners.""" + _LOGGER.debug("async_deinitialize") + while self._on_deinitialize: + self._on_deinitialize.pop()() + @property def supports_auth(self) -> bool: """Return if config supports auth.""" diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index caed7b38c47..415f2415095 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -246,21 +246,27 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): await self._prefs.async_update( alexa_settings_version=ALEXA_SETTINGS_VERSION ) - async_listen_entity_updates( - self.hass, CLOUD_ALEXA, self._async_exposed_entities_updated + self._on_deinitialize.append( + async_listen_entity_updates( + self.hass, CLOUD_ALEXA, self._async_exposed_entities_updated + ) ) async def on_hass_start(hass: HomeAssistant) -> None: if self.enabled and ALEXA_DOMAIN not in self.hass.config.components: await async_setup_component(self.hass, ALEXA_DOMAIN, {}) - start.async_at_start(self.hass, on_hass_start) - start.async_at_started(self.hass, on_hass_started) + self._on_deinitialize.append(start.async_at_start(self.hass, on_hass_start)) + self._on_deinitialize.append(start.async_at_started(self.hass, on_hass_started)) - self._prefs.async_listen_updates(self._async_prefs_updated) - self.hass.bus.async_listen( - er.EVENT_ENTITY_REGISTRY_UPDATED, - self._handle_entity_registry_updated, + self._on_deinitialize.append( + self._prefs.async_listen_updates(self._async_prefs_updated) + ) + self._on_deinitialize.append( + self.hass.bus.async_listen( + er.EVENT_ENTITY_REGISTRY_UPDATED, + self._handle_entity_registry_updated, + ) ) def _should_expose_legacy(self, entity_id: str) -> bool: diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 8cf79d20c5d..ea85821a0a9 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -213,6 +213,10 @@ class CloudClient(Interface): """Cleanup some stuff after logout.""" await self.prefs.async_set_username(None) + if self._alexa_config: + self._alexa_config.async_deinitialize() + self._alexa_config = None + if self._google_config: self._google_config.async_deinitialize() self._google_config = None diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 0ebc385b516..3ac2417247c 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -526,6 +526,9 @@ async def test_alexa_handle_logout( return_value=Mock(), ) as mock_enable: await aconf.async_enable_proactive_mode() + await hass.async_block_till_done() + + assert len(aconf._on_deinitialize) == 5 # This will trigger a prefs update when we logout. await cloud_prefs.get_cloud_user() @@ -536,8 +539,13 @@ async def test_alexa_handle_logout( "async_check_token", side_effect=AssertionError("Should not be called"), ): + # Fake logging out; CloudClient.logout_cleanups sets username to None + # and deinitializes the Google config. await cloud_prefs.async_set_username(None) + aconf.async_deinitialize() await hass.async_block_till_done() + # Check listeners are removed: + assert not aconf._on_deinitialize assert len(mock_enable.return_value.mock_calls) == 1 diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index c8c0e40a5bb..4284a11c94a 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -468,10 +468,11 @@ async def test_logged_out( await cloud.logout() await hass.async_block_till_done() - # Alexa is not cleaned up, Google is - assert cloud.client._alexa_config is alexa_config_mock + # Check we clean up Alexa and Google + assert cloud.client._alexa_config is None assert cloud.client._google_config is None google_config_mock.async_deinitialize.assert_called_once_with() + alexa_config_mock.async_deinitialize.assert_called_once_with() async def test_remote_enable(hass: HomeAssistant) -> None: