From 19dcc8c88f2e07e288533171d293aec60357954a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 May 2023 08:11:09 -0400 Subject: [PATCH] Re-run expose entities migration if first time failed (#92564) * Re-run expose entities migration if first time failed * Count number of exposed entities * Add tests --------- Co-authored-by: Erik --- .../components/cloud/alexa_config.py | 12 ++- .../components/cloud/google_config.py | 13 ++- homeassistant/components/cloud/prefs.py | 4 +- tests/components/cloud/test_alexa_config.py | 98 ++++++++++++++++++- tests/components/cloud/test_google_config.py | 98 ++++++++++++++++++- 5 files changed, 219 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index b7f0b5f6763..53bf44d8aa1 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -216,8 +216,18 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): async def on_hass_started(hass): if self._prefs.alexa_settings_version != ALEXA_SETTINGS_VERSION: - if self._prefs.alexa_settings_version < 2: + if self._prefs.alexa_settings_version < 2 or ( + # Recover from a bug we had in 2023.5.0 where entities didn't get exposed + self._prefs.alexa_settings_version < 3 + and not any( + settings.get("should_expose", False) + for settings in async_get_assistant_settings( + hass, CLOUD_ALEXA + ).values() + ) + ): self._migrate_alexa_entity_settings_v1() + await self._prefs.async_update( alexa_settings_version=ALEXA_SETTINGS_VERSION ) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 02aa5760597..351de5d0e65 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -12,6 +12,7 @@ from homeassistant.components.google_assistant import DOMAIN as GOOGLE_DOMAIN from homeassistant.components.google_assistant.helpers import AbstractConfig from homeassistant.components.homeassistant.exposed_entities import ( async_expose_entity, + async_get_assistant_settings, async_get_entity_settings, async_listen_entity_updates, async_set_assistant_option, @@ -200,8 +201,18 @@ class CloudGoogleConfig(AbstractConfig): async def on_hass_started(hass: HomeAssistant) -> None: if self._prefs.google_settings_version != GOOGLE_SETTINGS_VERSION: - if self._prefs.google_settings_version < 2: + if self._prefs.google_settings_version < 2 or ( + # Recover from a bug we had in 2023.5.0 where entities didn't get exposed + self._prefs.google_settings_version < 3 + and not any( + settings.get("should_expose", False) + for settings in async_get_assistant_settings( + hass, CLOUD_GOOGLE + ).values() + ) + ): self._migrate_google_entity_settings_v1() + await self._prefs.async_update( google_settings_version=GOOGLE_SETTINGS_VERSION ) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 75e1856503c..5ccc007e524 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -41,8 +41,8 @@ STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 STORAGE_VERSION_MINOR = 2 -ALEXA_SETTINGS_VERSION = 2 -GOOGLE_SETTINGS_VERSION = 2 +ALEXA_SETTINGS_VERSION = 3 +GOOGLE_SETTINGS_VERSION = 3 class CloudPreferencesStore(Store): diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 3a7e5a0874e..2be2a8eb2bb 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -542,11 +542,13 @@ async def test_alexa_handle_logout( assert len(mock_enable.return_value.mock_calls) == 1 +@pytest.mark.parametrize("alexa_settings_version", [1, 2]) async def test_alexa_config_migrate_expose_entity_prefs( hass: HomeAssistant, cloud_prefs: CloudPreferences, cloud_stub, entity_registry: er.EntityRegistry, + alexa_settings_version: int, ) -> None: """Test migrating Alexa entity config.""" hass.state = CoreState.starting @@ -593,7 +595,7 @@ async def test_alexa_config_migrate_expose_entity_prefs( await cloud_prefs.async_update( alexa_enabled=True, alexa_report_state=False, - alexa_settings_version=1, + alexa_settings_version=alexa_settings_version, ) expose_entity(hass, entity_migrated.entity_id, False) @@ -641,6 +643,100 @@ async def test_alexa_config_migrate_expose_entity_prefs( } +async def test_alexa_config_migrate_expose_entity_prefs_v2_no_exposed( + hass: HomeAssistant, + cloud_prefs: CloudPreferences, + entity_registry: er.EntityRegistry, +) -> None: + """Test migrating Alexa entity config from v2 to v3 when no entity is exposed.""" + hass.state = CoreState.starting + + assert await async_setup_component(hass, "homeassistant", {}) + hass.states.async_set("light.state_only", "on") + entity_migrated = entity_registry.async_get_or_create( + "light", + "test", + "light_migrated", + suggested_object_id="migrated", + ) + await cloud_prefs.async_update( + alexa_enabled=True, + alexa_report_state=False, + alexa_settings_version=2, + ) + expose_entity(hass, "light.state_only", False) + expose_entity(hass, entity_migrated.entity_id, False) + + cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.state_only"] = { + PREF_SHOULD_EXPOSE: True + } + cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS][entity_migrated.entity_id] = { + PREF_SHOULD_EXPOSE: True + } + conf = alexa_config.CloudAlexaConfig( + hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False) + ) + await conf.async_initialize() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert async_get_entity_settings(hass, "light.state_only") == { + "cloud.alexa": {"should_expose": True} + } + assert async_get_entity_settings(hass, entity_migrated.entity_id) == { + "cloud.alexa": {"should_expose": True} + } + + +async def test_alexa_config_migrate_expose_entity_prefs_v2_exposed( + hass: HomeAssistant, + cloud_prefs: CloudPreferences, + entity_registry: er.EntityRegistry, +) -> None: + """Test migrating Alexa entity config from v2 to v3 when an entity is exposed.""" + hass.state = CoreState.starting + + assert await async_setup_component(hass, "homeassistant", {}) + hass.states.async_set("light.state_only", "on") + entity_migrated = entity_registry.async_get_or_create( + "light", + "test", + "light_migrated", + suggested_object_id="migrated", + ) + await cloud_prefs.async_update( + alexa_enabled=True, + alexa_report_state=False, + alexa_settings_version=2, + ) + expose_entity(hass, "light.state_only", False) + expose_entity(hass, entity_migrated.entity_id, True) + + cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS]["light.state_only"] = { + PREF_SHOULD_EXPOSE: True + } + cloud_prefs._prefs[PREF_ALEXA_ENTITY_CONFIGS][entity_migrated.entity_id] = { + PREF_SHOULD_EXPOSE: True + } + conf = alexa_config.CloudAlexaConfig( + hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False) + ) + await conf.async_initialize() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert async_get_entity_settings(hass, "light.state_only") == { + "cloud.alexa": {"should_expose": False} + } + assert async_get_entity_settings(hass, entity_migrated.entity_id) == { + "cloud.alexa": {"should_expose": True} + } + + async def test_alexa_config_migrate_expose_entity_prefs_default_none( hass: HomeAssistant, cloud_prefs: CloudPreferences, diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 45bc56a1700..fe60ca971a1 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -483,10 +483,12 @@ async def test_google_handle_logout( assert len(mock_enable.return_value.mock_calls) == 1 +@pytest.mark.parametrize("google_settings_version", [1, 2]) async def test_google_config_migrate_expose_entity_prefs( hass: HomeAssistant, cloud_prefs: CloudPreferences, entity_registry: er.EntityRegistry, + google_settings_version: int, ) -> None: """Test migrating Google entity config.""" hass.state = CoreState.starting @@ -540,7 +542,7 @@ async def test_google_config_migrate_expose_entity_prefs( await cloud_prefs.async_update( google_enabled=True, google_report_state=False, - google_settings_version=1, + google_settings_version=google_settings_version, ) expose_entity(hass, entity_migrated.entity_id, False) @@ -596,6 +598,100 @@ async def test_google_config_migrate_expose_entity_prefs( } +async def test_google_config_migrate_expose_entity_prefs_v2_no_exposed( + hass: HomeAssistant, + cloud_prefs: CloudPreferences, + entity_registry: er.EntityRegistry, +) -> None: + """Test migrating Google entity config from v2 to v3 when no entity is exposed.""" + hass.state = CoreState.starting + + assert await async_setup_component(hass, "homeassistant", {}) + hass.states.async_set("light.state_only", "on") + entity_migrated = entity_registry.async_get_or_create( + "light", + "test", + "light_migrated", + suggested_object_id="migrated", + ) + await cloud_prefs.async_update( + google_enabled=True, + google_report_state=False, + google_settings_version=2, + ) + expose_entity(hass, "light.state_only", False) + expose_entity(hass, entity_migrated.entity_id, False) + + cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.state_only"] = { + PREF_SHOULD_EXPOSE: True + } + cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS][entity_migrated.entity_id] = { + PREF_SHOULD_EXPOSE: True + } + conf = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False) + ) + await conf.async_initialize() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert async_get_entity_settings(hass, "light.state_only") == { + "cloud.google_assistant": {"should_expose": True} + } + assert async_get_entity_settings(hass, entity_migrated.entity_id) == { + "cloud.google_assistant": {"should_expose": True} + } + + +async def test_google_config_migrate_expose_entity_prefs_v2_exposed( + hass: HomeAssistant, + cloud_prefs: CloudPreferences, + entity_registry: er.EntityRegistry, +) -> None: + """Test migrating Google entity config from v2 to v3 when an entity is exposed.""" + hass.state = CoreState.starting + + assert await async_setup_component(hass, "homeassistant", {}) + hass.states.async_set("light.state_only", "on") + entity_migrated = entity_registry.async_get_or_create( + "light", + "test", + "light_migrated", + suggested_object_id="migrated", + ) + await cloud_prefs.async_update( + google_enabled=True, + google_report_state=False, + google_settings_version=2, + ) + expose_entity(hass, "light.state_only", False) + expose_entity(hass, entity_migrated.entity_id, True) + + cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS]["light.state_only"] = { + PREF_SHOULD_EXPOSE: True + } + cloud_prefs._prefs[PREF_GOOGLE_ENTITY_CONFIGS][entity_migrated.entity_id] = { + PREF_SHOULD_EXPOSE: True + } + conf = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, Mock(is_logged_in=False) + ) + await conf.async_initialize() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert async_get_entity_settings(hass, "light.state_only") == { + "cloud.google_assistant": {"should_expose": False} + } + assert async_get_entity_settings(hass, entity_migrated.entity_id) == { + "cloud.google_assistant": {"should_expose": True} + } + + async def test_google_config_migrate_expose_entity_prefs_default_none( hass: HomeAssistant, cloud_prefs: CloudPreferences,