From 25fc9454a859a6066d098f843341d63d452a2f5e Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Wed, 4 Mar 2026 14:17:01 -0500 Subject: [PATCH] Add error handling tests for USB connection and listing ports --- test/screens/usb_flow_test.dart | 59 ++++++++++++++++++- .../services/usb_serial_frame_codec_test.dart | 21 +++++++ test/utils/usb_port_labels_test.dart | 18 ++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/test/screens/usb_flow_test.dart b/test/screens/usb_flow_test.dart index a932a57..8bbd961 100644 --- a/test/screens/usb_flow_test.dart +++ b/test/screens/usb_flow_test.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; @@ -23,6 +24,8 @@ class _FakeMeshCoreConnector extends MeshCoreConnector { String? fakeActiveUsbPort; String? fakeActiveUsbPortDisplayLabel; bool fakeUsbTransportConnected = false; + Future> Function()? listUsbPortsImpl; + Future Function({required String portName})? connectUsbImpl; @override MeshCoreConnectionState get state => initialState; @@ -38,13 +41,21 @@ class _FakeMeshCoreConnector extends MeshCoreConnector { bool get isUsbTransportConnected => fakeUsbTransportConnected; @override - Future> listUsbPorts() async => List.from(_ports); + Future> listUsbPorts() async { + if (listUsbPortsImpl != null) { + return listUsbPortsImpl!(); + } + return List.from(_ports); + } @override Future connectUsb({ required String portName, int baudRate = 115200, }) async { + if (connectUsbImpl != null) { + return connectUsbImpl!(portName: portName); + } connectUsbCalls += 1; lastConnectPortName = portName; } @@ -145,4 +156,50 @@ void main() { expect(find.widgetWithText(FloatingActionButton, 'USB'), findsNothing); } }); + + group('Error Handling', () { + testWidgets('shows error message when listing ports fails', (tester) async { + final connector = _FakeMeshCoreConnector(); + connector.listUsbPortsImpl = () async { + throw PlatformException( + code: 'usb_permission_denied', + message: 'Permission denied', + ); + }; + + await tester.pumpWidget( + _buildTestApp(connector: connector, child: const UsbScreen()), + ); + await tester.pumpAndSettle(); + + expect( + find.text('USB permission was denied.'), + findsOneWidget, + ); + }); + + testWidgets( + 'connection failure completes without leaving loading state', + (tester) async { + final connector = _FakeMeshCoreConnector( + ports: ['COM1'], + ); + var connectAttempted = false; + connector.connectUsbImpl = ({required String portName}) async { + connectAttempted = true; + throw PlatformException(code: 'usb_busy', message: 'Device is busy'); + }; + + await tester.pumpWidget( + _buildTestApp(connector: connector, child: const UsbScreen()), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(FilledButton, 'Connect')); + await tester.pumpAndSettle(); + + expect(connectAttempted, isTrue); + expect(find.byType(CircularProgressIndicator), findsNothing); + }); + }); } diff --git a/test/services/usb_serial_frame_codec_test.dart b/test/services/usb_serial_frame_codec_test.dart index 54165de..32242bd 100644 --- a/test/services/usb_serial_frame_codec_test.dart +++ b/test/services/usb_serial_frame_codec_test.dart @@ -138,4 +138,25 @@ void main() { expect(packets, hasLength(1)); expect(packets.single.payload, orderedEquals([0x55])); }); + + test('recovers from invalid frame header', () { + final decoder = UsbSerialFrameDecoder(); + + final packets = decoder.ingest( + Uint8List.fromList([ + // First, a malformed frame (e.g. from a partial TX echo) + usbSerialRxFrameStart, + usbSerialTxFrameStart, + // Then, a valid frame + usbSerialRxFrameStart, + 0x01, + 0x00, + 0x88, + ]), + ); + + expect(packets, hasLength(1)); + expect(packets.single.isRxFrame, isTrue); + expect(packets.single.payload, orderedEquals([0x88])); + }); } diff --git a/test/utils/usb_port_labels_test.dart b/test/utils/usb_port_labels_test.dart index b2d88bd..8c9212b 100644 --- a/test/utils/usb_port_labels_test.dart +++ b/test/utils/usb_port_labels_test.dart @@ -35,6 +35,24 @@ void main() { }, ); + test('friendlyUsbPortName works for Linux-style label', () { + expect( + friendlyUsbPortName( + '/dev/ttyACM0 - RAK4631 - USB VID:PID=239A:8029 SER=xxxxxxxx', + ), + 'RAK4631', + ); + }); + + test('friendlyUsbPortName trims whitespace from label parts', () { + expect( + friendlyUsbPortName( + ' /dev/ttyS0 - My Serial Port - n/a ', + ), + 'My Serial Port', + ); + }); + test( 'friendlyUsbPortName falls back to port name when description is n/a', () {