Skip to content

Commit 3938266

Browse files
Add next_flow to config flow result (home-assistant#151998)
1 parent 9edd5c3 commit 3938266

File tree

2 files changed

+194
-1
lines changed

2 files changed

+194
-1
lines changed

homeassistant/config_entries.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,22 @@ class ConfigFlowResult(FlowResult[ConfigFlowContext, str], total=False):
299299
"""Typed result dict for config flow."""
300300

301301
# Extra keys, only present if type is CREATE_ENTRY
302+
next_flow: tuple[FlowType, str] # (flow type, flow id)
302303
minor_version: int
303304
options: Mapping[str, Any]
304305
result: ConfigEntry
305306
subentries: Iterable[ConfigSubentryData]
306307
version: int
307308

308309

310+
class FlowType(StrEnum):
311+
"""Flow type."""
312+
313+
CONFIG_FLOW = "config_flow"
314+
# Add other flow types here as needed in the future,
315+
# if we want to support them in the `next_flow` parameter.
316+
317+
309318
def _validate_item(*, disabled_by: ConfigEntryDisabler | Any | None = None) -> None:
310319
"""Validate config entry item."""
311320

@@ -3138,6 +3147,7 @@ def async_create_entry( # type: ignore[override]
31383147
data: Mapping[str, Any],
31393148
description: str | None = None,
31403149
description_placeholders: Mapping[str, str] | None = None,
3150+
next_flow: tuple[FlowType, str] | None = None,
31413151
options: Mapping[str, Any] | None = None,
31423152
subentries: Iterable[ConfigSubentryData] | None = None,
31433153
) -> ConfigFlowResult:
@@ -3158,6 +3168,13 @@ def async_create_entry( # type: ignore[override]
31583168
)
31593169

31603170
result["minor_version"] = self.MINOR_VERSION
3171+
if next_flow is not None:
3172+
flow_type, flow_id = next_flow
3173+
if flow_type != FlowType.CONFIG_FLOW:
3174+
raise HomeAssistantError("Invalid next_flow type")
3175+
# Raises UnknownFlow if the flow does not exist.
3176+
self.hass.config_entries.flow.async_get(flow_id)
3177+
result["next_flow"] = next_flow
31613178
result["options"] = options or {}
31623179
result["subentries"] = subentries or ()
31633180
result["version"] = self.VERSION

tests/test_config_entries.py

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
HomeAssistant,
3232
callback,
3333
)
34-
from homeassistant.data_entry_flow import BaseServiceInfo, FlowResult, FlowResultType
34+
from homeassistant.data_entry_flow import (
35+
BaseServiceInfo,
36+
FlowResult,
37+
FlowResultType,
38+
UnknownFlow,
39+
)
3540
from homeassistant.exceptions import (
3641
ConfigEntryAuthFailed,
3742
ConfigEntryError,
@@ -1838,6 +1843,177 @@ async def _mock_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
18381843
]
18391844

18401845

