mirror of
https://github.com/home-assistant/core.git
synced 2026-09-24 07:25:52 -05:00
sandbox: carry version/minor_version/options on create_entry (Phase 1)
The proxy CREATE_ENTRY path read only data/title/description and called
async_create_entry, which stamps the proxy class defaults VERSION=1 /
MINOR_VERSION=1 / options={}. A sandboxed flow with VERSION>1 lost its
schema version (spurious migration on next setup) and dropped options.
async_create_entry reads self.VERSION/self.MINOR_VERSION off the *instance*
(config_entries.py async_create_entry), so override the proxy instance's
values from the wire result before the call, and plumb options= through.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0e27fb4c8d
commit
aba0a19474
@@ -193,7 +193,21 @@ class SandboxFlowProxy(ConfigFlow):
|
||||
|
||||
if result_type is FlowResultType.CREATE_ENTRY:
|
||||
entry_data = struct_to_dict(result.data)
|
||||
options = (
|
||||
struct_to_dict(result.options) if result.HasField("options") else None
|
||||
)
|
||||
self._terminated = True
|
||||
# ``async_create_entry`` stamps the created result's
|
||||
# ``version``/``minor_version`` from ``self.VERSION``/
|
||||
# ``self.MINOR_VERSION`` (read off the instance, not the class —
|
||||
# see ``ConfigFlow.async_create_entry``). Override the proxy
|
||||
# instance's values with the sandbox flow's so the entry carries
|
||||
# the integration's real schema version; otherwise the proxy's
|
||||
# default ``VERSION=1`` triggers a spurious migration on next setup.
|
||||
if result.HasField("version"):
|
||||
self.VERSION = result.version
|
||||
if result.HasField("minor_version"):
|
||||
self.MINOR_VERSION = result.minor_version
|
||||
create_result = self.async_create_entry(
|
||||
title=(
|
||||
result.title
|
||||
@@ -205,6 +219,7 @@ class SandboxFlowProxy(ConfigFlow):
|
||||
result.description if result.HasField("description") else None
|
||||
),
|
||||
description_placeholders=placeholders,
|
||||
options=options,
|
||||
)
|
||||
# Tag the FlowResult so the framework's entry constructor in
|
||||
# ``ConfigEntriesFlowManager.async_finish_flow`` reads it into
|
||||
|
||||
@@ -78,6 +78,22 @@ class _DemoFlow(ConfigFlow, domain="phase4_demo"):
|
||||
)
|
||||
|
||||
|
||||
class _VersionedFlow(ConfigFlow, domain="phase4_versioned"):
|
||||
"""A one-step flow with a non-default VERSION/MINOR_VERSION and options."""
|
||||
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 3
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
return self.async_create_entry(
|
||||
title="Versioned",
|
||||
data={"host": "1.2.3.4"},
|
||||
options={"poll_interval": 30},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="channels")
|
||||
async def _channels_fixture() -> tuple[Channel, Channel]:
|
||||
main, sandbox = _make_channel_pair()
|
||||
@@ -162,6 +178,40 @@ async def test_flow_step_creates_entry(
|
||||
assert struct_to_dict(step_result.data) == {"host": "1.2.3.4"}
|
||||
|
||||
|
||||
async def test_create_entry_marshals_version_and_options(
|
||||
channels: tuple[Channel, Channel], runner: FlowRunner
|
||||
) -> None:
|
||||
"""CREATE_ENTRY carries the flow's VERSION/MINOR_VERSION/options on the wire."""
|
||||
main, sandbox = channels
|
||||
runner.register(sandbox)
|
||||
main.start()
|
||||
sandbox.start()
|
||||
|
||||
ha_config_entries.HANDLERS["phase4_versioned"] = _VersionedFlow
|
||||
fake_module = ModuleType("homeassistant.components.phase4_versioned")
|
||||
fake_flow_module = ModuleType("homeassistant.components.phase4_versioned.config_flow")
|
||||
runner.hass.data[ha_loader.DATA_COMPONENTS]["phase4_versioned"] = fake_module
|
||||
runner.hass.data[ha_loader.DATA_COMPONENTS]["phase4_versioned.config_flow"] = (
|
||||
fake_flow_module
|
||||
)
|
||||
runner.hass.config.components.add("phase4_versioned")
|
||||
try:
|
||||
init_msg = pb.FlowInit(handler="phase4_versioned")
|
||||
init_msg.context.update({"source": "user"})
|
||||
result = await main.call("sandbox/flow_init", init_msg)
|
||||
finally:
|
||||
ha_config_entries.HANDLERS.pop("phase4_versioned", None)
|
||||
runner.hass.data[ha_loader.DATA_COMPONENTS].pop("phase4_versioned", None)
|
||||
runner.hass.data[ha_loader.DATA_COMPONENTS].pop(
|
||||
"phase4_versioned.config_flow", None
|
||||
)
|
||||
|
||||
assert result.type == "create_entry"
|
||||
assert result.version == 2
|
||||
assert result.minor_version == 3
|
||||
assert struct_to_dict(result.options) == {"poll_interval": 30}
|
||||
|
||||
|
||||
async def test_flow_step_validation_error_returns_form(
|
||||
channels: tuple[Channel, Channel], runner: FlowRunner
|
||||
) -> None:
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.components.sandbox._proto import sandbox_pb2 as pb
|
||||
from homeassistant.components.sandbox.channel import Channel
|
||||
from homeassistant.components.sandbox.manager import SandboxManager
|
||||
from homeassistant.components.sandbox.messages import struct_to_dict
|
||||
from homeassistant.components.sandbox.proxy_flow import SandboxFlowProxy
|
||||
from homeassistant.components.sandbox.router import SandboxFlowRouter
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -166,6 +167,55 @@ async def test_full_flow_user_to_create_entry(
|
||||
assert entries[0].state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_create_entry_carries_version_and_options(
|
||||
hass: HomeAssistant, manager: FakeSandboxManager
|
||||
) -> None:
|
||||
"""A sandbox CREATE_ENTRY with VERSION/MINOR_VERSION/options round-trips.
|
||||
|
||||
Pins the framework behaviour the fix relies on: ``async_create_entry``
|
||||
reads ``self.VERSION``/``self.MINOR_VERSION`` off the proxy *instance*,
|
||||
so overriding them before the call lands the integration's real schema
|
||||
version on the entry (instead of the proxy class default ``VERSION=1``,
|
||||
which would trigger a spurious migration on next setup).
|
||||
"""
|
||||
mock_integration(hass, MockModule("test_proxy_full"))
|
||||
create_entry = pb.FlowResult(
|
||||
type=FlowResultType.CREATE_ENTRY.value,
|
||||
flow_id="sandbox-flow-ver",
|
||||
handler="test_proxy_full",
|
||||
title="Versioned",
|
||||
version=2,
|
||||
minor_version=3,
|
||||
)
|
||||
create_entry.data.update({"host": "1.2.3.4"})
|
||||
create_entry.options.update({"poll_interval": 30})
|
||||
responses = [create_entry]
|
||||
|
||||
with (
|
||||
_wired_sandbox(manager, group="built-in", responses=responses),
|
||||
patch(
|
||||
"homeassistant.components.sandbox.router.classify",
|
||||
return_value=type("A", (), {"is_main": False, "group": "built-in"})(),
|
||||
),
|
||||
):
|
||||
await _install_router(hass, manager)
|
||||
# The proxy class default is VERSION=1 — the entry must NOT inherit it.
|
||||
assert SandboxFlowProxy.VERSION == 1
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"test_proxy_full", context={"source": SOURCE_USER}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
entries = hass.config_entries.async_entries("test_proxy_full")
|
||||
assert len(entries) == 1
|
||||
entry = entries[0]
|
||||
assert entry.version == 2
|
||||
assert entry.minor_version == 3
|
||||
assert entry.options == {"poll_interval": 30}
|
||||
assert entry.data == {"host": "1.2.3.4"}
|
||||
|
||||
|
||||
async def test_form_with_errors_reshows(
|
||||
hass: HomeAssistant, manager: FakeSandboxManager
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"data": {
|
||||
"counter": 42
|
||||
},
|
||||
"version": 2,
|
||||
"minor_version": 3,
|
||||
"key": "demo_key"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"data": {
|
||||
"survives": true
|
||||
},
|
||||
"version": 4,
|
||||
"minor_version": 0,
|
||||
"key": "persistent"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"data": {
|
||||
"side": "built-in"
|
||||
},
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "shared_key"
|
||||
}
|
||||
Reference in New Issue
Block a user