Handle late abort when creating subentry (#145765)
* Handle late abort when creating subentry * Move error handling to the base class * Narrow down expected error in testpull/144056/head
parent
e4cc842584
commit
a857461059
|
@ -543,8 +543,17 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]):
|
|||
flow.cur_step = result
|
||||
return result
|
||||
|
||||
# We pass a copy of the result because we're mutating our version
|
||||
result = await self.async_finish_flow(flow, result.copy())
|
||||
try:
|
||||
# We pass a copy of the result because we're mutating our version
|
||||
result = await self.async_finish_flow(flow, result.copy())
|
||||
except AbortFlow as err:
|
||||
result = self._flow_result(
|
||||
type=FlowResultType.ABORT,
|
||||
flow_id=flow.flow_id,
|
||||
handler=flow.handler,
|
||||
reason=err.reason,
|
||||
description_placeholders=err.description_placeholders,
|
||||
)
|
||||
|
||||
# _async_finish_flow may change result type, check it again
|
||||
if result["type"] == FlowResultType.FORM:
|
||||
|
|
|
@ -1526,6 +1526,88 @@ async def test_subentry_reconfigure_flow(hass: HomeAssistant, client) -> None:
|
|||
}
|
||||
|
||||
|
||||
async def test_subentry_flow_abort_duplicate(hass: HomeAssistant, client) -> None:
|
||||
"""Test we can handle a subentry flow raising due to unique_id collision."""
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
class SubentryFlowHandler(core_ce.ConfigSubentryFlow):
|
||||
async def async_step_user(self, user_input=None):
|
||||
return await self.async_step_finish()
|
||||
|
||||
async def async_step_finish(self, user_input=None):
|
||||
if user_input:
|
||||
return self.async_create_entry(
|
||||
title="Mock title", data=user_input, unique_id="test"
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="finish", data_schema=vol.Schema({"enabled": bool})
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_get_supported_subentry_types(
|
||||
cls, config_entry: core_ce.ConfigEntry
|
||||
) -> dict[str, type[core_ce.ConfigSubentryFlow]]:
|
||||
return {"test": TestFlow.SubentryFlowHandler}
|
||||
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
MockConfigEntry(
|
||||
domain="test",
|
||||
entry_id="test1",
|
||||
source="bla",
|
||||
subentries_data=[
|
||||
core_ce.ConfigSubentryData(
|
||||
data={},
|
||||
subentry_id="mock_id",
|
||||
subentry_type="test",
|
||||
title="Title",
|
||||
unique_id="test",
|
||||
)
|
||||
],
|
||||
).add_to_hass(hass)
|
||||
entry = hass.config_entries.async_entries()[0]
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
url = "/api/config/config_entries/subentries/flow"
|
||||
resp = await client.post(url, json={"handler": [entry.entry_id, "test"]})
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data.pop("flow_id")
|
||||
assert data == {
|
||||
"type": "form",
|
||||
"handler": ["test1", "test"],
|
||||
"step_id": "finish",
|
||||
"data_schema": [{"name": "enabled", "type": "boolean"}],
|
||||
"description_placeholders": None,
|
||||
"errors": None,
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
}
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
resp = await client.post(
|
||||
f"/api/config/config_entries/subentries/flow/{flow_id}",
|
||||
json={"enabled": True},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
|
||||
entries = hass.config_entries.async_entries("test")
|
||||
assert len(entries) == 1
|
||||
|
||||
data = await resp.json()
|
||||
data.pop("flow_id")
|
||||
assert data == {
|
||||
"handler": ["test1", "test"],
|
||||
"reason": "already_configured",
|
||||
"type": "abort",
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
|
||||
async def test_subentry_does_not_support_reconfigure(
|
||||
hass: HomeAssistant, client: TestClient
|
||||
) -> None:
|
||||
|
|
|
@ -2226,7 +2226,7 @@ async def test_entry_subentry_no_context(
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
("unique_id", "expected_result"),
|
||||
[(None, does_not_raise()), ("test", pytest.raises(HomeAssistantError))],
|
||||
[(None, does_not_raise()), ("test", pytest.raises(data_entry_flow.AbortFlow))],
|
||||
)
|
||||
async def test_entry_subentry_duplicate(
|
||||
hass: HomeAssistant,
|
||||
|
|
|
@ -886,8 +886,8 @@ async def test_show_progress_fires_only_when_changed(
|
|||
) # change (description placeholder)
|
||||
|
||||
|
||||
async def test_abort_flow_exception(manager: MockFlowManager) -> None:
|
||||
"""Test that the AbortFlow exception works."""
|
||||
async def test_abort_flow_exception_step(manager: MockFlowManager) -> None:
|
||||
"""Test that the AbortFlow exception works in a step."""
|
||||
|
||||
@manager.mock_reg_handler("test")
|
||||
class TestFlow(data_entry_flow.FlowHandler):
|
||||
|
@ -900,6 +900,33 @@ async def test_abort_flow_exception(manager: MockFlowManager) -> None:
|
|||
assert form["description_placeholders"] == {"placeholder": "yo"}
|
||||
|
||||
|
||||
async def test_abort_flow_exception_finish_flow(hass: HomeAssistant) -> None:
|
||||
"""Test that the AbortFlow exception works when finishing a flow."""
|
||||
|
||||
class TestFlow(data_entry_flow.FlowHandler):
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_init(self, input):
|
||||
"""Return init form with one input field 'count'."""
|
||||
return self.async_create_entry(title="init", data=input)
|
||||
|
||||
class FlowManager(data_entry_flow.FlowManager):
|
||||
async def async_create_flow(self, handler_key, *, context, data):
|
||||
"""Create a test flow."""
|
||||
return TestFlow()
|
||||
|
||||
async def async_finish_flow(self, flow, result):
|
||||
"""Raise AbortFlow."""
|
||||
raise data_entry_flow.AbortFlow("mock-reason", {"placeholder": "yo"})
|
||||
|
||||
manager = FlowManager(hass)
|
||||
|
||||
form = await manager.async_init("test")
|
||||
assert form["type"] == data_entry_flow.FlowResultType.ABORT
|
||||
assert form["reason"] == "mock-reason"
|
||||
assert form["description_placeholders"] == {"placeholder": "yo"}
|
||||
|
||||
|
||||
async def test_init_unknown_flow(manager: MockFlowManager) -> None:
|
||||
"""Test that UnknownFlow is raised when async_create_flow returns None."""
|
||||
|
||||
|
|
Loading…
Reference in New Issue