From b106ca79837bbdeb8ee7d414fde0773637ae28fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Jul 2023 08:11:51 -1000 Subject: [PATCH] Fix race fetching ESPHome dashboard when there are no devices set up (#96196) * Fix fetching ESPHome dashboard when there are no devices setup fixes #96194 * coverage * fix --- .../components/esphome/config_flow.py | 6 ++- homeassistant/components/esphome/dashboard.py | 9 +++- tests/components/esphome/test_config_flow.py | 42 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 9ed7ad7123d..5011439c778 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -35,7 +35,7 @@ from .const import ( DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS, DOMAIN, ) -from .dashboard import async_get_dashboard, async_set_dashboard_info +from .dashboard import async_get_or_create_dashboard_manager, async_set_dashboard_info ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key" ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk" @@ -406,7 +406,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): """ if ( self._device_name is None - or (dashboard := async_get_dashboard(self.hass)) is None + or (manager := await async_get_or_create_dashboard_manager(self.hass)) + is None + or (dashboard := manager.async_get()) is None ): return False diff --git a/homeassistant/components/esphome/dashboard.py b/homeassistant/components/esphome/dashboard.py index 35e9cf74555..c9d74f0b30c 100644 --- a/homeassistant/components/esphome/dashboard.py +++ b/homeassistant/components/esphome/dashboard.py @@ -143,7 +143,14 @@ class ESPHomeDashboardManager: @callback def async_get_dashboard(hass: HomeAssistant) -> ESPHomeDashboard | None: - """Get an instance of the dashboard if set.""" + """Get an instance of the dashboard if set. + + This is only safe to call after `async_setup` has been completed. + + It should not be called from the config flow because there is a race + where manager can be an asyncio.Event instead of the actual manager + because the singleton decorator is not yet done. + """ manager: ESPHomeDashboardManager | None = hass.data.get(KEY_DASHBOARD_MANAGER) return manager.async_get() if manager else None diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 28d411be939..fc37e1e51ee 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -1374,3 +1374,45 @@ async def test_option_flow( assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["data"] == {CONF_ALLOW_SERVICE_CALLS: option_value} assert len(mock_reload.mock_calls) == int(option_value) + + +async def test_user_discovers_name_no_dashboard( + hass: HomeAssistant, + mock_client, + mock_zeroconf: None, + mock_setup_entry: None, +) -> None: + """Test user step can discover the name and the there is not dashboard.""" + mock_client.device_info.side_effect = [ + RequiresEncryptionAPIError, + InvalidEncryptionKeyAPIError("Wrong key", "test"), + DeviceInfo( + uses_password=False, + name="test", + mac_address="11:22:33:44:55:AA", + ), + ] + + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": config_entries.SOURCE_USER}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "encryption_key" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + CONF_HOST: "127.0.0.1", + CONF_PORT: 6053, + CONF_PASSWORD: "", + CONF_NOISE_PSK: VALID_NOISE_PSK, + CONF_DEVICE_NAME: "test", + } + assert mock_client.noise_psk == VALID_NOISE_PSK