Fix SwitchBot encrypted device method selection not resetting on back (#167749)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Retha Runolfsson
2026-04-09 15:15:49 +02:00
committed by GitHub
co-authored by Claude Sonnet 4.6 Martin Hjelmare
parent 998f24649d
commit 872120821c
2 changed files with 132 additions and 0 deletions
@@ -96,6 +96,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovered_advs: dict[str, SwitchBotAdvertisement] = {}
self._cloud_username: str | None = None
self._cloud_password: str | None = None
self._encryption_method_selected = False
async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
@@ -197,6 +198,13 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
assert self._discovered_adv is not None
description_placeholders: dict[str, str] = {}
if user_input is None:
if not self._encryption_method_selected and not (
self._cloud_username and self._cloud_password
):
return await self.async_step_encrypted_choose_method()
self._encryption_method_selected = False
# If we have saved credentials from cloud login, try them first
if user_input is None and self._cloud_username and self._cloud_password:
user_input = {
@@ -258,6 +266,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the SwitchBot API chose method step."""
assert self._discovered_adv is not None
self._encryption_method_selected = True
return self.async_show_menu(
step_id="encrypted_choose_method",
menu_options=["encrypted_auth", "encrypted_key"],
@@ -272,6 +281,12 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the encryption key step."""
errors: dict[str, str] = {}
assert self._discovered_adv is not None
if user_input is None:
if not self._encryption_method_selected:
return await self.async_step_encrypted_choose_method()
self._encryption_method_selected = False
if user_input is not None:
model: SwitchbotModel = self._discovered_adv.data["modelName"]
cls = ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS[model]
@@ -221,6 +221,113 @@ async def test_bluetooth_discovery_key(hass: HomeAssistant) -> None:
assert len(mock_setup_entry.mock_calls) == 1
async def test_bluetooth_discovery_encrypted_key_back_navigation(
hass: HomeAssistant,
) -> None:
"""Test that resuming an abandoned encrypted_key flow resets to the method menu."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=WOLOCK_SERVICE_INFO,
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "encrypted_choose_method"
# User selects encrypted_key
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "encrypted_key"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_key"
# Simulate user closing dialog and re-opening: call the step with no input
# (as HA does when resuming an in-progress flow)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=None
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "encrypted_choose_method"
# User can now pick a method again and complete the flow
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "encrypted_key"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_key"
with (
patch_async_setup_entry() as mock_setup_entry,
patch(
"switchbot.SwitchbotLock.verify_encryption_key",
return_value=True,
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_KEY_ID: "ff",
CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert len(mock_setup_entry.mock_calls) == 1
async def test_bluetooth_discovery_encrypted_auth_back_navigation(
hass: HomeAssistant,
) -> None:
"""Test that resuming an abandoned encrypted_auth flow resets to the method menu."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=WOLOCK_SERVICE_INFO,
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "encrypted_choose_method"
# User selects encrypted_auth
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "encrypted_auth"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_auth"
# Simulate user closing dialog and re-opening: call the step with no input
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=None
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "encrypted_choose_method"
# User can switch to encrypted_key and complete the flow
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "encrypted_key"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_key"
with (
patch_async_setup_entry() as mock_setup_entry,
patch(
"switchbot.SwitchbotLock.verify_encryption_key",
return_value=True,
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_KEY_ID: "ff",
CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff",
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert len(mock_setup_entry.mock_calls) == 1
async def test_bluetooth_discovery_already_setup(hass: HomeAssistant) -> None:
"""Test discovery via bluetooth with a valid device when already setup."""
entry = MockConfigEntry(
@@ -1208,12 +1315,22 @@ async def test_user_cloud_login_then_encrypted_device(hass: HomeAssistant) -> No
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_auth"
# Simulate the user navigating away and re-opening the dialog.
# The failed auto-auth cleared credentials, so calling with None now
# redirects back to the method selection menu.
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
None,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "encrypted_choose_method"
# User selects encrypted_auth again and manually enters credentials
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "encrypted_auth"}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "encrypted_auth"