1846+
async def test_create_entry_next_flow(
1847+
hass: HomeAssistant, manager: config_entries.ConfigEntries
1848+
) -> None:
1849+
"""Test next_flow parameter for create entry."""
1850+
1851+
async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
1852+
"""Mock setup."""
1853+
return True
1854+
1855+
async_setup_entry = AsyncMock(return_value=True)
1856+
mock_integration(
1857+
hass,
1858+
MockModule(
1859+
"comp",
1860+
async_setup=mock_async_setup,
1861+
async_setup_entry=async_setup_entry,
1862+
),
1863+
)
1864+
mock_platform(hass, "comp.config_flow", None)
1865+
1866+
class TestFlow(config_entries.ConfigFlow):
1867+
"""Test flow."""
1868+
1869+
async def async_step_import(
1870+
self, user_input: dict[str, Any] | None = None
1871+
) -> config_entries.ConfigFlowResult:
1872+
"""Test create entry with next_flow parameter."""
1873+
result = await hass.config_entries.flow.async_init(
1874+
"comp",
1875+
context={"source": config_entries.SOURCE_USER},
1876+
)
1877+
return self.async_create_entry(
1878+
title="import",
1879+
data={"flow": "import"},
1880+
next_flow=(config_entries.FlowType.CONFIG_FLOW, result["flow_id"]),
1881+
)
1882+
1883+
async def async_step_user(
1884+
self, user_input: dict[str, Any] | None = None
1885+
) -> config_entries.ConfigFlowResult:
1886+
"""Test next step."""
1887+
if user_input is None:
1888+
return self.async_show_form(step_id="user")
1889+
return self.async_create_entry(title="user", data={"flow": "user"})
1890+
1891+
with mock_config_flow("comp", TestFlow):
1892+
assert await async_setup_component(hass, "comp", {})
1893+
1894+
result = await hass.config_entries.flow.async_init(
1895+
"comp",
1896+
context={"source": config_entries.SOURCE_IMPORT},
1897+
)
1898+
await hass.async_block_till_done()
1899+
1900+
flows = hass.config_entries.flow.async_progress()
1901+
assert len(flows) == 1
1902+
user_flow = flows[0]
1903+
assert async_setup_entry.call_count == 1
1904+
1905+
entries = hass.config_entries.async_entries("comp")
1906+
assert len(entries) == 1
1907+
entry = entries[0]
1908+
assert result == {
1909+
"context": {"source": "import"},
1910+
"data": {"flow": "import"},
1911+
"description_placeholders": None,
1912+
"description": None,
1913+
"flow_id": ANY,
1914+
"handler": "comp",
1915+
"minor_version": 1,
1916+
"next_flow": (config_entries.FlowType.CONFIG_FLOW, user_flow["flow_id"]),
1917+
"options": {},
1918+
"result": entry,
1919+
"subentries": (),
1920+
"title": "import",
1921+
"type": FlowResultType.CREATE_ENTRY,
1922+
"version": 1,
1923+
}
1924+
1925+
result = await hass.config_entries.flow.async_configure(
1926+
user_flow["flow_id"], {}
1927+
)
1928+
await hass.async_block_till_done()
1929+
1930+
assert async_setup_entry.call_count == 2
1931+
entries = hass.config_entries.async_entries("comp")
1932+
entry = next(entry for entry in entries if entry.data.get("flow") == "user")
1933+
assert result == {
1934+
"context": {"source": "user"},
1935+
"data": {"flow": "user"},
1936+
"description_placeholders": None,
1937+
"description": None,
1938+
"flow_id": user_flow["flow_id"],
1939+
"handler": "comp",
1940+
"minor_version": 1,
1941+
"options": {},
1942+
"result": entry,
1943+
"subentries": (),
1944+
"title": "user",
1945+
"type": FlowResultType.CREATE_ENTRY,
1946+
"version": 1,
1947+
}
1948+
1949+
1950+
@pytest.mark.parametrize(
1951+
("invalid_next_flow", "error"),
1952+
[
1953+
(("invalid_flow_type", "invalid_flow_id"), HomeAssistantError),
1954+
((config_entries.FlowType.CONFIG_FLOW, "invalid_flow_id"), UnknownFlow),
1955+
],
1956+
)
1957+
async def test_create_entry_next_flow_invalid(
1958+
hass: HomeAssistant,
1959+
manager: config_entries.ConfigEntries,
1960+
invalid_next_flow: tuple[str, str],
1961+
error: type[Exception],
1962+
) -> None:
1963+
"""Test next_flow invalid parameter for create entry."""
1964+
1965+
async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
1966+
"""Mock setup."""
1967+
return True
1968+
1969+
async_setup_entry = AsyncMock(return_value=True)
1970+
mock_integration(
1971+
hass,
1972+
MockModule(
1973+
"comp",
1974+
async_setup=mock_async_setup,
1975+
async_setup_entry=async_setup_entry,
1976+
),
1977+
)
1978+
mock_platform(hass, "comp.config_flow", None)
1979+
1980+
class TestFlow(config_entries.ConfigFlow):
1981+
"""Test flow."""
1982+
1983+
async def async_step_import(
1984+
self, user_input: dict[str, Any] | None = None
1985+
) -> config_entries.ConfigFlowResult:
1986+
"""Test create entry with next_flow parameter."""
1987+
await hass.config_entries.flow.async_init(
1988+
"comp",
1989+
context={"source": config_entries.SOURCE_USER},
1990+
)
1991+
return self.async_create_entry(
1992+
title="import",
1993+
data={"flow": "import"},
1994+
next_flow=invalid_next_flow, # type: ignore[arg-type]
1995+
)
1996+
1997+
async def async_step_user(
1998+
self, user_input: dict[str, Any] | None = None
1999+
) -> config_entries.ConfigFlowResult:
2000+
"""Test next step."""
2001+
if user_input is None:
2002+
return self.async_show_form(step_id="user")
2003+
return self.async_create_entry(title="user", data={"flow": "user"})
2004+
2005+
with mock_config_flow("comp", TestFlow):
2006+
assert await async_setup_component(hass, "comp", {})
2007+
2008+
with pytest.raises(error):
2009+
await hass.config_entries.flow.async_init(
2010+
"comp",
2011+
context={"source": config_entries.SOURCE_IMPORT},
2012+
)
2013+
2014+
assert async_setup_entry.call_count == 0
2015+
2016+
18412017
async def test_create_entry_options(
18422018
hass: HomeAssistant, manager: config_entries.ConfigEntries
18432019
) -> None:

0 commit comments

Comments
 (0)