diff --git a/src/meshcore/ble_cx.py b/src/meshcore/ble_cx.py index afe9dfb..0fa4d01 100644 --- a/src/meshcore/ble_cx.py +++ b/src/meshcore/ble_cx.py @@ -116,9 +116,12 @@ class BLEConnection: await self.client.pair() logger.info("BLE pairing successful") except Exception as e: - logger.warning(f"BLE pairing failed: {e}") - # Don't fail the connection if pairing fails, as the device - # might already be paired or not require pairing + logger.error(f"BLE pairing failed: {e}") + # A failed pairing leaves the transport in a half-usable + # state — re-raise so the caller gets a clean failure + # instead of a silently degraded connection. + await self.client.disconnect() + raise except BleakDeviceNotFoundError: return None diff --git a/tests/test_ble_pin_pairing.py b/tests/test_ble_pin_pairing.py index aaa3acc..8b49184 100644 --- a/tests/test_ble_pin_pairing.py +++ b/tests/test_ble_pin_pairing.py @@ -37,7 +37,7 @@ class TestBLEPinPairing(unittest.TestCase): @patch("meshcore.ble_cx.BleakClient") def test_ble_connection_with_pin_failed_pairing(self, mock_bleak_client): - """Test BLE connection with PIN when pairing fails but connection continues""" + """Test BLE connection with PIN when pairing fails — re-raises (F17).""" # Arrange mock_client_instance = self._get_mock_bleak_client() mock_client_instance.pair = AsyncMock(side_effect=Exception("Pairing failed")) @@ -47,17 +47,16 @@ class TestBLEPinPairing(unittest.TestCase): pin = "123456" ble_conn = BLEConnection(address=address, pin=pin) - # Act - result = asyncio.run(ble_conn.connect()) - - # Assert + # Act & Assert — pairing failure now re-raises instead of being + # swallowed, because a half-usable transport is worse than a clean + # failure (forensics finding F17). + with self.assertRaises(Exception) as ctx: + asyncio.run(ble_conn.connect()) + self.assertIn("Pairing failed", str(ctx.exception)) mock_client_instance.connect.assert_called_once() mock_client_instance.pair.assert_called_once() - mock_client_instance.start_notify.assert_called_once_with( - UART_TX_CHAR_UUID, ble_conn.handle_rx - ) - # Connection should still succeed even if pairing fails - self.assertEqual(result, address) + # disconnect should be called to clean up the failed connection + mock_client_instance.disconnect.assert_called_once() @patch("meshcore.ble_cx.BleakClient") def test_ble_connection_without_pin_no_pairing(self, mock_bleak_client):