Revert "Add support for subentries to config entries" (#133470)
Revert "Add support for subentries to config entries (#117355)"
This reverts commit ad15786115
.
pull/133488/head
parent
2aba1d399b
commit
ecb3bf79f3
|
@ -46,13 +46,6 @@ def async_setup(hass: HomeAssistant) -> bool:
|
|||
hass.http.register_view(OptionManagerFlowIndexView(hass.config_entries.options))
|
||||
hass.http.register_view(OptionManagerFlowResourceView(hass.config_entries.options))
|
||||
|
||||
hass.http.register_view(
|
||||
SubentryManagerFlowIndexView(hass.config_entries.subentries)
|
||||
)
|
||||
hass.http.register_view(
|
||||
SubentryManagerFlowResourceView(hass.config_entries.subentries)
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(hass, config_entries_get)
|
||||
websocket_api.async_register_command(hass, config_entry_disable)
|
||||
websocket_api.async_register_command(hass, config_entry_get_single)
|
||||
|
@ -61,9 +54,6 @@ def async_setup(hass: HomeAssistant) -> bool:
|
|||
websocket_api.async_register_command(hass, config_entries_progress)
|
||||
websocket_api.async_register_command(hass, ignore_config_flow)
|
||||
|
||||
websocket_api.async_register_command(hass, config_subentry_delete)
|
||||
websocket_api.async_register_command(hass, config_subentry_list)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -295,63 +285,6 @@ class OptionManagerFlowResourceView(
|
|||
return await super().post(request, flow_id)
|
||||
|
||||
|
||||
class SubentryManagerFlowIndexView(
|
||||
FlowManagerIndexView[config_entries.ConfigSubentryFlowManager]
|
||||
):
|
||||
"""View to create subentry flows."""
|
||||
|
||||
url = "/api/config/config_entries/subentries/flow"
|
||||
name = "api:config:config_entries:subentries:flow"
|
||||
|
||||
@require_admin(
|
||||
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
|
||||
)
|
||||
@RequestDataValidator(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required("handler"): vol.All(vol.Coerce(tuple), (str, str)),
|
||||
vol.Optional("show_advanced_options", default=False): cv.boolean,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
)
|
||||
async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
|
||||
"""Handle a POST request.
|
||||
|
||||
handler in request is [entry_id, subentry_type].
|
||||
"""
|
||||
return await super()._post_impl(request, data)
|
||||
|
||||
def get_context(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return context."""
|
||||
context = super().get_context(data)
|
||||
context["source"] = config_entries.SOURCE_USER
|
||||
return context
|
||||
|
||||
|
||||
class SubentryManagerFlowResourceView(
|
||||
FlowManagerResourceView[config_entries.ConfigSubentryFlowManager]
|
||||
):
|
||||
"""View to interact with the subentry flow manager."""
|
||||
|
||||
url = "/api/config/config_entries/subentries/flow/{flow_id}"
|
||||
name = "api:config:config_entries:subentries:flow:resource"
|
||||
|
||||
@require_admin(
|
||||
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
|
||||
)
|
||||
async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
|
||||
"""Get the current state of a data_entry_flow."""
|
||||
return await super().get(request, flow_id)
|
||||
|
||||
@require_admin(
|
||||
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
|
||||
)
|
||||
async def post(self, request: web.Request, flow_id: str) -> web.Response:
|
||||
"""Handle a POST request."""
|
||||
return await super().post(request, flow_id)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command({"type": "config_entries/flow/progress"})
|
||||
def config_entries_progress(
|
||||
|
@ -655,62 +588,3 @@ async def _async_matching_config_entries_json_fragments(
|
|||
)
|
||||
or (filter_is_not_helper and entry.domain not in integrations)
|
||||
]
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
"type": "config_entries/subentries/list",
|
||||
"entry_id": str,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
async def config_subentry_list(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""List subentries of a config entry."""
|
||||
entry = get_entry(hass, connection, msg["entry_id"], msg["id"])
|
||||
if entry is None:
|
||||
return
|
||||
|
||||
result = [
|
||||
{
|
||||
"subentry_id": subentry.subentry_id,
|
||||
"title": subentry.title,
|
||||
"unique_id": subentry.unique_id,
|
||||
}
|
||||
for subentry_id, subentry in entry.subentries.items()
|
||||
]
|
||||
connection.send_result(msg["id"], result)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
"type": "config_entries/subentries/delete",
|
||||
"entry_id": str,
|
||||
"subentry_id": str,
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
async def config_subentry_delete(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Delete a subentry of a config entry."""
|
||||
entry = get_entry(hass, connection, msg["entry_id"], msg["id"])
|
||||
if entry is None:
|
||||
return
|
||||
|
||||
try:
|
||||
hass.config_entries.async_remove_subentry(entry, msg["subentry_id"])
|
||||
except config_entries.UnknownSubEntry:
|
||||
connection.send_error(
|
||||
msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config subentry not found"
|
||||
)
|
||||
return
|
||||
|
||||
connection.send_result(msg["id"])
|
||||
|
|
|
@ -15,7 +15,6 @@ from collections.abc import (
|
|||
)
|
||||
from contextvars import ContextVar
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum, StrEnum
|
||||
import functools
|
||||
|
@ -23,7 +22,7 @@ from functools import cache
|
|||
import logging
|
||||
from random import randint
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, Generic, Self, TypedDict, cast
|
||||
from typing import TYPE_CHECKING, Any, Generic, Self, cast
|
||||
|
||||
from async_interrupt import interrupt
|
||||
from propcache import cached_property
|
||||
|
@ -129,7 +128,7 @@ HANDLERS: Registry[str, type[ConfigFlow]] = Registry()
|
|||
|
||||
STORAGE_KEY = "core.config_entries"
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_VERSION_MINOR = 5
|
||||
STORAGE_VERSION_MINOR = 4
|
||||
|
||||
SAVE_DELAY = 1
|
||||
|
||||
|
@ -257,10 +256,6 @@ class UnknownEntry(ConfigError):
|
|||
"""Unknown entry specified."""
|
||||
|
||||
|
||||
class UnknownSubEntry(ConfigError):
|
||||
"""Unknown subentry specified."""
|
||||
|
||||
|
||||
class OperationNotAllowed(ConfigError):
|
||||
"""Raised when a config entry operation is not allowed."""
|
||||
|
||||
|
@ -305,7 +300,6 @@ class ConfigFlowResult(FlowResult[ConfigFlowContext, str], total=False):
|
|||
|
||||
minor_version: int
|
||||
options: Mapping[str, Any]
|
||||
subentries: Iterable[ConfigSubentryData]
|
||||
version: int
|
||||
|
||||
|
||||
|
@ -319,51 +313,6 @@ def _validate_item(*, disabled_by: ConfigEntryDisabler | Any | None = None) -> N
|
|||
)
|
||||
|
||||
|
||||
class ConfigSubentryData(TypedDict):
|
||||
"""Container for configuration subentry data.
|
||||
|
||||
Returned by integrations, a subentry_id will be assigned automatically.
|
||||
"""
|
||||
|
||||
data: Mapping[str, Any]
|
||||
title: str
|
||||
unique_id: str | None
|
||||
|
||||
|
||||
class ConfigSubentryDataWithId(ConfigSubentryData):
|
||||
"""Container for configuration subentry data.
|
||||
|
||||
This type is used when loading existing subentries from storage.
|
||||
"""
|
||||
|
||||
subentry_id: str
|
||||
|
||||
|
||||
class SubentryFlowResult(FlowResult[FlowContext, tuple[str, str]], total=False):
|
||||
"""Typed result dict for subentry flow."""
|
||||
|
||||
unique_id: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ConfigSubentry:
|
||||
"""Container for a configuration subentry."""
|
||||
|
||||
data: MappingProxyType[str, Any]
|
||||
subentry_id: str = field(default_factory=ulid_util.ulid_now)
|
||||
title: str
|
||||
unique_id: str | None
|
||||
|
||||
def as_dict(self) -> ConfigSubentryDataWithId:
|
||||
"""Return dictionary version of this subentry."""
|
||||
return {
|
||||
"data": dict(self.data),
|
||||
"subentry_id": self.subentry_id,
|
||||
"title": self.title,
|
||||
"unique_id": self.unique_id,
|
||||
}
|
||||
|
||||
|
||||
class ConfigEntry(Generic[_DataT]):
|
||||
"""Hold a configuration entry."""
|
||||
|
||||
|
@ -373,7 +322,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
data: MappingProxyType[str, Any]
|
||||
runtime_data: _DataT
|
||||
options: MappingProxyType[str, Any]
|
||||
subentries: MappingProxyType[str, ConfigSubentry]
|
||||
unique_id: str | None
|
||||
state: ConfigEntryState
|
||||
reason: str | None
|
||||
|
@ -389,7 +337,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
supports_remove_device: bool | None
|
||||
_supports_options: bool | None
|
||||
_supports_reconfigure: bool | None
|
||||
_supported_subentries: tuple[str, ...] | None
|
||||
update_listeners: list[UpdateListenerType]
|
||||
_async_cancel_retry_setup: Callable[[], Any] | None
|
||||
_on_unload: list[Callable[[], Coroutine[Any, Any, None] | None]] | None
|
||||
|
@ -419,7 +366,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
pref_disable_polling: bool | None = None,
|
||||
source: str,
|
||||
state: ConfigEntryState = ConfigEntryState.NOT_LOADED,
|
||||
subentries_data: Iterable[ConfigSubentryData | ConfigSubentryDataWithId] | None,
|
||||
title: str,
|
||||
unique_id: str | None,
|
||||
version: int,
|
||||
|
@ -445,24 +391,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
# Entry options
|
||||
_setter(self, "options", MappingProxyType(options or {}))
|
||||
|
||||
# Subentries
|
||||
subentries_data = subentries_data or ()
|
||||
subentries = {}
|
||||
for subentry_data in subentries_data:
|
||||
subentry_kwargs = {}
|
||||
if "subentry_id" in subentry_data:
|
||||
# If subentry_data has key "subentry_id", we're loading from storage
|
||||
subentry_kwargs["subentry_id"] = subentry_data["subentry_id"] # type: ignore[typeddict-item]
|
||||
subentry = ConfigSubentry(
|
||||
data=MappingProxyType(subentry_data["data"]),
|
||||
title=subentry_data["title"],
|
||||
unique_id=subentry_data.get("unique_id"),
|
||||
**subentry_kwargs,
|
||||
)
|
||||
subentries[subentry.subentry_id] = subentry
|
||||
|
||||
_setter(self, "subentries", MappingProxyType(subentries))
|
||||
|
||||
# Entry system options
|
||||
if pref_disable_new_entities is None:
|
||||
pref_disable_new_entities = False
|
||||
|
@ -499,9 +427,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
# Supports reconfigure
|
||||
_setter(self, "_supports_reconfigure", None)
|
||||
|
||||
# Supports subentries
|
||||
_setter(self, "_supported_subentries", None)
|
||||
|
||||
# Listeners to call on update
|
||||
_setter(self, "update_listeners", [])
|
||||
|
||||
|
@ -574,18 +499,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
)
|
||||
return self._supports_reconfigure or False
|
||||
|
||||
@property
|
||||
def supported_subentries(self) -> tuple[str, ...]:
|
||||
"""Return supported subentries."""
|
||||
if self._supported_subentries is None and (
|
||||
handler := HANDLERS.get(self.domain)
|
||||
):
|
||||
# work out sub entries supported by the handler
|
||||
object.__setattr__(
|
||||
self, "_supported_subentries", handler.async_supported_subentries(self)
|
||||
)
|
||||
return self._supported_subentries or ()
|
||||
|
||||
def clear_state_cache(self) -> None:
|
||||
"""Clear cached properties that are included in as_json_fragment."""
|
||||
self.__dict__.pop("as_json_fragment", None)
|
||||
|
@ -605,14 +518,12 @@ class ConfigEntry(Generic[_DataT]):
|
|||
"supports_remove_device": self.supports_remove_device or False,
|
||||
"supports_unload": self.supports_unload or False,
|
||||
"supports_reconfigure": self.supports_reconfigure,
|
||||
"supported_subentries": self.supported_subentries,
|
||||
"pref_disable_new_entities": self.pref_disable_new_entities,
|
||||
"pref_disable_polling": self.pref_disable_polling,
|
||||
"disabled_by": self.disabled_by,
|
||||
"reason": self.reason,
|
||||
"error_reason_translation_key": self.error_reason_translation_key,
|
||||
"error_reason_translation_placeholders": self.error_reason_translation_placeholders,
|
||||
"num_subentries": len(self.subentries),
|
||||
}
|
||||
return json_fragment(json_bytes(json_repr))
|
||||
|
||||
|
@ -1107,7 +1018,6 @@ class ConfigEntry(Generic[_DataT]):
|
|||
"pref_disable_new_entities": self.pref_disable_new_entities,
|
||||
"pref_disable_polling": self.pref_disable_polling,
|
||||
"source": self.source,
|
||||
"subentries": [subentry.as_dict() for subentry in self.subentries.values()],
|
||||
"title": self.title,
|
||||
"unique_id": self.unique_id,
|
||||
"version": self.version,
|
||||
|
@ -1593,7 +1503,6 @@ class ConfigEntriesFlowManager(
|
|||
minor_version=result["minor_version"],
|
||||
options=result["options"],
|
||||
source=flow.context["source"],
|
||||
subentries_data=result["subentries"],
|
||||
title=result["title"],
|
||||
unique_id=flow.unique_id,
|
||||
version=result["version"],
|
||||
|
@ -1884,11 +1793,6 @@ class ConfigEntryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
|
|||
for entry in data["entries"]:
|
||||
entry["discovery_keys"] = {}
|
||||
|
||||
if old_minor_version < 5:
|
||||
# Version 1.4 adds config subentries
|
||||
for entry in data["entries"]:
|
||||
entry.setdefault("subentries", entry.get("subentries", {}))
|
||||
|
||||
if old_major_version > 1:
|
||||
raise NotImplementedError
|
||||
return data
|
||||
|
@ -1905,7 +1809,6 @@ class ConfigEntries:
|
|||
self.hass = hass
|
||||
self.flow = ConfigEntriesFlowManager(hass, self, hass_config)
|
||||
self.options = OptionsFlowManager(hass)
|
||||
self.subentries = ConfigSubentryFlowManager(hass)
|
||||
self._hass_config = hass_config
|
||||
self._entries = ConfigEntryItems(hass)
|
||||
self._store = ConfigEntryStore(hass)
|
||||
|
@ -2108,7 +2011,6 @@ class ConfigEntries:
|
|||
pref_disable_new_entities=entry["pref_disable_new_entities"],
|
||||
pref_disable_polling=entry["pref_disable_polling"],
|
||||
source=entry["source"],
|
||||
subentries_data=entry["subentries"],
|
||||
title=entry["title"],
|
||||
unique_id=entry["unique_id"],
|
||||
version=entry["version"],
|
||||
|
@ -2268,44 +2170,6 @@ class ConfigEntries:
|
|||
If the entry was changed, the update_listeners are
|
||||
fired and this function returns True
|
||||
|
||||
If the entry was not changed, the update_listeners are
|
||||
not fired and this function returns False
|
||||
"""
|
||||
return self._async_update_entry(
|
||||
entry,
|
||||
data=data,
|
||||
discovery_keys=discovery_keys,
|
||||
minor_version=minor_version,
|
||||
options=options,
|
||||
pref_disable_new_entities=pref_disable_new_entities,
|
||||
pref_disable_polling=pref_disable_polling,
|
||||
title=title,
|
||||
unique_id=unique_id,
|
||||
version=version,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_update_entry(
|
||||
self,
|
||||
entry: ConfigEntry,
|
||||
*,
|
||||
data: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
discovery_keys: MappingProxyType[str, tuple[DiscoveryKey, ...]]
|
||||
| UndefinedType = UNDEFINED,
|
||||
minor_version: int | UndefinedType = UNDEFINED,
|
||||
options: Mapping[str, Any] | UndefinedType = UNDEFINED,
|
||||
pref_disable_new_entities: bool | UndefinedType = UNDEFINED,
|
||||
pref_disable_polling: bool | UndefinedType = UNDEFINED,
|
||||
subentries: dict[str, ConfigSubentry] | UndefinedType = UNDEFINED,
|
||||
title: str | UndefinedType = UNDEFINED,
|
||||
unique_id: str | None | UndefinedType = UNDEFINED,
|
||||
version: int | UndefinedType = UNDEFINED,
|
||||
) -> bool:
|
||||
"""Update a config entry.
|
||||
|
||||
If the entry was changed, the update_listeners are
|
||||
fired and this function returns True
|
||||
|
||||
If the entry was not changed, the update_listeners are
|
||||
not fired and this function returns False
|
||||
"""
|
||||
|
@ -2368,11 +2232,6 @@ class ConfigEntries:
|
|||
changed = True
|
||||
_setter(entry, "options", MappingProxyType(options))
|
||||
|
||||
if subentries is not UNDEFINED:
|
||||
if entry.subentries != subentries:
|
||||
changed = True
|
||||
_setter(entry, "subentries", MappingProxyType(subentries))
|
||||
|
||||
if not changed:
|
||||
return False
|
||||
|
||||
|
@ -2390,37 +2249,6 @@ class ConfigEntries:
|
|||
self._async_dispatch(ConfigEntryChange.UPDATED, entry)
|
||||
return True
|
||||
|
||||
@callback
|
||||
def async_add_subentry(self, entry: ConfigEntry, subentry: ConfigSubentry) -> bool:
|
||||
"""Add a subentry to a config entry."""
|
||||
self._raise_if_subentry_unique_id_exists(entry, subentry.unique_id)
|
||||
|
||||
return self._async_update_entry(
|
||||
entry,
|
||||
subentries=entry.subentries | {subentry.subentry_id: subentry},
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_remove_subentry(self, entry: ConfigEntry, subentry_id: str) -> bool:
|
||||
"""Remove a subentry from a config entry."""
|
||||
subentries = dict(entry.subentries)
|
||||
try:
|
||||
subentries.pop(subentry_id)
|
||||
except KeyError as err:
|
||||
raise UnknownSubEntry from err
|
||||
|
||||
return self._async_update_entry(entry, subentries=subentries)
|
||||
|
||||
def _raise_if_subentry_unique_id_exists(
|
||||
self, entry: ConfigEntry, unique_id: str | None
|
||||
) -> None:
|
||||
"""Raise if a subentry with the same unique_id exists."""
|
||||
if unique_id is None:
|
||||
return
|
||||
for existing_subentry in entry.subentries.values():
|
||||
if existing_subentry.unique_id == unique_id:
|
||||
raise data_entry_flow.AbortFlow("already_configured")
|
||||
|
||||
@callback
|
||||
def _async_dispatch(
|
||||
self, change_type: ConfigEntryChange, entry: ConfigEntry
|
||||
|
@ -2757,20 +2585,6 @@ class ConfigFlow(ConfigEntryBaseFlow):
|
|||
"""Return options flow support for this handler."""
|
||||
return cls.async_get_options_flow is not ConfigFlow.async_get_options_flow
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(
|
||||
config_entry: ConfigEntry, subentry_type: str
|
||||
) -> ConfigSubentryFlow:
|
||||
"""Get the subentry flow for this handler."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(cls, config_entry: ConfigEntry) -> tuple[str, ...]:
|
||||
"""Return subentries supported by this handler."""
|
||||
return ()
|
||||
|
||||
@callback
|
||||
def _async_abort_entries_match(
|
||||
self, match_dict: dict[str, Any] | None = None
|
||||
|
@ -3079,7 +2893,6 @@ class ConfigFlow(ConfigEntryBaseFlow):
|
|||
description: str | None = None,
|
||||
description_placeholders: Mapping[str, str] | None = None,
|
||||
options: Mapping[str, Any] | None = None,
|
||||
subentries: Iterable[ConfigSubentryData] | None = None,
|
||||
) -> ConfigFlowResult:
|
||||
"""Finish config flow and create a config entry."""
|
||||
if self.source in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
|
||||
|
@ -3099,7 +2912,6 @@ class ConfigFlow(ConfigEntryBaseFlow):
|
|||
|
||||
result["minor_version"] = self.MINOR_VERSION
|
||||
result["options"] = options or {}
|
||||
result["subentries"] = subentries or ()
|
||||
result["version"] = self.VERSION
|
||||
|
||||
return result
|
||||
|
@ -3214,126 +3026,17 @@ class ConfigFlow(ConfigEntryBaseFlow):
|
|||
)
|
||||
|
||||
|
||||
class _ConfigSubFlowManager:
|
||||
"""Mixin class for flow managers which manage flows tied to a config entry."""
|
||||
class OptionsFlowManager(
|
||||
data_entry_flow.FlowManager[ConfigFlowContext, ConfigFlowResult]
|
||||
):
|
||||
"""Flow to set options for a configuration entry."""
|
||||
|
||||
hass: HomeAssistant
|
||||
_flow_result = ConfigFlowResult
|
||||
|
||||
def _async_get_config_entry(self, config_entry_id: str) -> ConfigEntry:
|
||||
"""Return config entry or raise if not found."""
|
||||
return self.hass.config_entries.async_get_known_entry(config_entry_id)
|
||||
|
||||
|
||||
class ConfigSubentryFlowManager(
|
||||
data_entry_flow.FlowManager[FlowContext, SubentryFlowResult, tuple[str, str]],
|
||||
_ConfigSubFlowManager,
|
||||
):
|
||||
"""Manage all the config subentry flows that are in progress."""
|
||||
|
||||
_flow_result = SubentryFlowResult
|
||||
|
||||
async def async_create_flow(
|
||||
self,
|
||||
handler_key: tuple[str, str],
|
||||
*,
|
||||
context: FlowContext | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> ConfigSubentryFlow:
|
||||
"""Create a subentry flow for a config entry.
|
||||
|
||||
The entry_id and flow.handler[0] is the same thing to map entry with flow.
|
||||
"""
|
||||
if not context or "source" not in context:
|
||||
raise KeyError("Context not set or doesn't have a source set")
|
||||
|
||||
entry_id, subentry_type = handler_key
|
||||
entry = self._async_get_config_entry(entry_id)
|
||||
handler = await _async_get_flow_handler(self.hass, entry.domain, {})
|
||||
if subentry_type not in handler.async_supported_subentries(entry):
|
||||
raise data_entry_flow.UnknownHandler(
|
||||
f"Config entry '{entry.domain}' does not support subentry '{subentry_type}'"
|
||||
)
|
||||
subentry_flow = handler.async_get_subentry_flow(entry, subentry_type)
|
||||
subentry_flow.init_step = context["source"]
|
||||
return subentry_flow
|
||||
|
||||
async def async_finish_flow(
|
||||
self,
|
||||
flow: data_entry_flow.FlowHandler[
|
||||
FlowContext, SubentryFlowResult, tuple[str, str]
|
||||
],
|
||||
result: SubentryFlowResult,
|
||||
) -> SubentryFlowResult:
|
||||
"""Finish a subentry flow and add a new subentry to the configuration entry.
|
||||
|
||||
The flow.handler[0] and entry_id is the same thing to map flow with entry.
|
||||
"""
|
||||
flow = cast(ConfigSubentryFlow, flow)
|
||||
|
||||
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
return result
|
||||
|
||||
entry_id = flow.handler[0]
|
||||
entry = self.hass.config_entries.async_get_entry(entry_id)
|
||||
if entry is None:
|
||||
raise UnknownEntry(entry_id)
|
||||
|
||||
unique_id = result.get("unique_id")
|
||||
if unique_id is not None and not isinstance(unique_id, str):
|
||||
raise HomeAssistantError("unique_id must be a string")
|
||||
|
||||
self.hass.config_entries.async_add_subentry(
|
||||
entry,
|
||||
ConfigSubentry(
|
||||
data=MappingProxyType(result["data"]),
|
||||
title=result["title"],
|
||||
unique_id=unique_id,
|
||||
),
|
||||
)
|
||||
|
||||
result["result"] = True
|
||||
return result
|
||||
|
||||
|
||||
class ConfigSubentryFlow(
|
||||
data_entry_flow.FlowHandler[FlowContext, SubentryFlowResult, tuple[str, str]]
|
||||
):
|
||||
"""Base class for config subentry flows."""
|
||||
|
||||
_flow_result = SubentryFlowResult
|
||||
handler: tuple[str, str]
|
||||
|
||||
@callback
|
||||
def async_create_entry(
|
||||
self,
|
||||
*,
|
||||
title: str | None = None,
|
||||
data: Mapping[str, Any],
|
||||
description: str | None = None,
|
||||
description_placeholders: Mapping[str, str] | None = None,
|
||||
unique_id: str | None = None,
|
||||
) -> SubentryFlowResult:
|
||||
"""Finish config flow and create a config entry."""
|
||||
result = super().async_create_entry(
|
||||
title=title,
|
||||
data=data,
|
||||
description=description,
|
||||
description_placeholders=description_placeholders,
|
||||
)
|
||||
|
||||
result["unique_id"] = unique_id
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class OptionsFlowManager(
|
||||
data_entry_flow.FlowManager[ConfigFlowContext, ConfigFlowResult],
|
||||
_ConfigSubFlowManager,
|
||||
):
|
||||
"""Manage all the config entry option flows that are in progress."""
|
||||
|
||||
_flow_result = ConfigFlowResult
|
||||
|
||||
async def async_create_flow(
|
||||
self,
|
||||
handler_key: str,
|
||||
|
@ -3343,7 +3046,7 @@ class OptionsFlowManager(
|
|||
) -> OptionsFlow:
|
||||
"""Create an options flow for a config entry.
|
||||
|
||||
The entry_id and the flow.handler is the same thing to map entry with flow.
|
||||
Entry_id and flow.handler is the same thing to map entry with flow.
|
||||
"""
|
||||
entry = self._async_get_config_entry(handler_key)
|
||||
handler = await _async_get_flow_handler(self.hass, entry.domain, {})
|
||||
|
@ -3359,7 +3062,7 @@ class OptionsFlowManager(
|
|||
This method is called when a flow step returns FlowResultType.ABORT or
|
||||
FlowResultType.CREATE_ENTRY.
|
||||
|
||||
The flow.handler and the entry_id is the same thing to map flow with entry.
|
||||
Flow.handler and entry_id is the same thing to map flow with entry.
|
||||
"""
|
||||
flow = cast(OptionsFlow, flow)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from . import config_validation as cv
|
|||
|
||||
_FlowManagerT = TypeVar(
|
||||
"_FlowManagerT",
|
||||
bound=data_entry_flow.FlowManager[Any, Any, Any],
|
||||
bound=data_entry_flow.FlowManager[Any, Any],
|
||||
default=data_entry_flow.FlowManager,
|
||||
)
|
||||
|
||||
|
@ -71,7 +71,7 @@ class FlowManagerIndexView(_BaseFlowManagerView[_FlowManagerT]):
|
|||
async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
|
||||
"""Initialize a POST request.
|
||||
|
||||
Override `post` and call `_post_impl` in subclasses which need
|
||||
Override `_post_impl` in subclasses which need
|
||||
to implement their own `RequestDataValidator`
|
||||
"""
|
||||
return await self._post_impl(request, data)
|
||||
|
|
|
@ -285,15 +285,6 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
|||
"user" if integration.integration_type == "helper" else None
|
||||
),
|
||||
),
|
||||
vol.Optional("config_subentries"): cv.schema_with_slug_keys(
|
||||
gen_data_entry_schema(
|
||||
config=config,
|
||||
integration=integration,
|
||||
flow_title=REQUIRED,
|
||||
require_step_title=False,
|
||||
),
|
||||
slug_validator=vol.Any("_", cv.slug),
|
||||
),
|
||||
vol.Optional("options"): gen_data_entry_schema(
|
||||
config=config,
|
||||
integration=integration,
|
||||
|
|
|
@ -1000,7 +1000,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|||
reason=None,
|
||||
source=config_entries.SOURCE_USER,
|
||||
state=None,
|
||||
subentries_data=None,
|
||||
title="Mock Title",
|
||||
unique_id=None,
|
||||
version=1,
|
||||
|
@ -1017,7 +1016,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|||
"options": options or {},
|
||||
"pref_disable_new_entities": pref_disable_new_entities,
|
||||
"pref_disable_polling": pref_disable_polling,
|
||||
"subentries_data": subentries_data or (),
|
||||
"title": title,
|
||||
"unique_id": unique_id,
|
||||
"version": version,
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Home',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 2,
|
||||
|
|
|
@ -47,8 +47,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 3,
|
||||
|
|
|
@ -101,8 +101,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'XXXXXXX',
|
||||
'version': 1,
|
||||
|
|
|
@ -287,8 +287,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -101,8 +101,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'installation1',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 2,
|
||||
|
|
|
@ -47,8 +47,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 3,
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Beosound Balance-11111111',
|
||||
'unique_id': '11111111',
|
||||
'version': 1,
|
||||
|
|
|
@ -48,8 +48,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 3,
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'very_unique_string',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -44,8 +44,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -71,8 +71,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
@ -137,8 +135,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -137,13 +137,11 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": True,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -157,13 +155,11 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -177,13 +173,11 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -197,13 +191,11 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -217,13 +209,11 @@ async def test_get_entries(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -581,13 +571,11 @@ async def test_create_account(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": core_ce.SOURCE_USER,
|
||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -598,7 +586,6 @@ async def test_create_account(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"description_placeholders": None,
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
"subentries": [],
|
||||
}
|
||||
|
||||
|
||||
|
@ -667,13 +654,11 @@ async def test_two_step_flow(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": core_ce.SOURCE_USER,
|
||||
"state": core_ce.ConfigEntryState.LOADED.value,
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -684,7 +669,6 @@ async def test_two_step_flow(hass: HomeAssistant, client: TestClient) -> None:
|
|||
"description_placeholders": None,
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
"subentries": [],
|
||||
}
|
||||
|
||||
|
||||
|
@ -1104,273 +1088,6 @@ async def test_options_flow_with_invalid_data(
|
|||
assert data == {"errors": {"choices": "invalid is not a valid option"}}
|
||||
|
||||
|
||||
async def test_subentry_flow(hass: HomeAssistant, client) -> None:
|
||||
"""Test we can start a subentry flow."""
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
class SubentryFlowHandler(core_ce.ConfigSubentryFlow):
|
||||
async def async_step_init(self, user_input=None):
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
schema = OrderedDict()
|
||||
schema[vol.Required("enabled")] = bool
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
description_placeholders={"enabled": "Set to true to be true"},
|
||||
)
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(cls, config_entry):
|
||||
return ("test",)
|
||||
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
MockConfigEntry(
|
||||
domain="test",
|
||||
entry_id="test1",
|
||||
source="bla",
|
||||
).add_to_hass(hass)
|
||||
entry = hass.config_entries.async_entries()[0]
|
||||
|
||||
with patch.dict(HANDLERS, {"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()
|
||||
|
||||
data.pop("flow_id")
|
||||
assert data == {
|
||||
"type": "form",
|
||||
"handler": ["test1", "test"],
|
||||
"step_id": "user",
|
||||
"data_schema": [{"name": "enabled", "required": True, "type": "boolean"}],
|
||||
"description_placeholders": {"enabled": "Set to true to be true"},
|
||||
"errors": None,
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "method"),
|
||||
[
|
||||
("/api/config/config_entries/subentries/flow", "post"),
|
||||
("/api/config/config_entries/subentries/flow/1", "get"),
|
||||
("/api/config/config_entries/subentries/flow/1", "post"),
|
||||
],
|
||||
)
|
||||
async def test_subentry_flow_unauth(
|
||||
hass: HomeAssistant, client, hass_admin_user: MockUser, endpoint: str, method: str
|
||||
) -> None:
|
||||
"""Test unauthorized on subentry flow."""
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
class SubentryFlowHandler(core_ce.ConfigSubentryFlow):
|
||||
async def async_step_init(self, user_input=None):
|
||||
schema = OrderedDict()
|
||||
schema[vol.Required("enabled")] = bool
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
description_placeholders={"enabled": "Set to true to be true"},
|
||||
)
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(cls, config_entry):
|
||||
return ("test",)
|
||||
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
MockConfigEntry(
|
||||
domain="test",
|
||||
entry_id="test1",
|
||||
source="bla",
|
||||
).add_to_hass(hass)
|
||||
entry = hass.config_entries.async_entries()[0]
|
||||
|
||||
hass_admin_user.groups = []
|
||||
|
||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||
resp = await getattr(client, method)(endpoint, json={"handler": entry.entry_id})
|
||||
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_two_step_subentry_flow(hass: HomeAssistant, client) -> None:
|
||||
"""Test we can finish a two step subentry flow."""
|
||||
mock_integration(
|
||||
hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True))
|
||||
)
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
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})
|
||||
)
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(cls, config_entry):
|
||||
return ("test",)
|
||||
|
||||
MockConfigEntry(
|
||||
domain="test",
|
||||
entry_id="test1",
|
||||
source="bla",
|
||||
).add_to_hass(hass)
|
||||
entry = hass.config_entries.async_entries()[0]
|
||||
|
||||
with patch.dict(HANDLERS, {"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["flow_id"]
|
||||
expected_data = {
|
||||
"data_schema": [{"name": "enabled", "type": "boolean"}],
|
||||
"description_placeholders": None,
|
||||
"errors": None,
|
||||
"flow_id": flow_id,
|
||||
"handler": ["test1", "test"],
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
"step_id": "finish",
|
||||
"type": "form",
|
||||
}
|
||||
assert data == expected_data
|
||||
|
||||
resp = await client.get(f"/api/config/config_entries/subentries/flow/{flow_id}")
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
assert data == expected_data
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/config/config_entries/subentries/flow/{flow_id}",
|
||||
json={"enabled": True},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
assert data == {
|
||||
"description_placeholders": None,
|
||||
"description": None,
|
||||
"flow_id": flow_id,
|
||||
"handler": ["test1", "test"],
|
||||
"title": "Mock title",
|
||||
"type": "create_entry",
|
||||
"unique_id": "test",
|
||||
}
|
||||
|
||||
|
||||
async def test_subentry_flow_with_invalid_data(hass: HomeAssistant, client) -> None:
|
||||
"""Test a subentry flow with invalid_data."""
|
||||
mock_integration(
|
||||
hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True))
|
||||
)
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
class SubentryFlowHandler(core_ce.ConfigSubentryFlow):
|
||||
async def async_step_user(self, user_input=None):
|
||||
return self.async_show_form(
|
||||
step_id="finish",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
"choices", default=["invalid", "valid"]
|
||||
): cv.multi_select({"valid": "Valid"})
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_finish(self, user_input=None):
|
||||
return self.async_create_entry(
|
||||
title="Enable disable", data=user_input
|
||||
)
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(cls, config_entry):
|
||||
return ("test",)
|
||||
|
||||
MockConfigEntry(
|
||||
domain="test",
|
||||
entry_id="test1",
|
||||
source="bla",
|
||||
).add_to_hass(hass)
|
||||
entry = hass.config_entries.async_entries()[0]
|
||||
|
||||
with patch.dict(HANDLERS, {"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": [
|
||||
{
|
||||
"default": ["invalid", "valid"],
|
||||
"name": "choices",
|
||||
"options": {"valid": "Valid"},
|
||||
"required": True,
|
||||
"type": "multi_select",
|
||||
}
|
||||
],
|
||||
"description_placeholders": None,
|
||||
"errors": None,
|
||||
"last_step": None,
|
||||
"preview": None,
|
||||
}
|
||||
|
||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||
resp = await client.post(
|
||||
f"/api/config/config_entries/subentries/flow/{flow_id}",
|
||||
json={"choices": ["valid", "invalid"]},
|
||||
)
|
||||
assert resp.status == HTTPStatus.BAD_REQUEST
|
||||
data = await resp.json()
|
||||
assert data == {"errors": {"choices": "invalid is not a valid option"}}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("freezer")
|
||||
async def test_get_single(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
|
@ -1403,13 +1120,11 @@ async def test_get_single(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "user",
|
||||
"state": "loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1765,13 +1480,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1786,13 +1499,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1807,13 +1518,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1828,13 +1537,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1849,13 +1556,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1881,13 +1586,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1912,13 +1615,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1933,13 +1634,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1964,13 +1663,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -1985,13 +1682,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2022,13 +1717,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2043,13 +1736,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2064,13 +1755,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2085,13 +1774,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla4",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2106,13 +1793,11 @@ async def test_get_matching_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": timestamp,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla5",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2215,13 +1900,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": created,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2239,13 +1922,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": created,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": "Unsupported API",
|
||||
"source": "bla2",
|
||||
"state": "setup_error",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2263,13 +1944,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": created,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2293,13 +1972,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": modified,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2324,13 +2001,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": modified,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2354,13 +2029,11 @@ async def test_subscribe_entries_ws(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": entry.modified_at.timestamp(),
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2446,13 +2119,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": created,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2470,13 +2141,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": created,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2502,13 +2171,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": modified,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2530,13 +2197,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": modified,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla3",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2562,13 +2227,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": modified,
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2592,13 +2255,11 @@ async def test_subscribe_entries_ws_filtered(
|
|||
"error_reason_translation_key": None,
|
||||
"error_reason_translation_placeholders": None,
|
||||
"modified_at": entry.modified_at.timestamp(),
|
||||
"num_subentries": 0,
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"reason": None,
|
||||
"source": "bla",
|
||||
"state": "not_loaded",
|
||||
"supported_subentries": [],
|
||||
"supports_options": False,
|
||||
"supports_reconfigure": False,
|
||||
"supports_remove_device": False,
|
||||
|
@ -2809,133 +2470,3 @@ async def test_does_not_support_reconfigure(
|
|||
response
|
||||
== '{"message":"Handler ConfigEntriesFlowManager doesn\'t support step reconfigure"}'
|
||||
)
|
||||
|
||||
|
||||
async def test_list_subentries(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test that we can list subentries."""
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain="test",
|
||||
state=core_ce.ConfigEntryState.LOADED,
|
||||
subentries_data=[
|
||||
core_ce.ConfigSubentryData(
|
||||
data={"test": "test"},
|
||||
subentry_id="mock_id",
|
||||
title="Mock title",
|
||||
unique_id="test",
|
||||
)
|
||||
],
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert entry.pref_disable_new_entities is False
|
||||
assert entry.pref_disable_polling is False
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/list",
|
||||
"entry_id": entry.entry_id,
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"] == [
|
||||
{"subentry_id": "mock_id", "title": "Mock title", "unique_id": "test"},
|
||||
]
|
||||
|
||||
# Try listing subentries for an unknown entry
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/list",
|
||||
"entry_id": "no_such_entry",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert not response["success"]
|
||||
assert response["error"] == {
|
||||
"code": "not_found",
|
||||
"message": "Config entry not found",
|
||||
}
|
||||
|
||||
|
||||
async def test_delete_subentry(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test that we can delete a subentry."""
|
||||
assert await async_setup_component(hass, "config", {})
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain="test",
|
||||
state=core_ce.ConfigEntryState.LOADED,
|
||||
subentries_data=[
|
||||
core_ce.ConfigSubentryData(
|
||||
data={"test": "test"}, subentry_id="mock_id", title="Mock title"
|
||||
)
|
||||
],
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert entry.pref_disable_new_entities is False
|
||||
assert entry.pref_disable_polling is False
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/delete",
|
||||
"entry_id": entry.entry_id,
|
||||
"subentry_id": "mock_id",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"] is None
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/list",
|
||||
"entry_id": entry.entry_id,
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"] == []
|
||||
|
||||
# Try deleting the subentry again
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/delete",
|
||||
"entry_id": entry.entry_id,
|
||||
"subentry_id": "mock_id",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert not response["success"]
|
||||
assert response["error"] == {
|
||||
"code": "not_found",
|
||||
"message": "Config subentry not found",
|
||||
}
|
||||
|
||||
# Try deleting subentry from an unknown entry
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "config_entries/subentries/delete",
|
||||
"entry_id": "no_such_entry",
|
||||
"subentry_id": "mock_id",
|
||||
}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
|
||||
assert not response["success"]
|
||||
assert response["error"] == {
|
||||
"code": "not_found",
|
||||
"message": "Config entry not found",
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -47,8 +47,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '123456',
|
||||
'version': 1,
|
||||
|
|
|
@ -32,8 +32,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '1234567890',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'dsmr_reader',
|
||||
'unique_id': 'UNIQUE_TEST_ID',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
@ -72,8 +70,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -28,14 +28,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'EnergyZero',
|
||||
'unique_id': 'energyzero',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'EnergyZero',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
@ -456,8 +454,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
@ -932,8 +928,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'ESPHome Device',
|
||||
'unique_id': '11:22:33:44:55:aa',
|
||||
'version': 1,
|
||||
|
|
|
@ -79,7 +79,6 @@ async def test_diagnostics_with_bluetooth(
|
|||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Mock Title",
|
||||
"unique_id": "11:22:33:44:55:aa",
|
||||
"version": 1,
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Green House',
|
||||
'unique_id': 'unique',
|
||||
'version': 2,
|
||||
|
|
|
@ -61,8 +61,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'fyta_user',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -66,14 +66,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'bluetooth',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Gardena Water Computer',
|
||||
'unique_id': '00000000-0000-0000-0000-000000000001',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Gardena Water Computer',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -227,14 +223,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Gardena Water Computer',
|
||||
'unique_id': '00000000-0000-0000-0000-000000000001',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Gardena Water Computer',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Home',
|
||||
'unique_id': '123',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'import',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '1234',
|
||||
'unique_id': '1234',
|
||||
'version': 1,
|
||||
|
|
|
@ -42,7 +42,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"data": {
|
||||
"valve_controller": {
|
||||
|
|
|
@ -30,14 +30,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'P1 meter',
|
||||
'unique_id': 'HWE-P1_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'P1 meter',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -78,14 +74,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'P1 meter',
|
||||
'unique_id': 'HWE-P1_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'P1 meter',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -126,14 +118,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Socket',
|
||||
'unique_id': 'HWE-SKT_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Energy Socket',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -170,14 +158,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'P1 meter',
|
||||
'unique_id': 'HWE-P1_5c2fafabcdef',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'P1 meter',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -183,8 +183,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Husqvarna Automower of Erika Mustermann',
|
||||
'unique_id': '123',
|
||||
'version': 1,
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'River Name (Station Name)',
|
||||
'unique_id': '123',
|
||||
'version': 1,
|
||||
|
|
|
@ -358,8 +358,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -57,7 +57,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"client": {
|
||||
"version": "api_version='0.2.0' hostname='scb' name='PUCK RESTful API' sw_version='01.16.05025'",
|
||||
|
|
|
@ -25,8 +25,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -73,8 +73,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'test-site-name',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'envy',
|
||||
'unique_id': '00:11:22:33:44:55',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'melcloud',
|
||||
'unique_id': 'UNIQUE_TEST_ID',
|
||||
'version': 1,
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'AA:BB:CC:DD:EE:FF',
|
||||
'version': 1,
|
||||
|
|
|
@ -28,8 +28,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -646,8 +646,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'netatmo',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Fake Profile',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -60,8 +60,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -37,7 +37,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"data": {
|
||||
"bridges": [
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'version': 1,
|
||||
|
|
|
@ -39,7 +39,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"data": {
|
||||
"protection_window": {
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'unique_thingy',
|
||||
'version': 2,
|
||||
|
@ -40,8 +38,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'unique_thingy',
|
||||
'version': 2,
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '70272185-xxxx-xxxx-xxxx-43bea330dcae',
|
||||
'version': 1,
|
||||
|
|
|
@ -94,8 +94,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -155,7 +155,6 @@ async def test_pairing(hass: HomeAssistant, mock_tv_pairable, mock_setup_entry)
|
|||
"version": 1,
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
"subentries": (),
|
||||
}
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -102,8 +102,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'home',
|
||||
'unique_id': 'proximity_home',
|
||||
'version': 1,
|
||||
|
|
|
@ -52,7 +52,6 @@ MOCK_FLOW_RESULT = {
|
|||
"title": "test_ps4",
|
||||
"data": MOCK_DATA,
|
||||
"options": {},
|
||||
"subentries": (),
|
||||
}
|
||||
|
||||
MOCK_ENTRY_ID = "SomeID"
|
||||
|
|
|
@ -38,7 +38,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"data": {
|
||||
"fields": [
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
@ -86,8 +84,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -1144,8 +1144,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 2,
|
||||
|
@ -2277,8 +2275,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 2,
|
||||
|
|
|
@ -34,7 +34,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
|
|
|
@ -44,8 +44,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 2,
|
||||
|
|
|
@ -51,7 +51,6 @@ async def test_entry_diagnostics(
|
|||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Mock Title",
|
||||
"unique_id": "any",
|
||||
"version": 2,
|
||||
|
@ -92,7 +91,6 @@ async def test_entry_diagnostics_encrypted(
|
|||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Mock Title",
|
||||
"unique_id": "any",
|
||||
"version": 2,
|
||||
|
@ -132,7 +130,6 @@ async def test_entry_diagnostics_encrypte_offline(
|
|||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Mock Title",
|
||||
"unique_id": "any",
|
||||
"version": 2,
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Pentair: DD-EE-FF',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'version': 1,
|
||||
|
|
|
@ -32,7 +32,6 @@ async def test_entry_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
"subscription_data": {
|
||||
"12345": {
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'solarlog',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -136,7 +136,6 @@ async def test_user_form_pin_not_required(
|
|||
"data": deepcopy(TEST_CONFIG),
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
"subentries": (),
|
||||
}
|
||||
|
||||
expected["data"][CONF_PIN] = None
|
||||
|
@ -342,7 +341,6 @@ async def test_pin_form_success(hass: HomeAssistant, pin_form) -> None:
|
|||
"data": TEST_CONFIG,
|
||||
"options": {},
|
||||
"minor_version": 1,
|
||||
"subentries": (),
|
||||
}
|
||||
result["data"][CONF_DEVICE_ID] = TEST_DEVICE_ID
|
||||
assert result == expected
|
||||
|
|
|
@ -69,6 +69,5 @@ async def test_diagnostics(
|
|||
"created_at": ANY,
|
||||
"modified_at": ANY,
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -56,8 +56,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'System Monitor',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
@ -113,8 +111,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'System Monitor',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -37,8 +37,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': 'very_unique_string',
|
||||
'version': 1,
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '12345',
|
||||
'unique_id': '12345',
|
||||
'version': 1,
|
||||
|
@ -56,8 +54,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Old Tuya configuration entry',
|
||||
'unique_id': '12345',
|
||||
'version': 1,
|
||||
|
@ -111,14 +107,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'mocked_username',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'mocked_username',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -37,8 +37,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Twinkly',
|
||||
'unique_id': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'version': 1,
|
||||
|
|
|
@ -42,8 +42,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': '1',
|
||||
'version': 1,
|
||||
|
|
|
@ -27,14 +27,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Uptime',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Uptime',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -25,8 +25,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Energy Bill',
|
||||
'unique_id': None,
|
||||
'version': 2,
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': 'ABC123',
|
||||
'version': 1,
|
||||
|
|
|
@ -4731,8 +4731,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': 'ViCare',
|
||||
'version': 1,
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': '**REDACTED**',
|
||||
'version': 1,
|
||||
|
|
|
@ -253,8 +253,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': '**REDACTED**',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -61,6 +61,5 @@ async def test_diagnostics(
|
|||
"created_at": entry.created_at.isoformat(),
|
||||
"modified_at": entry.modified_at.isoformat(),
|
||||
"discovery_keys": {},
|
||||
"subentries": [],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -30,14 +30,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Example.com',
|
||||
'unique_id': 'example.com',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Example.com',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -74,14 +70,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Example.com',
|
||||
'unique_id': 'example.com',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Example.com',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -118,14 +110,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Example.com',
|
||||
'unique_id': 'example.com',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Example.com',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -162,14 +150,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Example.com',
|
||||
'unique_id': 'example.com',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Example.com',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -206,14 +190,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Example.com',
|
||||
'unique_id': 'example.com',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Example.com',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -40,8 +40,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -36,14 +36,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'hassio',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Piper',
|
||||
'unique_id': '1234',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Piper',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -86,14 +82,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'hassio',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Piper',
|
||||
'unique_id': '1234',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Piper',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
@ -135,14 +127,10 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'zeroconf',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Test Satellite',
|
||||
'unique_id': 'test_zeroconf_name._wyoming._tcp.local._Test Satellite',
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': tuple(
|
||||
),
|
||||
'title': 'Test Satellite',
|
||||
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
|
||||
'version': 1,
|
||||
|
|
|
@ -113,8 +113,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 4,
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
from contextlib import AbstractContextManager, nullcontext as does_not_raise
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import re
|
||||
|
@ -906,7 +905,7 @@ async def test_entries_excludes_ignore_and_disabled(
|
|||
|
||||
|
||||
async def test_saving_and_loading(
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory, hass_storage: dict[str, Any]
|
||||
hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
||||
) -> None:
|
||||
"""Test that we're saving and loading correctly."""
|
||||
mock_integration(
|
||||
|
@ -923,17 +922,7 @@ async def test_saving_and_loading(
|
|||
async def async_step_user(self, user_input=None):
|
||||
"""Test user step."""
|
||||
await self.async_set_unique_id("unique")
|
||||
subentries = [
|
||||
config_entries.ConfigSubentryData(
|
||||
data={"foo": "bar"}, title="subentry 1"
|
||||
),
|
||||
config_entries.ConfigSubentryData(
|
||||
data={"sun": "moon"}, title="subentry 2", unique_id="very_unique"
|
||||
),
|
||||
]
|
||||
return self.async_create_entry(
|
||||
title="Test Title", data={"token": "abcd"}, subentries=subentries
|
||||
)
|
||||
return self.async_create_entry(title="Test Title", data={"token": "abcd"})
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
await hass.config_entries.flow.async_init(
|
||||
|
@ -982,98 +971,6 @@ async def test_saving_and_loading(
|
|||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
|
||||
stored_data = hass_storage["core.config_entries"]
|
||||
assert stored_data == {
|
||||
"data": {
|
||||
"entries": [
|
||||
{
|
||||
"created_at": ANY,
|
||||
"data": {
|
||||
"token": "abcd",
|
||||
},
|
||||
"disabled_by": None,
|
||||
"discovery_keys": {},
|
||||
"domain": "test",
|
||||
"entry_id": ANY,
|
||||
"minor_version": 1,
|
||||
"modified_at": ANY,
|
||||
"options": {},
|
||||
"pref_disable_new_entities": True,
|
||||
"pref_disable_polling": True,
|
||||
"source": "user",
|
||||
"subentries": [
|
||||
{
|
||||
"data": {"foo": "bar"},
|
||||
"subentry_id": ANY,
|
||||
"title": "subentry 1",
|
||||
"unique_id": None,
|
||||
},
|
||||
{
|
||||
"data": {"sun": "moon"},
|
||||
"subentry_id": ANY,
|
||||
"title": "subentry 2",
|
||||
"unique_id": "very_unique",
|
||||
},
|
||||
],
|
||||
"title": "Test Title",
|
||||
"unique_id": "unique",
|
||||
"version": 5,
|
||||
},
|
||||
{
|
||||
"created_at": ANY,
|
||||
"data": {
|
||||
"username": "bla",
|
||||
},
|
||||
"disabled_by": None,
|
||||
"discovery_keys": {
|
||||
"test": [
|
||||
{"domain": "test", "key": "blah", "version": 1},
|
||||
],
|
||||
},
|
||||
"domain": "test",
|
||||
"entry_id": ANY,
|
||||
"minor_version": 1,
|
||||
"modified_at": ANY,
|
||||
"options": {},
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Test 2 Title",
|
||||
"unique_id": None,
|
||||
"version": 3,
|
||||
},
|
||||
{
|
||||
"created_at": ANY,
|
||||
"data": {
|
||||
"username": "bla",
|
||||
},
|
||||
"disabled_by": None,
|
||||
"discovery_keys": {
|
||||
"test": [
|
||||
{"domain": "test", "key": ["a", "b"], "version": 1},
|
||||
],
|
||||
},
|
||||
"domain": "test",
|
||||
"entry_id": ANY,
|
||||
"minor_version": 1,
|
||||
"modified_at": ANY,
|
||||
"options": {},
|
||||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "user",
|
||||
"subentries": [],
|
||||
"title": "Test 2 Title",
|
||||
"unique_id": None,
|
||||
"version": 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
"key": "core.config_entries",
|
||||
"minor_version": 5,
|
||||
"version": 1,
|
||||
}
|
||||
|
||||
# Now load written data in new config manager
|
||||
manager = config_entries.ConfigEntries(hass, {})
|
||||
await manager.async_initialize()
|
||||
|
@ -1086,25 +983,6 @@ async def test_saving_and_loading(
|
|||
):
|
||||
assert orig.as_dict() == loaded.as_dict()
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry_1,
|
||||
pref_disable_polling=False,
|
||||
pref_disable_new_entities=False,
|
||||
)
|
||||
|
||||
# To trigger the call_later
|
||||
freezer.tick(1.0)
|
||||
async_fire_time_changed(hass)
|
||||
# To execute the save
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Assert no data is lost when storing again
|
||||
expected_stored_data = stored_data
|
||||
expected_stored_data["data"]["entries"][0]["modified_at"] = ANY
|
||||
expected_stored_data["data"]["entries"][0]["pref_disable_new_entities"] = False
|
||||
expected_stored_data["data"]["entries"][0]["pref_disable_polling"] = False
|
||||
assert hass_storage["core.config_entries"] == expected_stored_data | {}
|
||||
|
||||
|
||||
@freeze_time("2024-02-14 12:00:00")
|
||||
async def test_as_dict(snapshot: SnapshotAssertion) -> None:
|
||||
|
@ -1538,42 +1416,6 @@ async def test_update_entry_options_and_trigger_listener(
|
|||
assert len(update_listener_calls) == 1
|
||||
|
||||
|
||||
async def test_update_subentry_and_trigger_listener(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test that we can update subentry and trigger listener."""
|
||||
entry = MockConfigEntry(domain="test", options={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
update_listener_calls = []
|
||||
|
||||
subentry = config_entries.ConfigSubentry(
|
||||
data={"test": "test"}, unique_id="test", title="Mock title"
|
||||
)
|
||||
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
||||
) -> None:
|
||||
"""Test function."""
|
||||
assert entry.subentries == expected_subentries
|
||||
update_listener_calls.append(None)
|
||||
|
||||
entry.add_update_listener(update_listener)
|
||||
|
||||
expected_subentries = {subentry.subentry_id: subentry}
|
||||
assert manager.async_add_subentry(entry, subentry) is True
|
||||
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert entry.subentries == expected_subentries
|
||||
assert len(update_listener_calls) == 1
|
||||
|
||||
expected_subentries = {}
|
||||
assert manager.async_remove_subentry(entry, subentry.subentry_id) is True
|
||||
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert entry.subentries == expected_subentries
|
||||
assert len(update_listener_calls) == 2
|
||||
|
||||
|
||||
async def test_setup_raise_not_ready(
|
||||
hass: HomeAssistant,
|
||||
manager: config_entries.ConfigEntries,
|
||||
|
@ -1900,456 +1742,20 @@ async def test_entry_options_unknown_config_entry(
|
|||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
class TestFlow:
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Test options flow."""
|
||||
|
||||
with pytest.raises(config_entries.UnknownEntry):
|
||||
await manager.options.async_create_flow(
|
||||
"blah", context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
|
||||
async def test_create_entry_subentries(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test a config entry being created with subentries."""
|
||||
|
||||
subentrydata = config_entries.ConfigSubentryData(
|
||||
data={"test": "test"},
|
||||
title="Mock title",
|
||||
unique_id="test",
|
||||
)
|
||||
|
||||
async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Mock setup."""
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
"comp",
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={"data": "data", "subentry": subentrydata},
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
async_setup_entry = AsyncMock(return_value=True)
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
"comp", async_setup=mock_async_setup, async_setup_entry=async_setup_entry
|
||||
),
|
||||
)
|
||||
mock_platform(hass, "comp.config_flow", None)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_import(self, user_input):
|
||||
"""Test import step creating entry, with subentry."""
|
||||
return self.async_create_entry(
|
||||
title="title",
|
||||
data={"example": user_input["data"]},
|
||||
subentries=[user_input["subentry"]],
|
||||
)
|
||||
|
||||
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
||||
assert await async_setup_component(hass, "comp", {})
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(async_setup_entry.mock_calls) == 1
|
||||
|
||||
entries = hass.config_entries.async_entries("comp")
|
||||
assert len(entries) == 1
|
||||
assert entries[0].supported_subentries == ()
|
||||
assert entries[0].data == {"example": "data"}
|
||||
assert len(entries[0].subentries) == 1
|
||||
subentry_id = list(entries[0].subentries)[0]
|
||||
subentry = config_entries.ConfigSubentry(
|
||||
data=subentrydata["data"],
|
||||
subentry_id=subentry_id,
|
||||
title=subentrydata["title"],
|
||||
unique_id="test",
|
||||
)
|
||||
assert entries[0].subentries == {subentry_id: subentry}
|
||||
|
||||
|
||||
async def test_entry_subentry(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test that we can add a subentry to an entry."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
flow = await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
flow.handler = (entry.entry_id, "test") # Set to keep reference to config entry
|
||||
|
||||
await manager.subentries.async_finish_flow(
|
||||
flow,
|
||||
{
|
||||
"data": {"second": True},
|
||||
"title": "Mock title",
|
||||
"type": data_entry_flow.FlowResultType.CREATE_ENTRY,
|
||||
"unique_id": "test",
|
||||
},
|
||||
)
|
||||
|
||||
assert entry.data == {"first": True}
|
||||
assert entry.options == {}
|
||||
subentry_id = list(entry.subentries)[0]
|
||||
assert entry.subentries == {
|
||||
subentry_id: config_entries.ConfigSubentry(
|
||||
data={"second": True},
|
||||
subentry_id=subentry_id,
|
||||
title="Mock title",
|
||||
unique_id="test",
|
||||
)
|
||||
}
|
||||
assert entry.supported_subentries == ("test",)
|
||||
|
||||
|
||||
async def test_entry_subentry_non_string(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test adding an invalid subentry to an entry."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
flow = await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
flow.handler = (entry.entry_id, "test") # Set to keep reference to config entry
|
||||
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await manager.subentries.async_finish_flow(
|
||||
flow,
|
||||
{
|
||||
"data": {"second": True},
|
||||
"title": "Mock title",
|
||||
"type": data_entry_flow.FlowResultType.CREATE_ENTRY,
|
||||
"unique_id": 123,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("context", [None, {}, {"bla": "bleh"}])
|
||||
async def test_entry_subentry_no_context(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries, context: dict | None
|
||||
) -> None:
|
||||
"""Test starting a subentry flow without "source" in context."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow), pytest.raises(KeyError):
|
||||
await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context=context, data=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("unique_id", "expected_result"),
|
||||
[(None, does_not_raise()), ("test", pytest.raises(HomeAssistantError))],
|
||||
)
|
||||
async def test_entry_subentry_duplicate(
|
||||
hass: HomeAssistant,
|
||||
manager: config_entries.ConfigEntries,
|
||||
unique_id: str | None,
|
||||
expected_result: AbstractContextManager,
|
||||
) -> None:
|
||||
"""Test adding a duplicated subentry to an entry."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(
|
||||
domain="test",
|
||||
data={"first": True},
|
||||
subentries_data=[
|
||||
config_entries.ConfigSubentryData(
|
||||
data={},
|
||||
subentry_id="blabla",
|
||||
title="Mock title",
|
||||
unique_id=unique_id,
|
||||
)
|
||||
],
|
||||
)
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
flow = await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
flow.handler = (entry.entry_id, "test") # Set to keep reference to config entry
|
||||
|
||||
with expected_result:
|
||||
await manager.subentries.async_finish_flow(
|
||||
flow,
|
||||
{
|
||||
"data": {"second": True},
|
||||
"title": "Mock title",
|
||||
"type": data_entry_flow.FlowResultType.CREATE_ENTRY,
|
||||
"unique_id": unique_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_subentry_abort(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test that we can abort subentry flow."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
flow = await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
flow.handler = (entry.entry_id, "test") # Set to keep reference to config entry
|
||||
|
||||
assert await manager.subentries.async_finish_flow(
|
||||
flow, {"type": data_entry_flow.FlowResultType.ABORT, "reason": "test"}
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_subentry_unknown_config_entry(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test attempting to start a subentry flow for an unknown config entry."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
|
||||
with pytest.raises(config_entries.UnknownEntry):
|
||||
await manager.subentries.async_create_flow(
|
||||
("blah", "blah"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_subentry_deleted_config_entry(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test attempting to finish a subentry flow for a deleted config entry."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with mock_config_flow("test", TestFlow):
|
||||
flow = await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
flow.handler = (entry.entry_id, "test") # Set to keep reference to config entry
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
|
||||
with pytest.raises(config_entries.UnknownEntry):
|
||||
await manager.subentries.async_finish_flow(
|
||||
flow,
|
||||
{
|
||||
"data": {"second": True},
|
||||
"title": "Mock title",
|
||||
"type": data_entry_flow.FlowResultType.CREATE_ENTRY,
|
||||
"unique_id": "test",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_subentry_unsupported(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test attempting to start a subentry flow for a config entry without support."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_subentry_flow(config_entry, subentry_type: str):
|
||||
"""Test subentry flow."""
|
||||
|
||||
class SubentryFlowHandler(data_entry_flow.FlowHandler):
|
||||
"""Test subentry flow handler."""
|
||||
|
||||
return SubentryFlowHandler()
|
||||
|
||||
@classmethod
|
||||
@callback
|
||||
def async_supported_subentries(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> tuple[str, ...]:
|
||||
return ("test",)
|
||||
|
||||
with (
|
||||
mock_config_flow("test", TestFlow),
|
||||
pytest.raises(data_entry_flow.UnknownHandler),
|
||||
):
|
||||
await manager.subentries.async_create_flow(
|
||||
(
|
||||
entry.entry_id,
|
||||
"unknown",
|
||||
),
|
||||
context={"source": "test"},
|
||||
data=None,
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_subentry_unsupported_subentry_type(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
"""Test attempting to start a subentry flow for a config entry without support."""
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.config_flow", None)
|
||||
entry = MockConfigEntry(domain="test", data={"first": True})
|
||||
entry.add_to_manager(manager)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
with (
|
||||
mock_config_flow("test", TestFlow),
|
||||
pytest.raises(data_entry_flow.UnknownHandler),
|
||||
):
|
||||
await manager.subentries.async_create_flow(
|
||||
(entry.entry_id, "test"), context={"source": "test"}, data=None
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_setup_succeed(
|
||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||
) -> None:
|
||||
|
@ -4505,20 +3911,21 @@ async def test_updating_entry_with_and_without_changes(
|
|||
|
||||
assert manager.async_update_entry(entry) is False
|
||||
|
||||
for change, expected_value in (
|
||||
({"data": {"second": True, "third": 456}}, {"second": True, "third": 456}),
|
||||
({"data": {"second": True}}, {"second": True}),
|
||||
({"minor_version": 2}, 2),
|
||||
({"options": {"hello": True}}, {"hello": True}),
|
||||
({"pref_disable_new_entities": True}, True),
|
||||
({"pref_disable_polling": True}, True),
|
||||
({"title": "sometitle"}, "sometitle"),
|
||||
({"unique_id": "abcd1234"}, "abcd1234"),
|
||||
({"version": 2}, 2),
|
||||
for change in (
|
||||
{"data": {"second": True, "third": 456}},
|
||||
{"data": {"second": True}},
|
||||
{"minor_version": 2},
|
||||
{"options": {"hello": True}},
|
||||
{"pref_disable_new_entities": True},
|
||||
{"pref_disable_polling": True},
|
||||
{"title": "sometitle"},
|
||||
{"unique_id": "abcd1234"},
|
||||
{"version": 2},
|
||||
):
|
||||
assert manager.async_update_entry(entry, **change) is True
|
||||
key = next(iter(change))
|
||||
assert getattr(entry, key) == expected_value
|
||||
value = next(iter(change.values()))
|
||||
assert getattr(entry, key) == value
|
||||
assert manager.async_update_entry(entry, **change) is False
|
||||
|
||||
assert manager.async_entry_for_domain_unique_id("test", "abc123") is None
|
||||
|
@ -6052,7 +5459,6 @@ async def test_unhashable_unique_id_fails(
|
|||
minor_version=1,
|
||||
options={},
|
||||
source="test",
|
||||
subentries_data=(),
|
||||
title="title",
|
||||
unique_id=unique_id,
|
||||
version=1,
|
||||
|
@ -6088,7 +5494,6 @@ async def test_unhashable_unique_id_fails_on_update(
|
|||
minor_version=1,
|
||||
options={},
|
||||
source="test",
|
||||
subentries_data=(),
|
||||
title="title",
|
||||
unique_id="123",
|
||||
version=1,
|
||||
|
@ -6119,7 +5524,6 @@ async def test_string_unique_id_no_warning(
|
|||
minor_version=1,
|
||||
options={},
|
||||
source="test",
|
||||
subentries_data=(),
|
||||
title="title",
|
||||
unique_id="123",
|
||||
version=1,
|
||||
|
@ -6162,7 +5566,6 @@ async def test_hashable_unique_id(
|
|||
minor_version=1,
|
||||
options={},
|
||||
source="test",
|
||||
subentries_data=(),
|
||||
title="title",
|
||||
unique_id=unique_id,
|
||||
version=1,
|
||||
|
@ -6197,7 +5600,6 @@ async def test_no_unique_id_no_warning(
|
|||
minor_version=1,
|
||||
options={},
|
||||
source="test",
|
||||
subentries_data=(),
|
||||
title="title",
|
||||
unique_id=None,
|
||||
version=1,
|
||||
|
@ -7122,7 +6524,6 @@ async def test_migration_from_1_2(
|
|||
"pref_disable_new_entities": False,
|
||||
"pref_disable_polling": False,
|
||||
"source": "import",
|
||||
"subentries": {},
|
||||
"title": "Sun",
|
||||
"unique_id": None,
|
||||
"version": 1,
|
||||
|
|
Loading…
Reference in New Issue