diff --git a/homeassistant/components/yalexs_ble/config_flow.py b/homeassistant/components/yalexs_ble/config_flow.py index 7f632ebfab0..c7213eefbe9 100644 --- a/homeassistant/components/yalexs_ble/config_flow.py +++ b/homeassistant/components/yalexs_ble/config_flow.py @@ -16,7 +16,7 @@ from yalexs_ble import ( ) from yalexs_ble.const import YALE_MFR_ID -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, @@ -25,7 +25,6 @@ from homeassistant.const import CONF_ADDRESS from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import DiscoveryInfoType -from homeassistant.loader import async_get_integration from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN from .util import async_get_service_info, human_readable_name @@ -85,39 +84,52 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): discovery_info["key"], discovery_info["slot"], ) + + address = lock_cfg.address + local_name = lock_cfg.local_name + hass = self.hass + # We do not want to raise on progress as integration_discovery takes # precedence over other discovery flows since we already have the keys. - await self.async_set_unique_id(lock_cfg.address, raise_on_progress=False) + # + # After we do discovery we will abort the flows that do not have the keys + # below unless the user is already setting them up. + await self.async_set_unique_id(address, raise_on_progress=False) new_data = {CONF_KEY: lock_cfg.key, CONF_SLOT: lock_cfg.slot} self._abort_if_unique_id_configured(updates=new_data) for entry in self._async_current_entries(): if entry.data.get(CONF_LOCAL_NAME) == lock_cfg.local_name: - if self.hass.config_entries.async_update_entry( + if hass.config_entries.async_update_entry( entry, data={**entry.data, **new_data} ): - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) + hass.async_create_task( + hass.config_entries.async_reload(entry.entry_id) ) raise AbortFlow(reason="already_configured") try: self._discovery_info = await async_get_service_info( - self.hass, lock_cfg.local_name, lock_cfg.address + hass, local_name, address ) except asyncio.TimeoutError: return self.async_abort(reason="no_devices_found") + # Integration discovery should abort other flows unless they + # are already in the process of being set up since this discovery + # will already have all the keys and the user can simply confirm. for progress in self._async_in_progress(include_uninitialized=True): - # Integration discovery should abort other discovery types - # since it already has the keys and slots, and the other - # discovery types do not. context = progress["context"] if ( - not context.get("active") - and context.get("local_name") == lock_cfg.local_name - or context.get("unique_id") == lock_cfg.address - ): - self.hass.config_entries.flow.async_abort(progress["flow_id"]) + local_name_is_unique(local_name) + and context.get("local_name") == local_name + ) or context.get("unique_id") == address: + if context.get("active"): + # The user has already started interacting with this flow + # and entered the keys. We abort the discovery flow since + # we assume they do not want to use the discovered keys for + # some reason. + raise data_entry_flow.AbortFlow("already_in_progress") + hass.config_entries.flow.async_abort(progress["flow_id"]) self._lock_cfg = lock_cfg self.context["title_placeholders"] = { @@ -228,12 +240,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): vol.Required(CONF_SLOT): int, } ) - integration = await async_get_integration(self.hass, DOMAIN) return self.async_show_form( step_id="user", data_schema=data_schema, errors=errors, - description_placeholders={"docs_url": integration.documentation}, ) diff --git a/homeassistant/components/yalexs_ble/lock.py b/homeassistant/components/yalexs_ble/lock.py index 3f75a282f67..9e97c2f080f 100644 --- a/homeassistant/components/yalexs_ble/lock.py +++ b/homeassistant/components/yalexs_ble/lock.py @@ -55,8 +55,8 @@ class YaleXSBLELock(YALEXSBLEEntity, LockEntity): async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock.""" - return await self._device.unlock() + await self._device.unlock() async def async_lock(self, **kwargs: Any) -> None: """Lock the lock.""" - return await self._device.lock() + await self._device.lock() diff --git a/homeassistant/components/yalexs_ble/strings.json b/homeassistant/components/yalexs_ble/strings.json index 4d867474dbe..df5ac713b07 100644 --- a/homeassistant/components/yalexs_ble/strings.json +++ b/homeassistant/components/yalexs_ble/strings.json @@ -3,7 +3,7 @@ "flow_title": "{name}", "step": { "user": { - "description": "Check the documentation at {docs_url} for how to find the offline key.", + "description": "Check the documentation for how to find the offline key.", "data": { "address": "Bluetooth address", "key": "Offline Key (32-byte hex string)", @@ -22,6 +22,7 @@ "invalid_key_index": "The offline key slot must be an integer between 0 and 255." }, "abort": { + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "no_unconfigured_devices": "No unconfigured devices found.", "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" diff --git a/homeassistant/components/yalexs_ble/translations/en.json b/homeassistant/components/yalexs_ble/translations/en.json index 6d817499270..f8ebc0737ac 100644 --- a/homeassistant/components/yalexs_ble/translations/en.json +++ b/homeassistant/components/yalexs_ble/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "no_devices_found": "No devices found on the network", "no_unconfigured_devices": "No unconfigured devices found." }, @@ -23,7 +24,7 @@ "key": "Offline Key (32-byte hex string)", "slot": "Offline Key Slot (Integer between 0 and 255)" }, - "description": "Check the documentation at {docs_url} for how to find the offline key." + "description": "Check the documentation for how to find the offline key." } } } diff --git a/tests/components/yalexs_ble/test_config_flow.py b/tests/components/yalexs_ble/test_config_flow.py index 7607b710934..6ea1b4e8a63 100644 --- a/tests/components/yalexs_ble/test_config_flow.py +++ b/tests/components/yalexs_ble/test_config_flow.py @@ -752,3 +752,83 @@ async def test_integration_discovery_takes_precedence_over_bluetooth_non_unique_ if flow["handler"] == DOMAIN ] assert len(flows) == 1 + + +async def test_user_is_setting_up_lock_and_discovery_happens_in_the_middle( + hass: HomeAssistant, +) -> None: + """Test that the user is setting up the lock and waiting for validation and the keys get discovered. + + In this case the integration discovery should abort and let the user continue setting up the lock. + """ + with patch( + "homeassistant.components.yalexs_ble.config_flow.async_discovered_service_info", + return_value=[NOT_YALE_DISCOVERY_INFO, YALE_ACCESS_LOCK_DISCOVERY_INFO], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + user_flow_event = asyncio.Event() + valdidate_started = asyncio.Event() + + async def _wait_for_user_flow(): + valdidate_started.set() + await user_flow_event.wait() + + with patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + side_effect=_wait_for_user_flow, + ), patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + user_flow_task = asyncio.create_task( + hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ADDRESS: YALE_ACCESS_LOCK_DISCOVERY_INFO.address, + CONF_KEY: "2fd51b8621c6a139eaffbedcb846b60f", + CONF_SLOT: 66, + }, + ) + ) + await valdidate_started.wait() + + with patch( + "homeassistant.components.yalexs_ble.util.async_process_advertisements", + return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + ): + discovery_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data={ + "name": "Front Door", + "address": OLD_FIRMWARE_LOCK_DISCOVERY_INFO.address, + "key": "2fd51b8621c6a139eaffbedcb846b60f", + "slot": 66, + "serial": "M1XXX012LU", + }, + ) + await hass.async_block_till_done() + assert discovery_result["type"] == FlowResultType.ABORT + assert discovery_result["reason"] == "already_in_progress" + + user_flow_event.set() + user_flow_result = await user_flow_task + + assert user_flow_result["type"] == FlowResultType.CREATE_ENTRY + assert user_flow_result["title"] == YALE_ACCESS_LOCK_DISCOVERY_INFO.name + assert user_flow_result["data"] == { + CONF_LOCAL_NAME: YALE_ACCESS_LOCK_DISCOVERY_INFO.name, + CONF_ADDRESS: YALE_ACCESS_LOCK_DISCOVERY_INFO.address, + CONF_KEY: "2fd51b8621c6a139eaffbedcb846b60f", + CONF_SLOT: 66, + } + assert ( + user_flow_result["result"].unique_id == YALE_ACCESS_LOCK_DISCOVERY_INFO.address + ) + assert len(mock_setup_entry.mock_calls) == 1