mirror of
https://github.com/home-assistant/core
synced 2024-10-01 05:30:36 +02:00
Add reconfigure step to config flow (#108794)
* Initial commit reconfigure * test config config_entries * Fix reconfigure * test_config_entries * review comment * No reconfigure if reauth ongoing * Fix tests * Fix tests * handle source creating flows * combine * No black * Also check reconfigure in reauth flow * Fix support * Add entry id * reset data entry flow * Mods * context data * reset formatting * Fix config flow platforms * Fix tests * Fix step message * Handling reconfigure step * Fix more tests * Config entries tests * entry_id always means reconfigure * Mods * Remove no longer valid exception * Fixes * reset silabs test * dev reg * resets * assist pipeline * Adjust config_entries * Fix * Fixes * docstrings * Review comment * docstring
This commit is contained in:
parent
fd9e9ebf50
commit
9989a63cdf
@ -13,6 +13,7 @@ from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http import HomeAssistantView, require_admin
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import DependencyError, Unauthorized
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -153,10 +154,26 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
||||
@require_admin(
|
||||
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
|
||||
)
|
||||
async def post(self, request: web.Request) -> web.Response:
|
||||
"""Handle a POST request."""
|
||||
@RequestDataValidator(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required("handler"): vol.Any(str, list),
|
||||
vol.Optional("show_advanced_options", default=False): cv.boolean,
|
||||
vol.Optional("entry_id"): cv.string,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
)
|
||||
async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
|
||||
"""Initialize a POST request for a config entry flow."""
|
||||
return await self._post_impl(request, data)
|
||||
|
||||
async def _post_impl(
|
||||
self, request: web.Request, data: dict[str, Any]
|
||||
) -> web.Response:
|
||||
"""Handle a POST request for a config entry flow."""
|
||||
try:
|
||||
return await super().post(request)
|
||||
return await super()._post_impl(request, data)
|
||||
except DependencyError as exc:
|
||||
return web.Response(
|
||||
text=f"Failed dependencies {', '.join(exc.failed_dependencies)}",
|
||||
@ -167,6 +184,9 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
|
||||
"""Return context."""
|
||||
context = super().get_context(data)
|
||||
context["source"] = config_entries.SOURCE_USER
|
||||
if entry_id := data.get("entry_id"):
|
||||
context["source"] = config_entries.SOURCE_RECONFIGURE
|
||||
context["entry_id"] = entry_id
|
||||
return context
|
||||
|
||||
def _prepare_result_json(
|
||||
|
@ -104,6 +104,9 @@ SOURCE_UNIGNORE = "unignore"
|
||||
# This is used to signal that re-authentication is required by the user.
|
||||
SOURCE_REAUTH = "reauth"
|
||||
|
||||
# This is used to initiate a reconfigure flow by the user.
|
||||
SOURCE_RECONFIGURE = "reconfigure"
|
||||
|
||||
HANDLERS: Registry[str, type[ConfigFlow]] = Registry()
|
||||
|
||||
STORAGE_KEY = "core.config_entries"
|
||||
@ -343,6 +346,9 @@ class ConfigEntry:
|
||||
# Supports options
|
||||
self._supports_options: bool | None = None
|
||||
|
||||
# Supports reconfigure
|
||||
self._supports_reconfigure: bool | None = None
|
||||
|
||||
# Listeners to call on update
|
||||
self.update_listeners: list[UpdateListenerType] = []
|
||||
|
||||
@ -361,6 +367,8 @@ class ConfigEntry:
|
||||
self.reload_lock = asyncio.Lock()
|
||||
# Reauth lock to prevent concurrent reauth flows
|
||||
self._reauth_lock = asyncio.Lock()
|
||||
# Reconfigure lock to prevent concurrent reconfigure flows
|
||||
self._reconfigure_lock = asyncio.Lock()
|
||||
|
||||
self._tasks: set[asyncio.Future[Any]] = set()
|
||||
self._background_tasks: set[asyncio.Future[Any]] = set()
|
||||
@ -413,6 +421,20 @@ class ConfigEntry:
|
||||
)
|
||||
return self._supports_options or False
|
||||
|
||||
@property
|
||||
def supports_reconfigure(self) -> bool:
|
||||
"""Return if entry supports config options."""
|
||||
if self._supports_reconfigure is None and (
|
||||
handler := HANDLERS.get(self.domain)
|
||||
):
|
||||
# work out if handler has support for reconfigure step
|
||||
object.__setattr__(
|
||||
self,
|
||||
"_supports_reconfigure",
|
||||
hasattr(handler, "async_step_reconfigure"),
|
||||
)
|
||||
return self._supports_reconfigure or False
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
"""Clear cached properties."""
|
||||
with contextlib.suppress(AttributeError):
|
||||
@ -430,6 +452,7 @@ class ConfigEntry:
|
||||
"supports_options": self.supports_options,
|
||||
"supports_remove_device": self.supports_remove_device or False,
|
||||
"supports_unload": self.supports_unload or False,
|
||||
"supports_reconfigure": self.supports_reconfigure or False,
|
||||
"pref_disable_new_entities": self.pref_disable_new_entities,
|
||||
"pref_disable_polling": self.pref_disable_polling,
|
||||
"disabled_by": self.disabled_by,
|
||||
@ -462,7 +485,6 @@ class ConfigEntry:
|
||||
self.supports_remove_device = await support_remove_from_device(
|
||||
hass, self.domain
|
||||
)
|
||||
|
||||
try:
|
||||
component = integration.get_component()
|
||||
except ImportError as err:
|
||||
@ -856,8 +878,8 @@ class ConfigEntry:
|
||||
"""Start a reauth flow."""
|
||||
# We will check this again in the task when we hold the lock,
|
||||
# but we also check it now to try to avoid creating the task.
|
||||
if any(self.async_get_active_flows(hass, {SOURCE_REAUTH})):
|
||||
# Reauth flow already in progress for this entry
|
||||
if any(self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})):
|
||||
# Reauth or Reconfigure flow already in progress for this entry
|
||||
return
|
||||
hass.async_create_task(
|
||||
self._async_init_reauth(hass, context, data),
|
||||
@ -872,8 +894,10 @@ class ConfigEntry:
|
||||
) -> None:
|
||||
"""Start a reauth flow."""
|
||||
async with self._reauth_lock:
|
||||
if any(self.async_get_active_flows(hass, {SOURCE_REAUTH})):
|
||||
# Reauth flow already in progress for this entry
|
||||
if any(
|
||||
self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})
|
||||
):
|
||||
# Reauth or Reconfigure flow already in progress for this entry
|
||||
return
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
self.domain,
|
||||
@ -903,6 +927,49 @@ class ConfigEntry:
|
||||
translation_placeholders={"name": self.title},
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_start_reconfigure(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
context: dict[str, Any] | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Start a reconfigure flow."""
|
||||
# We will check this again in the task when we hold the lock,
|
||||
# but we also check it now to try to avoid creating the task.
|
||||
if any(self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})):
|
||||
# Reconfigure or reauth flow already in progress for this entry
|
||||
return
|
||||
hass.async_create_task(
|
||||
self._async_init_reconfigure(hass, context, data),
|
||||
f"config entry reconfigure {self.title} {self.domain} {self.entry_id}",
|
||||
)
|
||||
|
||||
async def _async_init_reconfigure(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
context: dict[str, Any] | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Start a reconfigure flow."""
|
||||
async with self._reconfigure_lock:
|
||||
if any(
|
||||
self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})
|
||||
):
|
||||
# Reconfigure or reauth flow already in progress for this entry
|
||||
return
|
||||
await hass.config_entries.flow.async_init(
|
||||
self.domain,
|
||||
context={
|
||||
"source": SOURCE_RECONFIGURE,
|
||||
"entry_id": self.entry_id,
|
||||
"title_placeholders": {"name": self.title},
|
||||
"unique_id": self.unique_id,
|
||||
}
|
||||
| (context or {}),
|
||||
data=self.data | (data or {}),
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_get_active_flows(
|
||||
self, hass: HomeAssistant, sources: set[str]
|
||||
|
@ -61,6 +61,16 @@ class FlowManagerIndexView(_BaseFlowManagerView):
|
||||
)
|
||||
)
|
||||
async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
|
||||
"""Initialize a POST request.
|
||||
|
||||
Override `_post_impl` in subclasses which need
|
||||
to implement their own `RequestDataValidator`
|
||||
"""
|
||||
return await self._post_impl(request, data)
|
||||
|
||||
async def _post_impl(
|
||||
self, request: web.Request, data: dict[str, Any]
|
||||
) -> web.Response:
|
||||
"""Handle a POST request."""
|
||||
if isinstance(data["handler"], list):
|
||||
handler = tuple(data["handler"])
|
||||
@ -74,10 +84,8 @@ class FlowManagerIndexView(_BaseFlowManagerView):
|
||||
)
|
||||
except data_entry_flow.UnknownHandler:
|
||||
return self.json_message("Invalid handler specified", HTTPStatus.NOT_FOUND)
|
||||
except data_entry_flow.UnknownStep:
|
||||
return self.json_message(
|
||||
"Handler does not support user", HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
except data_entry_flow.UnknownStep as err:
|
||||
return self.json_message(str(err), HTTPStatus.BAD_REQUEST)
|
||||
|
||||
result = self._prepare_result_json(result)
|
||||
|
||||
|
@ -3,6 +3,7 @@ from collections import OrderedDict
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import ANY, AsyncMock, patch
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
@ -39,7 +40,7 @@ def mock_test_component(hass):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(hass, hass_client):
|
||||
async def client(hass, hass_client) -> TestClient:
|
||||
"""Fixture that can interact with the config manager API."""
|
||||
await async_setup_component(hass, "http", {})
|
||||
config_entries.async_setup(hass)
|
||||
@ -121,6 +122,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
|
||||
"title": "Test 1",
|
||||
"source": "bla",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": True,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": True,
|
||||
@ -134,6 +136,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
|
||||
"title": "Test 2",
|
||||
"source": "bla2",
|
||||
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -147,6 +150,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
|
||||
"title": "Test 3",
|
||||
"source": "bla3",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -160,6 +164,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
|
||||
"title": "Test 4",
|
||||
"source": "bla4",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -173,6 +178,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None:
|
||||
"title": "Test 5",
|
||||
"source": "bla5",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -521,6 +527,7 @@ async def test_create_account(
|
||||
"entry_id": entries[0].entry_id,
|
||||
"source": core_ce.SOURCE_USER,
|
||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -599,6 +606,7 @@ async def test_two_step_flow(
|
||||
"entry_id": entries[0].entry_id,
|
||||
"source": core_ce.SOURCE_USER,
|
||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1051,6 +1059,7 @@ async def test_get_single(
|
||||
"reason": None,
|
||||
"source": "user",
|
||||
"state": "loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1385,6 +1394,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1399,6 +1409,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1413,6 +1424,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1427,6 +1439,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1441,6 +1454,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1466,6 +1480,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1490,6 +1505,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1504,6 +1520,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1528,6 +1545,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1542,6 +1560,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1572,6 +1591,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1586,6 +1606,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1600,6 +1621,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1614,6 +1636,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1628,6 +1651,7 @@ async def test_get_matching_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1726,6 +1750,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1743,6 +1768,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1760,6 +1786,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1781,6 +1808,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1803,6 +1831,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1825,6 +1854,7 @@ async def test_subscribe_entries_ws(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1906,6 +1936,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1923,6 +1954,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1946,6 +1978,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1967,6 +2000,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -1990,6 +2024,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -2012,6 +2047,7 @@ async def test_subscribe_entries_ws_filtered(
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supports_reconfigure": False,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
@ -2106,3 +2142,123 @@ async def test_flow_with_multiple_schema_errors_base(
|
||||
"latitude": "required key not provided",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_supports_reconfigure(
|
||||
hass: HomeAssistant, client, enable_custom_integrations: None
|
||||
) -> None:
|
||||
"""Test a flow that support reconfigure step."""
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
mock_integration(
|
||||
hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True))
|
||||
)
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
return self.async_create_entry(
|
||||
title="Test Entry", data={"secret": "account_token"}
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(self, user_input=None):
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure", data_schema=vol.Schema({})
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title="Test Entry", data={"secret": "account_token"}
|
||||
)
|
||||
|
||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||
resp = await client.post(
|
||||
"/api/config/config_entries/flow",
|
||||
json={"handler": "test", "entry_id": "1"},
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
|
||||
data = await resp.json()
|
||||
flow_id = data.pop("flow_id")
|
||||
|
||||
assert data == {
|
||||
"type": "form",
|
||||
"handler": "test",
|
||||
"step_id": "reconfigure",
|
||||
"data_schema": [],
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
"description_placeholders": None,
|
||||
"errors": None,
|
||||
}
|
||||
|
||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||
resp = await client.post(
|
||||
f"/api/config/config_entries/flow/{flow_id}",
|
||||
json={},
|
||||
)
|
||||
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": "test",
|
||||
"title": "Test Entry",
|
||||
"type": "create_entry",
|
||||
"version": 1,
|
||||
"result": {
|
||||
"disabled_by": None,
|
||||
"domain": "test",
|
||||
"entry_id": entries[0].entry_id,
|
||||
"source": core_ce.SOURCE_RECONFIGURE,
|
||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||
"supports_reconfigure": True,
|
||||
"supports_options": False,
|
||||
"supports_remove_device": False,
|
||||
"supports_unload": False,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"title": "Test Entry",
|
||||
"reason": None,
|
||||
},
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
}
|
||||
|
||||
|
||||
async def test_does_not_support_reconfigure(
|
||||
hass: HomeAssistant, client: TestClient, enable_custom_integrations: None
|
||||
) -> None:
|
||||
"""Test a flow that does not support reconfigure step."""
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
mock_integration(
|
||||
hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True))
|
||||
)
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
return self.async_create_entry(
|
||||
title="Test Entry", data={"secret": "account_token"}
|
||||
)
|
||||
|
||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||
resp = await client.post(
|
||||
"/api/config/config_entries/flow",
|
||||
json={"handler": "test", "entry_id": "1"},
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
response = await resp.text()
|
||||
assert (
|
||||
response
|
||||
== '{"message":"Handler ConfigEntriesFlowManager doesn\'t support step reconfigure"}'
|
||||
)
|
||||
|
@ -70,6 +70,10 @@ def mock_handlers() -> Generator[None, None, None]:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return self.async_abort(reason="test")
|
||||
|
||||
async def async_step_reconfigure(self, data):
|
||||
"""Mock Reauth."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
with patch.dict(
|
||||
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
||||
):
|
||||
@ -826,6 +830,8 @@ async def test_as_dict(snapshot: SnapshotAssertion) -> None:
|
||||
"_tries",
|
||||
"_setup_again_job",
|
||||
"_supports_options",
|
||||
"_reconfigure_lock",
|
||||
"supports_reconfigure",
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(entry_id="mock-entry")
|
||||
@ -4062,6 +4068,94 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
|
||||
async def test_reconfigure(hass: HomeAssistant) -> None:
|
||||
"""Test the async_reconfigure_helper."""
|
||||
entry = MockConfigEntry(title="test_title", domain="test")
|
||||
entry2 = MockConfigEntry(title="test_title", domain="test")
|
||||
|
||||
mock_setup_entry = AsyncMock(return_value=True)
|
||||
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
await entry.async_setup(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
flow = hass.config_entries.flow
|
||||
with patch.object(flow, "async_init", wraps=flow.async_init) as mock_init:
|
||||
entry.async_start_reconfigure(
|
||||
hass,
|
||||
context={"extra_context": "some_extra_context"},
|
||||
data={"extra_data": 1234},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
||||
assert flows[0]["context"]["source"] == config_entries.SOURCE_RECONFIGURE
|
||||
assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"}
|
||||
assert flows[0]["context"]["extra_context"] == "some_extra_context"
|
||||
|
||||
assert mock_init.call_args.kwargs["data"]["extra_data"] == 1234
|
||||
|
||||
assert entry.entry_id != entry2.entry_id
|
||||
|
||||
# Check that we can't start duplicate reconfigure flows
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
# Check that we can't start duplicate reconfigure flows when the context is different
|
||||
entry.async_start_reconfigure(hass, {"diff": "diff"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
# Check that we can start a reconfigure flow for a different entry
|
||||
entry2.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 2
|
||||
|
||||
# Abort all existing flows
|
||||
for flow in hass.config_entries.flow.async_progress():
|
||||
hass.config_entries.flow.async_abort(flow["flow_id"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that we can't start duplicate reconfigure flows
|
||||
# without blocking between flows
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
# Abort all existing flows
|
||||
for flow in hass.config_entries.flow.async_progress():
|
||||
hass.config_entries.flow.async_abort(flow["flow_id"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that we can't start reconfigure flows with active reauth flow
|
||||
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
# Abort all existing flows
|
||||
for flow in hass.config_entries.flow.async_progress():
|
||||
hass.config_entries.flow.async_abort(flow["flow_id"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check that we can't start reauth flows with active reconfigure flow
|
||||
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.config_entries.flow.async_progress()) == 1
|
||||
|
||||
|
||||
async def test_get_active_flows(hass: HomeAssistant) -> None:
|
||||
"""Test the async_get_active_flows helper."""
|
||||
entry = MockConfigEntry(title="test_title", domain="test")
|
||||
|
Loading…
Reference in New Issue
Block a user