import 'package:abawo_bt_app/controller/bluetooth.dart'; import 'package:abawo_bt_app/model/shifter_types.dart'; import 'package:abawo_bt_app/service/shifter_service.dart'; import 'package:anyhow/anyhow.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('ShifterService.runDfuPreflight', () { test('fails when no active button connection exists', () async { final adapter = _FakeDfuPreflightBluetoothAdapter( currentConnectionState: (ConnectionStatus.disconnected, null), mtuResult: Ok(128), ); final service = ShifterService( buttonDeviceId: 'target-device', dfuPreflightBluetooth: adapter, ); final result = await service.runDfuPreflight(); expect(result.isOk(), isTrue); final preflight = result.unwrap(); expect(preflight.canStart, isFalse); expect(preflight.failureReason, DfuPreflightFailureReason.deviceNotConnected); expect(adapter.requestMtuCallCount, 0); }); test('fails when connected to a different button', () async { final adapter = _FakeDfuPreflightBluetoothAdapter( currentConnectionState: (ConnectionStatus.connected, 'wrong-device'), mtuResult: Ok(128), ); final service = ShifterService( buttonDeviceId: 'target-device', dfuPreflightBluetooth: adapter, ); final result = await service.runDfuPreflight(); expect(result.isOk(), isTrue); final preflight = result.unwrap(); expect(preflight.canStart, isFalse); expect(preflight.failureReason, DfuPreflightFailureReason.wrongConnectedDevice); expect(adapter.requestMtuCallCount, 0); }); test('fails when MTU negotiation fails', () async { final adapter = _FakeDfuPreflightBluetoothAdapter( currentConnectionState: (ConnectionStatus.connected, 'target-device'), mtuResult: bail('adapter rejected mtu request'), ); final service = ShifterService( buttonDeviceId: 'target-device', dfuPreflightBluetooth: adapter, ); final result = await service.runDfuPreflight(requestedMtu: 247); expect(result.isOk(), isTrue); final preflight = result.unwrap(); expect(preflight.canStart, isFalse); expect( preflight.failureReason, DfuPreflightFailureReason.mtuRequestFailed); expect(preflight.message, contains('adapter rejected mtu request')); expect(adapter.requestedMtuValues, [247]); }); test('fails when negotiated MTU is too low for 64-byte frame writes', () async { final adapter = _FakeDfuPreflightBluetoothAdapter( currentConnectionState: (ConnectionStatus.connected, 'target-device'), mtuResult: Ok(universalShifterDfuMinimumMtu - 1), ); final service = ShifterService( buttonDeviceId: 'target-device', dfuPreflightBluetooth: adapter, ); final result = await service.runDfuPreflight(); expect(result.isOk(), isTrue); final preflight = result.unwrap(); expect(preflight.canStart, isFalse); expect(preflight.failureReason, DfuPreflightFailureReason.mtuTooLow); expect(preflight.negotiatedMtu, universalShifterDfuMinimumMtu - 1); expect(preflight.requiredMtu, universalShifterDfuMinimumMtu); }); test('passes when connected to target and MTU is sufficient', () async { final adapter = _FakeDfuPreflightBluetoothAdapter( currentConnectionState: (ConnectionStatus.connected, 'target-device'), mtuResult: Ok(128), ); final service = ShifterService( buttonDeviceId: 'target-device', dfuPreflightBluetooth: adapter, ); final result = await service.runDfuPreflight(); expect(result.isOk(), isTrue); final preflight = result.unwrap(); expect(preflight.canStart, isTrue); expect(preflight.failureReason, isNull); expect(preflight.negotiatedMtu, 128); expect(preflight.requestedMtu, universalShifterDfuPreferredMtu); }); }); } class _FakeDfuPreflightBluetoothAdapter implements DfuPreflightBluetoothAdapter { _FakeDfuPreflightBluetoothAdapter({ required this.currentConnectionState, required Result mtuResult, }) : _mtuResult = mtuResult; @override final (ConnectionStatus, String?) currentConnectionState; final Result _mtuResult; int requestMtuCallCount = 0; final List requestedMtuValues = []; @override Future> requestMtuAndGetValue( String deviceId, { required int mtu, }) async { requestMtuCallCount += 1; requestedMtuValues.add(mtu); return _mtuResult; } }