From d2a92ce4f36f1803f597cd684dbc1fa4e1cc5a80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 14:40:06 -0500 Subject: [PATCH] Ensure a deleted integration can be removed (#36130) * Ensure a deleted intergration can be removed * Update homeassistant/config_entries.py Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/config_entries.py | 20 ++++++++++++++++++-- tests/test_config_entries.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6dc259d6515..8dc88aa4da9 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -268,7 +268,15 @@ class ConfigEntry: return True if integration is None: - integration = await loader.async_get_integration(hass, self.domain) + try: + integration = await loader.async_get_integration(hass, self.domain) + except loader.IntegrationNotFound: + # The integration was likely a custom_component + # that was uninstalled, or an integration + # that has been renamed without removing the config + # entry. + self.state = ENTRY_STATE_NOT_LOADED + return True component = integration.get_component() @@ -316,7 +324,15 @@ class ConfigEntry: if self.source == SOURCE_IGNORE: return - integration = await loader.async_get_integration(hass, self.domain) + try: + integration = await loader.async_get_integration(hass, self.domain) + except loader.IntegrationNotFound: + # The integration was likely a custom_component + # that was uninstalled, or an integration + # that has been renamed without removing the config + # entry. + return + component = integration.get_component() if not hasattr(component, "async_remove_entry"): return diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 592bc1d4656..12b9c7308aa 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -365,6 +365,28 @@ async def test_remove_entry_if_not_loaded(hass, manager): assert len(mock_unload_entry.mock_calls) == 0 +async def test_remove_entry_if_integration_deleted(hass, manager): + """Test that we can remove an entry when the integration is deleted.""" + mock_unload_entry = AsyncMock(return_value=True) + + MockConfigEntry(domain="test", entry_id="test1").add_to_manager(manager) + MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager) + MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager) + + assert [item.entry_id for item in manager.async_entries()] == [ + "test1", + "test2", + "test3", + ] + + result = await manager.async_remove("test2") + + assert result == {"require_restart": False} + assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + + assert len(mock_unload_entry.mock_calls) == 0 + + async def test_add_entry_calls_setup_entry(hass, manager): """Test we call setup_config_entry.""" mock_setup_entry = AsyncMock(return_value=True)