@@ -4459,6 +4459,232 @@ async def test_bluetooth_provision_timeout_active_lookup_fails(
44594459 assert result ["reason" ] == "unknown"
44604460
44614461
4462+ async def test_bluetooth_provision_timeout_ble_fallback_succeeds (
4463+ hass : HomeAssistant ,
4464+ mock_setup_entry : AsyncMock ,
4465+ mock_setup : AsyncMock ,
4466+ ) -> None :
4467+ """Test WiFi provisioning times out, active lookup fails, but BLE fallback succeeds."""
4468+ # Inject BLE device
4469+ inject_bluetooth_service_info_bleak (hass , BLE_DISCOVERY_INFO )
4470+
4471+ result = await hass .config_entries .flow .async_init (
4472+ DOMAIN ,
4473+ data = BLE_DISCOVERY_INFO ,
4474+ context = {"source" : config_entries .SOURCE_BLUETOOTH },
4475+ )
4476+
4477+ # Confirm and scan
4478+ with patch (
4479+ "homeassistant.components.shelly.config_flow.async_scan_wifi_networks" ,
4480+ return_value = [{"ssid" : "MyNetwork" , "rssi" : - 50 , "auth" : 2 }],
4481+ ):
4482+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], {})
4483+
4484+ # Select network
4485+ result = await hass .config_entries .flow .async_configure (
4486+ result ["flow_id" ],
4487+ {CONF_SSID : "MyNetwork" },
4488+ )
4489+
4490+ # Mock device for BLE status query
4491+ mock_ble_status_device = AsyncMock ()
4492+ mock_ble_status_device .status = {"wifi" : {"sta_ip" : "192.168.1.100" }}
4493+
4494+ # Mock device for secure device feature
4495+ mock_device = AsyncMock ()
4496+ mock_device .initialize = AsyncMock ()
4497+ mock_device .name = "Test name"
4498+ mock_device .status = {"sys" : {}}
4499+ mock_device .xmod_info = {}
4500+ mock_device .shelly = {"model" : MODEL_PLUS_2PM }
4501+ mock_device .wifi_setconfig = AsyncMock (return_value = {})
4502+ mock_device .ble_setconfig = AsyncMock (return_value = {"restart_required" : False })
4503+ mock_device .shutdown = AsyncMock ()
4504+
4505+ # Provision WiFi but no zeroconf discovery arrives, active lookup fails, BLE fallback succeeds
4506+ with (
4507+ patch (
4508+ "homeassistant.components.shelly.config_flow.PROVISIONING_TIMEOUT" ,
4509+ 0.01 , # Short timeout to trigger timeout path
4510+ ),
4511+ patch ("homeassistant.components.shelly.config_flow.async_provision_wifi" ),
4512+ patch (
4513+ "homeassistant.components.shelly.config_flow.async_lookup_device_by_name" ,
4514+ return_value = None , # Active lookup fails
4515+ ),
4516+ patch (
4517+ "homeassistant.components.shelly.config_flow.ble_rpc_device" ,
4518+ ) as mock_ble_rpc ,
4519+ patch (
4520+ "homeassistant.components.shelly.config_flow.get_info" ,
4521+ return_value = MOCK_DEVICE_INFO ,
4522+ ),
4523+ patch (
4524+ "homeassistant.components.shelly.config_flow.RpcDevice.create" ,
4525+ return_value = mock_device ,
4526+ ),
4527+ ):
4528+ # Configure BLE RPC mock to return device with IP
4529+ mock_ble_rpc .return_value .__aenter__ .return_value = mock_ble_status_device
4530+
4531+ result = await hass .config_entries .flow .async_configure (
4532+ result ["flow_id" ],
4533+ {CONF_PASSWORD : "my_password" },
4534+ )
4535+
4536+ # Provisioning shows progress
4537+ assert result ["type" ] is FlowResultType .SHOW_PROGRESS
4538+ await hass .async_block_till_done ()
4539+
4540+ # Timeout occurs, active lookup fails, but BLE fallback gets IP
4541+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ])
4542+
4543+ # Should create entry successfully with IP from BLE
4544+ assert result ["type" ] is FlowResultType .CREATE_ENTRY
4545+ assert result ["title" ] == "Test name"
4546+ assert result ["data" ][CONF_HOST ] == "192.168.1.100"
4547+ assert result ["data" ][CONF_PORT ] == DEFAULT_HTTP_PORT
4548+
4549+
4550+ async def test_bluetooth_provision_timeout_ble_fallback_fails (
4551+ hass : HomeAssistant ,
4552+ ) -> None :
4553+ """Test WiFi provisioning times out, active lookup fails, and BLE fallback also fails."""
4554+ # Inject BLE device
4555+ inject_bluetooth_service_info_bleak (hass , BLE_DISCOVERY_INFO )
4556+
4557+ result = await hass .config_entries .flow .async_init (
4558+ DOMAIN ,
4559+ data = BLE_DISCOVERY_INFO ,
4560+ context = {"source" : config_entries .SOURCE_BLUETOOTH },
4561+ )
4562+
4563+ # Confirm and scan
4564+ with patch (
4565+ "homeassistant.components.shelly.config_flow.async_scan_wifi_networks" ,
4566+ return_value = [{"ssid" : "MyNetwork" , "rssi" : - 50 , "auth" : 2 }],
4567+ ):
4568+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], {})
4569+
4570+ # Select network
4571+ result = await hass .config_entries .flow .async_configure (
4572+ result ["flow_id" ],
4573+ {CONF_SSID : "MyNetwork" },
4574+ )
4575+
4576+ # Provision WiFi but no zeroconf discovery, active lookup fails, BLE fallback fails
4577+ with (
4578+ patch (
4579+ "homeassistant.components.shelly.config_flow.PROVISIONING_TIMEOUT" ,
4580+ 0.01 , # Short timeout to trigger timeout path
4581+ ),
4582+ patch ("homeassistant.components.shelly.config_flow.async_provision_wifi" ),
4583+ patch (
4584+ "homeassistant.components.shelly.config_flow.async_lookup_device_by_name" ,
4585+ return_value = None , # Active lookup fails
4586+ ),
4587+ patch (
4588+ "homeassistant.components.shelly.config_flow.async_get_ip_from_ble" ,
4589+ return_value = None , # BLE fallback also fails
4590+ ),
4591+ ):
4592+ result = await hass .config_entries .flow .async_configure (
4593+ result ["flow_id" ],
4594+ {CONF_PASSWORD : "my_password" },
4595+ )
4596+
4597+ # Provisioning shows progress
4598+ assert result ["type" ] is FlowResultType .SHOW_PROGRESS
4599+ await hass .async_block_till_done ()
4600+
4601+ # Timeout occurs, both active lookup and BLE fallback fail
4602+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ])
4603+
4604+ # Should show provision_failed form
4605+ assert result ["type" ] is FlowResultType .FORM
4606+ assert result ["step_id" ] == "provision_failed"
4607+
4608+ # User aborts after failure
4609+ with patch (
4610+ "homeassistant.components.shelly.config_flow.async_scan_wifi_networks" ,
4611+ side_effect = RuntimeError ("BLE device unavailable" ),
4612+ ):
4613+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], {})
4614+
4615+ assert result ["type" ] is FlowResultType .ABORT
4616+ assert result ["reason" ] == "unknown"
4617+
4618+
4619+ async def test_bluetooth_provision_timeout_ble_exception (
4620+ hass : HomeAssistant ,
4621+ ) -> None :
4622+ """Test WiFi provisioning times out, active lookup fails, and BLE raises exception."""
4623+ # Inject BLE device
4624+ inject_bluetooth_service_info_bleak (hass , BLE_DISCOVERY_INFO )
4625+
4626+ result = await hass .config_entries .flow .async_init (
4627+ DOMAIN ,
4628+ data = BLE_DISCOVERY_INFO ,
4629+ context = {"source" : config_entries .SOURCE_BLUETOOTH },
4630+ )
4631+
4632+ # Confirm and scan
4633+ with patch (
4634+ "homeassistant.components.shelly.config_flow.async_scan_wifi_networks" ,
4635+ return_value = [{"ssid" : "MyNetwork" , "rssi" : - 50 , "auth" : 2 }],
4636+ ):
4637+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], {})
4638+
4639+ # Select network
4640+ result = await hass .config_entries .flow .async_configure (
4641+ result ["flow_id" ],
4642+ {CONF_SSID : "MyNetwork" },
4643+ )
4644+
4645+ # Provision WiFi but no zeroconf discovery, active lookup fails, BLE raises exception
4646+ with (
4647+ patch (
4648+ "homeassistant.components.shelly.config_flow.PROVISIONING_TIMEOUT" ,
4649+ 0.01 , # Short timeout to trigger timeout path
4650+ ),
4651+ patch ("homeassistant.components.shelly.config_flow.async_provision_wifi" ),
4652+ patch (
4653+ "homeassistant.components.shelly.config_flow.async_lookup_device_by_name" ,
4654+ return_value = None , # Active lookup fails
4655+ ),
4656+ patch (
4657+ "homeassistant.components.shelly.config_flow.ble_rpc_device" ,
4658+ side_effect = DeviceConnectionError , # BLE raises exception
4659+ ),
4660+ ):
4661+ result = await hass .config_entries .flow .async_configure (
4662+ result ["flow_id" ],
4663+ {CONF_PASSWORD : "my_password" },
4664+ )
4665+
4666+ # Provisioning shows progress
4667+ assert result ["type" ] is FlowResultType .SHOW_PROGRESS
4668+ await hass .async_block_till_done ()
4669+
4670+ # Timeout occurs, both active lookup and BLE fallback fail (exception)
4671+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ])
4672+
4673+ # Should show provision_failed form
4674+ assert result ["type" ] is FlowResultType .FORM
4675+ assert result ["step_id" ] == "provision_failed"
4676+
4677+ # User aborts after failure
4678+ with patch (
4679+ "homeassistant.components.shelly.config_flow.async_scan_wifi_networks" ,
4680+ side_effect = RuntimeError ("BLE device unavailable" ),
4681+ ):
4682+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], {})
4683+
4684+ assert result ["type" ] is FlowResultType .ABORT
4685+ assert result ["reason" ] == "unknown"
4686+
4687+
44624688async def test_bluetooth_provision_secure_device_both_enabled (
44634689 hass : HomeAssistant ,
44644690 mock_setup_entry : AsyncMock ,
0 commit comments