diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index bfea2c29eac..843f5cd3657 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1538,8 +1538,7 @@ class ConfigEntriesFlowManager( if (entry_id := flow.context.get("entry_id")) is not None and ( entry := self.config_entries.async_get_entry(entry_id) ) is not None: - issue_id = f"config_entry_reauth_{entry.domain}_{entry.entry_id}" - ir.async_delete_issue(self.hass, HOMEASSISTANT_DOMAIN, issue_id) + _remove_reauth_issue(self.hass, entry.domain, entry_id) if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY: # If there's an ignored config entry with a matching unique ID, @@ -2128,13 +2127,8 @@ class ConfigEntries: # If the configuration entry is removed during reauth, it should # abort any reauth flow that is active for the removed entry and # linked issues. - for progress_flow in self.hass.config_entries.flow.async_progress_by_handler( - entry.domain, match_context={"entry_id": entry_id, "source": SOURCE_REAUTH} - ): - if "flow_id" in progress_flow: - self.hass.config_entries.flow.async_abort(progress_flow["flow_id"]) - issue_id = f"config_entry_reauth_{entry.domain}_{entry.entry_id}" - ir.async_delete_issue(self.hass, HOMEASSISTANT_DOMAIN, issue_id) + _abort_reauth_flows(self.hass, entry.domain, entry_id) + _remove_reauth_issue(self.hass, entry.domain, entry_id) self._async_dispatch(ConfigEntryChange.REMOVED, entry) @@ -2266,6 +2260,10 @@ class ConfigEntries: # attempts. entry.async_cancel_retry_setup() + # Abort any in-progress reauth flow and linked issues + _abort_reauth_flows(self.hass, entry.domain, entry_id) + _remove_reauth_issue(self.hass, entry.domain, entry_id) + if entry.domain not in self.hass.config.components: # If the component is not loaded, just load it as # the config entry will be loaded as well. We need @@ -3827,3 +3825,20 @@ async def _async_get_flow_handler( return handler raise data_entry_flow.UnknownHandler + + +@callback +def _abort_reauth_flows(hass: HomeAssistant, domain: str, entry_id: str) -> None: + """Abort reauth flows for an entry.""" + for progress_flow in hass.config_entries.flow.async_progress_by_handler( + domain, match_context={"entry_id": entry_id, "source": SOURCE_REAUTH} + ): + if "flow_id" in progress_flow: + hass.config_entries.flow.async_abort(progress_flow["flow_id"]) + + +@callback +def _remove_reauth_issue(hass: HomeAssistant, domain: str, entry_id: str) -> None: + """Remove reauth issue.""" + issue_id = f"config_entry_reauth_{domain}_{entry_id}" + ir.async_delete_issue(hass, HOMEASSISTANT_DOMAIN, issue_id) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index d19c3b38650..10decbf1343 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -696,7 +696,7 @@ async def test_remove_entry_cancels_reauth( manager: config_entries.ConfigEntries, issue_registry: ir.IssueRegistry, ) -> None: - """Tests that removing a config entry, also aborts existing reauth flows.""" + """Tests that removing a config entry also aborts existing reauth flows.""" entry = MockConfigEntry(title="test_title", domain="test") mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed()) @@ -723,6 +723,40 @@ async def test_remove_entry_cancels_reauth( assert not issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id) +async def test_reload_entry_cancels_reauth( + hass: HomeAssistant, + manager: config_entries.ConfigEntries, + issue_registry: ir.IssueRegistry, +) -> None: + """Tests that reloading a config entry also aborts existing reauth flows.""" + entry = MockConfigEntry(title="test_title", domain="test") + + mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed()) + mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) + mock_platform(hass, "test.config_flow", None) + + entry.add_to_hass(hass) + await manager.async_setup(entry.entry_id) + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress_by_handler("test") + assert len(flows) == 1 + assert flows[0]["context"]["entry_id"] == entry.entry_id + assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH + assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR + + issue_id = f"config_entry_reauth_test_{entry.entry_id}" + assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id) + + mock_setup_entry.return_value = True + mock_setup_entry.side_effect = None + await manager.async_reload(entry.entry_id) + + flows = hass.config_entries.flow.async_progress_by_handler("test") + assert len(flows) == 0 + assert not issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, issue_id) + + async def test_remove_entry_handles_callback_error( hass: HomeAssistant, manager: config_entries.ConfigEntries ) -> None: