From 08b337c9ddb9fe2a144cb9fb831fc05b014b239f Mon Sep 17 00:00:00 2001 From: hrabbach Date: Fri, 26 Jun 2026 00:48:39 +0200 Subject: [PATCH 1/2] fix(config-flow): restore async_step_menu so the pairing menu actually renders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1.1.1 regressed: async_step_user returned a menu with step_id="menu" but async_step_menu had been removed. HA validates that a shown step_id resolves to an async_step_ handler and raises UnknownStep otherwise, so opening "Add device" errored instead of showing the menu. The unit test that called async_step_user directly never exercised HA's step-existence check, so it passed. - async_step_user now delegates to async_step_menu (restored), which serves async_show_menu(step_id="menu", ...). Both methods exist, satisfying HA's subentry-flow contract. - add test_subentry_flow_entry_step_is_menu: drives the real seam hass.config_entries.subentries.async_init(...) and asserts the menu is the first step — this is the test that catches entry-point/step-existence bugs that direct handler-method calls cannot. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QwPMgtiypLgJ5nkR3mUGfL --- .../schellenberg_usb/config_flow.py | 16 +++++++-- tests/test_config_flow.py | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/custom_components/schellenberg_usb/config_flow.py b/custom_components/schellenberg_usb/config_flow.py index c666554..7736db5 100644 --- a/custom_components/schellenberg_usb/config_flow.py +++ b/custom_components/schellenberg_usb/config_flow.py @@ -228,11 +228,21 @@ async def async_step_user( Home Assistant initiates user-triggered subentry flows via the `user` step (per HA config-subentry docs) — NOT async_step_{subentry_type}. - Show the menu so the user can choose between auto-pair and manual-add. - Selecting an option routes to async_step_{option}: 'pair' or - 'manual_add'. + Delegate to the menu so the user can choose auto-pair or manual-add. """ _LOGGER.debug("Subentry blind flow initiated") + return await self.async_step_menu(user_input) + + async def async_step_menu( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: + """Show menu: Pair automatically or Add manually. + + async_show_menu(step_id="menu") REQUIRES a matching async_step_menu + method to exist — HA validates that a shown step_id resolves to a handler + (it raises UnknownStep otherwise). Selecting an option routes to + async_step_{option}: 'pair' or 'manual_add'. + """ return self.async_show_menu( step_id="menu", menu_options=["pair", "manual_add"], diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 62a4ef8..040a415 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -8,6 +8,8 @@ import pytest from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType +from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.schellenberg_usb.const import ( CONF_BIDIRECTIONAL, @@ -88,6 +90,39 @@ async def test_pair_step_shows_form( assert result["step_id"] == "pair" +@pytest.mark.asyncio +async def test_subentry_flow_entry_step_is_menu( + hass: HomeAssistant, + enable_custom_integrations: None, +) -> None: + """Driving the real HA subentry-flow seam must land on the menu. + + Regression for the v1.1.0 bug: the menu was wired to async_step_blind on the + false assumption that HA initiates a subentry flow at async_step_{subentry_type}. + HA actually initiates user-triggered subentry flows at async_step_user, so the + menu was never reachable. This test goes through + hass.config_entries.subentries.async_init — the ACTUAL seam HA uses — rather + than calling a handler method directly, so it would fail if the entry step + regressed back to async_step_blind / the old auto-pair form. + """ + entry = MockConfigEntry( + domain=DOMAIN, + title="Schellenberg USB", + data={CONF_SERIAL_PORT: "/dev/ttyUSB0"}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.subentries.async_init( + (entry.entry_id, SUBENTRY_TYPE_BLIND), + context={"source": "user"}, + ) + + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "menu" + assert "pair" in result["menu_options"] + assert "manual_add" in result["menu_options"] + + @pytest.mark.asyncio async def test_manual_add_creates_subentry( hass: HomeAssistant, mock_hub_entry: ConfigEntry From ee13cc9abfe7c9fec66efb3f7a48bad1891c5a86 Mon Sep 17 00:00:00 2001 From: hrabbach Date: Fri, 26 Jun 2026 00:49:12 +0200 Subject: [PATCH 2/2] chore(release): bump version to 1.1.2 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QwPMgtiypLgJ5nkR3mUGfL --- custom_components/schellenberg_usb/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/schellenberg_usb/manifest.json b/custom_components/schellenberg_usb/manifest.json index 267c8de..aca54ef 100644 --- a/custom_components/schellenberg_usb/manifest.json +++ b/custom_components/schellenberg_usb/manifest.json @@ -15,5 +15,5 @@ "manufacturer": "van ooijen" } ], - "version": "1.1.1" + "version": "1.1.2" }