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
Erik Montnemery 2024-12-18 13:51:05 +01:00 committed by GitHub
parent 2aba1d399b
commit ecb3bf79f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 33 additions and 1774 deletions

View File

@ -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"])

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -19,8 +19,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': 'Home',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -35,8 +35,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 2,

View File

@ -47,8 +47,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 3,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -17,8 +17,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 2,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -44,8 +44,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': None,
'version': 1,

View File

@ -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,

View File

@ -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",
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -17,8 +17,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': 'Home',
'unique_id': '123',
'version': 1,

View File

@ -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,

View File

@ -15,8 +15,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'import',
'subentries': list([
]),
'title': '1234',
'unique_id': '1234',
'version': 1,

View File

@ -42,7 +42,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"data": {
"valve_controller": {

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -358,8 +358,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -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'",

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -28,8 +28,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -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,

View File

@ -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,

View File

@ -60,8 +60,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -37,7 +37,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"data": {
"bridges": [

View File

@ -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,

View File

@ -39,7 +39,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"data": {
"protection_window": {

View File

@ -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,

View File

@ -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,

View File

@ -94,8 +94,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -52,7 +52,6 @@ MOCK_FLOW_RESULT = {
"title": "test_ps4",
"data": MOCK_DATA,
"options": {},
"subentries": (),
}
MOCK_ENTRY_ID = "SomeID"

View File

@ -38,7 +38,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"data": {
"fields": [

View File

@ -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,

View File

@ -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,

View File

@ -34,7 +34,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"data": [
{

View File

@ -44,8 +44,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 2,

View File

@ -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,

View File

@ -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,

View File

@ -32,7 +32,6 @@ async def test_entry_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
"subscription_data": {
"12345": {

View File

@ -18,8 +18,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': 'solarlog',
'unique_id': None,
'version': 1,

View File

@ -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

View File

@ -69,6 +69,5 @@ async def test_diagnostics(
"created_at": ANY,
"modified_at": ANY,
"discovery_keys": {},
"subentries": [],
},
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -16,8 +16,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': 'ABC123',
'version': 1,

View File

@ -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,

View File

@ -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,

View File

@ -27,8 +27,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': '**REDACTED**',
'version': 1,

View File

@ -253,8 +253,6 @@
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': '**REDACTED**',
'unique_id': None,
'version': 1,

View File

@ -61,6 +61,5 @@ async def test_diagnostics(
"created_at": entry.created_at.isoformat(),
"modified_at": entry.modified_at.isoformat(),
"discovery_keys": {},
"subentries": [],
},
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,