Add error handling tests for USB connection and listing ports

This commit is contained in:
just-stuff-tm 2026-03-04 14:17:01 -05:00
parent 524558c511
commit 25fc9454a8
3 changed files with 97 additions and 1 deletions

View file

@ -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<List<String>> Function()? listUsbPortsImpl;
Future<void> Function({required String portName})? connectUsbImpl;
@override
MeshCoreConnectionState get state => initialState;
@ -38,13 +41,21 @@ class _FakeMeshCoreConnector extends MeshCoreConnector {
bool get isUsbTransportConnected => fakeUsbTransportConnected;
@override
Future<List<String>> listUsbPorts() async => List<String>.from(_ports);
Future<List<String>> listUsbPorts() async {
if (listUsbPortsImpl != null) {
return listUsbPortsImpl!();
}
return List<String>.from(_ports);
}
@override
Future<void> 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: <String>['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);
});
});
}

View file

@ -138,4 +138,25 @@ void main() {
expect(packets, hasLength(1));
expect(packets.single.payload, orderedEquals(<int>[0x55]));
});
test('recovers from invalid frame header', () {
final decoder = UsbSerialFrameDecoder();
final packets = decoder.ingest(
Uint8List.fromList(<int>[
// 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(<int>[0x88]));
});
}

View file

@ -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',
() {