From f9bd384e6c85d80b232d7ba8fe46406655e2d442 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 May 2022 17:24:34 -1000 Subject: [PATCH] Stop waiting for setup retry upon discovery (#72738) --- homeassistant/config_entries.py | 52 +++++++++++++--------- tests/test_config_entries.py | 76 ++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 22 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 0ac02adb8d0..49b2059b2a2 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -108,7 +108,7 @@ class ConfigEntryState(Enum): DEFAULT_DISCOVERY_UNIQUE_ID = "default_discovery_unique_id" DISCOVERY_NOTIFICATION_ID = "config_entry_discovery" -DISCOVERY_SOURCES = ( +DISCOVERY_SOURCES = { SOURCE_DHCP, SOURCE_DISCOVERY, SOURCE_HOMEKIT, @@ -119,7 +119,7 @@ DISCOVERY_SOURCES = ( SOURCE_UNIGNORE, SOURCE_USB, SOURCE_ZEROCONF, -) +} RECONFIGURE_NOTIFICATION_ID = "config_entry_reconfigure" @@ -1242,24 +1242,36 @@ class ConfigFlow(data_entry_flow.FlowHandler): return for entry in self._async_current_entries(include_ignore=True): - if entry.unique_id == self.unique_id: - if updates is not None: - changed = self.hass.config_entries.async_update_entry( - entry, data={**entry.data, **updates} - ) - if ( - changed - and reload_on_update - and entry.state - in (ConfigEntryState.LOADED, ConfigEntryState.SETUP_RETRY) - ): - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) - ) - # Allow ignored entries to be configured on manual user step - if entry.source == SOURCE_IGNORE and self.source == SOURCE_USER: - continue - raise data_entry_flow.AbortFlow("already_configured") + if entry.unique_id != self.unique_id: + continue + should_reload = False + if ( + updates is not None + and self.hass.config_entries.async_update_entry( + entry, data={**entry.data, **updates} + ) + and reload_on_update + and entry.state + in (ConfigEntryState.LOADED, ConfigEntryState.SETUP_RETRY) + ): + # Existing config entry present, and the + # entry data just changed + should_reload = True + elif ( + self.source in DISCOVERY_SOURCES + and entry.state is ConfigEntryState.SETUP_RETRY + ): + # Existing config entry present in retry state, and we + # just discovered the unique id so we know its online + should_reload = True + # Allow ignored entries to be configured on manual user step + if entry.source == SOURCE_IGNORE and self.source == SOURCE_USER: + continue + if should_reload: + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + raise data_entry_flow.AbortFlow("already_configured") async def async_set_unique_id( self, unique_id: str | None = None, *, raise_on_progress: bool = True diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 2602887d1d5..09951b4f34e 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1,4 +1,6 @@ """Test the config manager.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging @@ -7,6 +9,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest from homeassistant import config_entries, data_entry_flow, loader +from homeassistant.components import dhcp from homeassistant.components.hassio import HassioServiceInfo from homeassistant.const import ( EVENT_COMPONENT_LOADED, @@ -14,7 +17,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import CoreState, Event, callback -from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, BaseServiceInfo +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, BaseServiceInfo, FlowResult from homeassistant.exceptions import ( ConfigEntryAuthFailed, ConfigEntryNotReady, @@ -1644,7 +1647,7 @@ async def test_unique_id_update_existing_entry_without_reload(hass, manager): async def async_step_user(self, user_input=None): """Test user step.""" await self.async_set_unique_id("mock-unique-id") - await self._abort_if_unique_id_configured( + self._abort_if_unique_id_configured( updates={"host": "1.1.1.1"}, reload_on_update=False ) @@ -1725,6 +1728,75 @@ async def test_unique_id_update_existing_entry_with_reload(hass, manager): assert len(async_reload.mock_calls) == 0 +async def test_unique_id_from_discovery_in_setup_retry(hass, manager): + """Test that we reload when in a setup retry state from discovery.""" + hass.config.components.add("comp") + unique_id = "34:ea:34:b4:3b:5a" + host = "0.0.0.0" + entry = MockConfigEntry( + domain="comp", + data={"additional": "data", "host": host}, + unique_id=unique_id, + state=config_entries.ConfigEntryState.SETUP_RETRY, + ) + entry.add_to_hass(hass) + + mock_integration( + hass, + MockModule("comp"), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> FlowResult: + """Test dhcp step.""" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + """Test user step.""" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + # Verify we do not reload from a user source + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload: + result = await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert len(async_reload.mock_calls) == 0 + + # Verify do reload from a discovery source + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload: + discovery_result = await manager.flow.async_init( + "comp", + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + hostname="any", + ip=host, + macaddress=unique_id, + ), + ) + await hass.async_block_till_done() + + assert discovery_result["type"] == RESULT_TYPE_ABORT + assert discovery_result["reason"] == "already_configured" + assert len(async_reload.mock_calls) == 1 + + async def test_unique_id_not_update_existing_entry(hass, manager): """Test that we do not update an entry if existing entry has the data.""" hass.config.components.add("comp")