wifi connection established by app, process socket binding, primary os connection untouched
This commit is contained in:
@@ -1,39 +1,183 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:dmc_app/transport/link_transport.dart';
|
||||
import 'package:dmc_app/transport/udp/mlrs_network_controller.dart';
|
||||
import 'package:dmc_app/transport/udp/udp_transport.dart';
|
||||
|
||||
/// Test-Doppel fuer [MlrsNetworkController] (Doku Kommunikationsschicht v2,
|
||||
/// Abschnitt 4): simuliert Netzanfrage-Ergebnisse ueber [emit], ohne einen
|
||||
/// echten Android-Platform-Channel zu brauchen. Zeichnet Aufrufe auf, damit
|
||||
/// Tests den Bindungsfenster-Ablauf (bind -> Socket -> unbind) pruefen
|
||||
/// koennen.
|
||||
class FakeMlrsNetworkController implements MlrsNetworkController {
|
||||
final _eventsController = StreamController<MlrsNetworkEvent>.broadcast();
|
||||
|
||||
final List<String> requestedSsidPrefixes = [];
|
||||
int releaseCount = 0;
|
||||
int bindCount = 0;
|
||||
int unbindCount = 0;
|
||||
bool bindResult = true;
|
||||
bool? staConcurrencyResult;
|
||||
|
||||
@override
|
||||
Stream<MlrsNetworkEvent> get events => _eventsController.stream;
|
||||
|
||||
void emit(MlrsNetworkEvent event) => _eventsController.add(event);
|
||||
|
||||
@override
|
||||
Future<void> requestNetwork(String ssidPrefix) async {
|
||||
requestedSsidPrefixes.add(ssidPrefix);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> releaseNetwork() async {
|
||||
releaseCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> bindProcessToNetwork() async {
|
||||
bindCount++;
|
||||
return bindResult;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> unbindProcessFromNetwork() async {
|
||||
unbindCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> isStaConcurrencySupported() async => staConcurrencyResult;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_eventsController.close();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Port 0 laesst das Betriebssystem einen freien Port waehlen, damit die
|
||||
// Tests nicht mit einer echten mLRS-Bruecke oder parallelen Testlaeufen
|
||||
// um Port 14550 konkurrieren.
|
||||
Future<UdpTransport> openTransport({String? host}) async {
|
||||
// Echter Timer-Tick statt nur eines Microtasks: laesst die
|
||||
// async/await-Kette in UdpTransport.connect() (getSsidPrefix ->
|
||||
// requestNetwork -> Listener-Aufbau) vollstaendig ablaufen, bevor der Test
|
||||
// ein Netzwerk-Ereignis einspeist.
|
||||
Future<void> pump() => Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
|
||||
Future<UdpTransport> connectWithFake(
|
||||
FakeMlrsNetworkController fake, {
|
||||
String? host,
|
||||
int port = 0,
|
||||
String ssidPrefix = 'mLRS-',
|
||||
MlrsNetworkEvent event = const MlrsNetworkAvailable(),
|
||||
}) async {
|
||||
final transport = UdpTransport(
|
||||
getHost: () async => host,
|
||||
getPort: () async => 0,
|
||||
getPort: () async => port,
|
||||
getSsidPrefix: () async => ssidPrefix,
|
||||
networkController: fake,
|
||||
);
|
||||
await transport.connect();
|
||||
final connectFuture = transport.connect();
|
||||
await pump();
|
||||
fake.emit(event);
|
||||
await connectFuture;
|
||||
return transport;
|
||||
}
|
||||
|
||||
test('connect() bindet einen lokalen Port und meldet connected', () async {
|
||||
final transport = await openTransport();
|
||||
final states = <LinkState>[];
|
||||
transport.state.listen(states.add);
|
||||
test('connect() requests the network with the configured SSID prefix',
|
||||
() async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport =
|
||||
await connectWithFake(fake, ssidPrefix: 'mLRS-1234');
|
||||
|
||||
expect(transport.currentState, LinkState.connected);
|
||||
expect(transport.boundPort, isNotNull);
|
||||
expect(transport.boundPort, greaterThan(0));
|
||||
expect(fake.requestedSsidPrefixes, ['mLRS-1234']);
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('eingehende Pakete kommen ueber incoming an und lernen die Gegenstelle',
|
||||
test(
|
||||
'connect() binds process to network, creates the socket, then unbinds',
|
||||
() async {
|
||||
final transport = await openTransport();
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
|
||||
expect(transport.currentState, LinkState.connected);
|
||||
expect(transport.boundPort, isNotNull);
|
||||
expect(fake.bindCount, 1);
|
||||
expect(fake.unbindCount, 1);
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('connect() fails with networkUnavailable when the request is denied',
|
||||
() async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = UdpTransport(
|
||||
getHost: () async => null,
|
||||
getPort: () async => 0,
|
||||
getSsidPrefix: () async => 'mLRS-',
|
||||
networkController: fake,
|
||||
);
|
||||
|
||||
final connectFuture = transport.connect();
|
||||
await pump();
|
||||
fake.emit(const MlrsNetworkUnavailable());
|
||||
|
||||
await expectLater(
|
||||
connectFuture,
|
||||
throwsA(isA<LinkConnectException>()),
|
||||
);
|
||||
expect(transport.lastErrorReason, LinkErrorReason.networkUnavailable);
|
||||
expect(transport.currentState, LinkState.error);
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('losing the network while connected closes the socket and reports '
|
||||
'networkLost', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
|
||||
fake.emit(const MlrsNetworkLost());
|
||||
await pump();
|
||||
|
||||
expect(transport.currentState, LinkState.error);
|
||||
expect(transport.lastErrorReason, LinkErrorReason.networkLost);
|
||||
expect(transport.boundPort, isNull);
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('uses the reported gateway address as initial peer when no host is '
|
||||
'configured', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(
|
||||
fake,
|
||||
event: const MlrsNetworkAvailable(gatewayAddress: '192.168.4.1'),
|
||||
);
|
||||
|
||||
expect(transport.peerDescription, startsWith('192.168.4.1'));
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('a configured host overrides the reported gateway address', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(
|
||||
fake,
|
||||
host: '10.0.0.5',
|
||||
event: const MlrsNetworkAvailable(gatewayAddress: '192.168.4.1'),
|
||||
);
|
||||
|
||||
expect(transport.peerDescription, startsWith('10.0.0.5'));
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('incoming packets arrive via incoming and learn the peer', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
final sender = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, 0);
|
||||
|
||||
final received = <Uint8List>[];
|
||||
@@ -55,8 +199,9 @@ void main() {
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('send() ohne bekannte Gegenstelle wirft LinkConnectException', () async {
|
||||
final transport = await openTransport();
|
||||
test('send() without a known peer throws LinkConnectException', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
|
||||
expect(
|
||||
() => transport.send(Uint8List.fromList([1])),
|
||||
@@ -66,13 +211,14 @@ void main() {
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('send() schickt Bytes an die gelernte Gegenstelle', () async {
|
||||
final transport = await openTransport();
|
||||
test('send() sends bytes to the learned peer', () async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
final peer = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, 0);
|
||||
|
||||
// Erst "sprechen" lassen, damit der Transport die Gegenstelle lernt
|
||||
// (Doku Kommunikationsschicht Abschnitt 3: "Gegenstelle aus dem Verkehr
|
||||
// lernen").
|
||||
// (Doku Kommunikationsschicht v2 Abschnitt 3: "Gegenstelle aus dem
|
||||
// Verkehr lernen").
|
||||
peer.send(
|
||||
Uint8List.fromList([9]),
|
||||
InternetAddress.loopbackIPv4,
|
||||
@@ -99,26 +245,26 @@ void main() {
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('disconnect() setzt LinkState.disconnected', () async {
|
||||
final transport = await openTransport();
|
||||
test('disconnect() closes the socket and releases the network request',
|
||||
() async {
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake);
|
||||
|
||||
await transport.disconnect();
|
||||
|
||||
expect(transport.currentState, LinkState.disconnected);
|
||||
expect(transport.boundPort, isNull);
|
||||
expect(fake.releaseCount, greaterThanOrEqualTo(1));
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('weicht auf einen fluechtigen Port aus, wenn der konfigurierte belegt ist',
|
||||
test(
|
||||
'falls back to an ephemeral port when the configured one is occupied',
|
||||
() async {
|
||||
final occupied = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
|
||||
final transport = UdpTransport(
|
||||
getHost: () async => null,
|
||||
getPort: () async => occupied.port,
|
||||
);
|
||||
|
||||
await transport.connect();
|
||||
final fake = FakeMlrsNetworkController();
|
||||
final transport = await connectWithFake(fake, port: occupied.port);
|
||||
|
||||
expect(transport.currentState, LinkState.connected);
|
||||
expect(transport.boundPort, isNot(occupied.port));
|
||||
@@ -128,16 +274,11 @@ void main() {
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('konfigurierte Host-Vorgabe wird als initiale Gegenstelle uebernommen',
|
||||
() async {
|
||||
final transport = UdpTransport(
|
||||
getHost: () async => '127.0.0.1',
|
||||
getPort: () async => 0,
|
||||
);
|
||||
await transport.connect();
|
||||
test('reports isStaConcurrencySupported() once connected', () async {
|
||||
final fake = FakeMlrsNetworkController()..staConcurrencyResult = true;
|
||||
final transport = await connectWithFake(fake);
|
||||
|
||||
expect(transport.peerDescription, isNotNull);
|
||||
expect(transport.peerDescription, startsWith('127.0.0.1'));
|
||||
expect(transport.staConcurrencySupported, isTrue);
|
||||
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user