1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Revert "Add support for subentries to config entries" (#133470)

Revert "Add support for subentries to config entries (#117355)"

This reverts commit ad15786115.
This commit is contained in:
Erik Montnemery
2024-12-18 13:51:05 +01:00
committed by GitHub
parent 2aba1d399b
commit ecb3bf79f3
95 changed files with 33 additions and 1774 deletions

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,