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/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" } 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