Handle gone next flow in improv_ble provisioning (#182147)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-09-14 12:30:52 +02:00
committed by GitHub
co-authored by Claude
parent 0349bfc8bc
commit 25fc54e50e
2 changed files with 97 additions and 6 deletions
@@ -28,7 +28,7 @@ from homeassistant.config_entries import (
)
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.data_entry_flow import AbortFlow, UnknownFlow
from homeassistant.helpers.device_registry import format_mac
from . import async_get_provisioning_futures
@@ -407,11 +407,17 @@ class ImprovBLEConfigFlow(ConfigFlow, domain=DOMAIN):
if next_flow_id:
_LOGGER.debug("Received next flow ID: %s", next_flow_id)
self._provision_result = self.async_abort(
reason="provision_successful",
next_flow=(FlowType.CONFIG_FLOW, next_flow_id),
)
return
try:
self._provision_result = self.async_abort(
reason="provision_successful",
next_flow=(FlowType.CONFIG_FLOW, next_flow_id),
)
except UnknownFlow:
# The other integration aborted its flow, for example
# because the device is already configured
_LOGGER.debug("Next flow %s is gone", next_flow_id)
else:
return
if redirect_url:
self._provision_result = self.async_abort(
@@ -1108,6 +1108,91 @@ async def test_flow_chaining_with_redirect_url(hass: HomeAssistant) -> None:
assert result["next_flow"] == (FlowType.CONFIG_FLOW, esphome_flow_id)
@pytest.mark.parametrize(
("redirect_url", "expected_reason"),
[
pytest.param(None, "provision_successful", id="no_redirect_url"),
pytest.param(
"http://blabla.local", "provision_successful_url", id="redirect_url"
),
],
)
async def test_flow_chaining_next_flow_gone(
hass: HomeAssistant, redirect_url: str | None, expected_reason: str
) -> None:
"""Test the next flow is aborted before provisioning completes."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH},
data=IMPROV_BLE_DISCOVERY_INFO,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm"
# Confirm bluetooth setup
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm"
# Start provisioning
with (
patch(
f"{IMPROV_BLE}.config_flow.ImprovBLEClient.can_identify",
return_value=False,
new_callable=PropertyMock,
),
patch(f"{IMPROV_BLE}.config_flow.ImprovBLEClient.ensure_connected"),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_ADDRESS: IMPROV_BLE_DISCOVERY_INFO.address},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "provision"
with (
patch(
f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization",
return_value=False,
),
patch(
f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision",
return_value=redirect_url,
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"ssid": "TestNetwork", "password": "secret"}
)
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["progress_action"] == "provisioning"
assert result["step_id"] == "do_provision"
# Yield to allow the background task to create the future
await asyncio.sleep(0) # task is created with eager_start=False
# Create a dummy target flow using a different device address
target_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH},
data=IMPROV_BLE_DISCOVERY_INFO_TARGET1,
)
next_config_flow_id = target_result["flow_id"]
# Simulate another integration registering a flow which it then aborts,
# for example because the device is already configured
improv_ble.async_register_next_flow(
hass, IMPROV_BLE_DISCOVERY_INFO.address, next_config_flow_id
)
hass.config_entries.flow.async_abort(next_config_flow_id)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == expected_reason
assert "next_flow" not in result
async def test_flow_chaining_future_already_done(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None: