tests and mocks for e2e testing (#78)

* tests and mocks for e2e testing

* formatting

* fix analyze errors
This commit is contained in:
davidliu
2022-02-06 20:36:42 +09:00
committed by GitHub
parent 0d92c569df
commit 9bd546b9a2
22 changed files with 799 additions and 34 deletions
+23
View File
@@ -0,0 +1,23 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
class MockDataChannel extends RTCDataChannel {
final String? _label;
RTCDataChannelState? _state = RTCDataChannelState.RTCDataChannelOpen;
MockDataChannel(this._label);
@override
String? get label => _label;
@override
Future<void> send(RTCDataChannelMessage message) async {}
@override
RTCDataChannelState? get state => _state;
@override
Future<void> close() async {
_state = RTCDataChannelState.RTCDataChannelClosing;
_state = RTCDataChannelState.RTCDataChannelClosed;
}
}
+35
View File
@@ -0,0 +1,35 @@
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_client/src/core/engine.dart';
import 'package:livekit_client/src/core/signal_client.dart';
import '../core/signal_client_test.dart';
import 'peerconnection_mock.dart';
import 'websocket_mock.dart';
class E2EContainer {
late MockWebSocketConnector wsConnector;
late SignalClient client;
late Room room;
late Engine engine;
E2EContainer() {
wsConnector = MockWebSocketConnector();
client = SignalClient(wsConnector.connect);
engine = Engine(
signalClient: client, peerConnectionCreate: MockPeerConnection.create);
room = Room(engine: engine);
}
Future<void> dispose() async {
await room.dispose();
}
Future<void> connectRoom() async {
final connectFuture = room.connect(exampleUri, token);
Future.delayed(const Duration(milliseconds: 1), () {
wsConnector.onData(joinResponse.writeToBuffer());
wsConnector.onData(offerResponse.writeToBuffer());
});
await connectFuture;
}
}
+25
View File
@@ -0,0 +1,25 @@
@Timeout(Duration(seconds: 5))
import 'package:flutter_test/flutter_test.dart';
import 'package:livekit_client/livekit_client.dart';
import '../core/signal_client_test.dart';
import '../mock/e2e_container.dart';
void main() {
late E2EContainer container;
late Room room;
setUp(() async {
container = E2EContainer();
room = container.room;
});
tearDown(() async {
await container.dispose();
});
test('connect', () async {
await container.connectRoom();
expect(room.connectionState, ConnectionState.connected);
expect(room.localParticipant?.sid, joinResponse.join.participant.sid);
});
}
+227
View File
@@ -0,0 +1,227 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'datachannel_mock.dart';
class MockPeerConnection extends RTCPeerConnection {
static const _offerType = 'offer';
static const _answerType = 'answer';
bool closed = false;
RTCSessionDescription? _localDescription;
RTCSessionDescription? _remoteDescription;
RTCPeerConnectionState _connectionState =
RTCPeerConnectionState.RTCPeerConnectionStateNew;
RTCIceConnectionState _iceConnectionState =
RTCIceConnectionState.RTCIceConnectionStateNew;
RTCIceGatheringState _iceGatheringState =
RTCIceGatheringState.RTCIceGatheringStateNew;
@override
Future<RTCSessionDescription?> getLocalDescription() async =>
_localDescription;
@override
Future<void> setLocalDescription(RTCSessionDescription description) async {
_localDescription = description;
_handleIceConnection();
}
@override
Future<RTCSessionDescription?> getRemoteDescription() async =>
_remoteDescription;
@override
Future<void> setRemoteDescription(RTCSessionDescription description) async {
_remoteDescription = description;
_handleIceConnection();
}
void _handleIceConnection() {
if ((_localDescription?.type == _offerType &&
_remoteDescription?.type == _answerType) ||
(_localDescription?.type == _answerType &&
_remoteDescription?.type == _offerType)) {
iceConnectionState = RTCIceConnectionState.RTCIceConnectionStateCompleted;
}
}
@override
RTCPeerConnectionState get connectionState => _connectionState;
set connectionState(RTCPeerConnectionState newState) {
if (newState != _connectionState) {
_connectionState = newState;
onConnectionState?.call(newState);
}
}
@override
RTCIceConnectionState get iceConnectionState => _iceConnectionState;
set iceConnectionState(RTCIceConnectionState newState) {
if (newState != _iceConnectionState) {
_iceConnectionState = newState;
onIceConnectionState?.call(newState);
switch (newState) {
case RTCIceConnectionState.RTCIceConnectionStateNew:
connectionState = RTCPeerConnectionState.RTCPeerConnectionStateNew;
break;
case RTCIceConnectionState.RTCIceConnectionStateChecking:
connectionState =
RTCPeerConnectionState.RTCPeerConnectionStateConnecting;
break;
case RTCIceConnectionState.RTCIceConnectionStateConnected:
case RTCIceConnectionState.RTCIceConnectionStateCompleted:
connectionState =
RTCPeerConnectionState.RTCPeerConnectionStateConnected;
break;
case RTCIceConnectionState.RTCIceConnectionStateFailed:
connectionState = RTCPeerConnectionState.RTCPeerConnectionStateFailed;
break;
case RTCIceConnectionState.RTCIceConnectionStateDisconnected:
connectionState =
RTCPeerConnectionState.RTCPeerConnectionStateDisconnected;
break;
case RTCIceConnectionState.RTCIceConnectionStateClosed:
connectionState = RTCPeerConnectionState.RTCPeerConnectionStateClosed;
break;
case RTCIceConnectionState.RTCIceConnectionStateCount:
throw Exception("This state shouldn't exist (not in RFC).");
}
}
}
@override
RTCIceGatheringState get iceGatheringState => _iceGatheringState;
set iceGatheringState(RTCIceGatheringState newState) {
if (newState != _iceGatheringState) {
_iceGatheringState = newState;
onIceGatheringState?.call(newState);
}
}
@override
Future<void> addCandidate(RTCIceCandidate candidate) async {}
@override
Future<void> addStream(MediaStream stream) async {}
@override
Future<RTCRtpSender> addTrack(MediaStreamTrack track,
[MediaStream? stream]) async {
// TODO: implement addTrack
throw UnimplementedError();
}
@override
Future<RTCRtpTransceiver> addTransceiver(
{MediaStreamTrack? track,
RTCRtpMediaType? kind,
RTCRtpTransceiverInit? init}) {
// TODO: implement addTransceiver
throw UnimplementedError();
}
@override
Future<void> close() async {
closed = true;
}
@override
Future<RTCSessionDescription> createAnswer(
[Map<String, dynamic>? constraints]) async {
return RTCSessionDescription('local_answer', 'answer');
}
@override
Future<RTCSessionDescription> createOffer(
[Map<String, dynamic>? constraints]) async {
return RTCSessionDescription('local_offer', 'offer');
}
@override
RTCSignalingState? get signalingState {
if (closed) {
return RTCSignalingState.RTCSignalingStateClosed;
}
if ((_localDescription?.type == null && _remoteDescription?.type == null) ||
(_localDescription?.type == _offerType &&
_remoteDescription?.type == _answerType) ||
(_localDescription?.type == _answerType &&
_remoteDescription?.type == _offerType)) {
return RTCSignalingState.RTCSignalingStateStable;
}
if (_localDescription?.type == _offerType &&
_remoteDescription?.type == null) {
return RTCSignalingState.RTCSignalingStateHaveLocalOffer;
}
if (_remoteDescription?.type == _offerType &&
_localDescription?.type == null) {
return RTCSignalingState.RTCSignalingStateHaveRemoteOffer;
}
throw Exception(
'Illegal signalling state? localDesc: $_localDescription, remoteDesc: $_remoteDescription');
}
@override
Future<RTCDataChannel> createDataChannel(
String label, RTCDataChannelInit dataChannelDict) async {
return MockDataChannel(label);
}
@override
RTCDTMFSender createDtmfSender(MediaStreamTrack track) {
// TODO: implement createDtmfSender
throw UnimplementedError();
}
@override
Future<void> dispose() async {
await close();
}
@override
// TODO: implement getConfiguration
Map<String, dynamic> get getConfiguration => throw UnimplementedError();
@override
List<MediaStream?> getLocalStreams() => List.empty();
@override
Future<List<RTCRtpReceiver>> getReceivers() async => List.empty();
@override
List<MediaStream?> getRemoteStreams() => List.empty();
@override
Future<List<RTCRtpSender>> getSenders() async => List.empty();
@override
Future<List<StatsReport>> getStats([MediaStreamTrack? track]) async =>
List.empty();
@override
Future<List<RTCRtpTransceiver>> getTransceivers() async => List.empty();
@override
Future<void> removeStream(MediaStream stream) async {}
@override
Future<bool> removeTrack(RTCRtpSender sender) async => true;
@override
Future<void> setConfiguration(Map<String, dynamic> configuration) {
// TODO: implement setConfiguration
throw UnimplementedError();
}
static Future<RTCPeerConnection> create(Map<String, dynamic> configuration,
[Map<String, dynamic>? constraints]) async =>
MockPeerConnection();
}
+31
View File
@@ -0,0 +1,31 @@
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_client/src/proto/livekit_models.pb.dart' as lk_models;
final localAudioTrack = lk_models.TrackInfo(
sid: 'local_audio_track_sid',
type: TrackType.AUDIO,
);
final remoteAudioTrack = lk_models.TrackInfo(
sid: 'remote_audio_track_sid',
type: TrackType.AUDIO,
);
final localParticipantData = lk_models.ParticipantInfo(
sid: 'local_participant_sid',
identity: 'local_participant_identity',
state: lk_models.ParticipantInfo_State.ACTIVE,
);
final remoteParticipantData = lk_models.ParticipantInfo(
sid: 'remote_participant_sid',
identity: 'remote_participant_identity',
state: lk_models.ParticipantInfo_State.ACTIVE,
tracks: [remoteAudioTrack],
);
final remoteSpeakerInfo = lk_models.SpeakerInfo(
sid: remoteParticipantData.sid,
level: 1.0,
active: true,
);
+22
View File
@@ -0,0 +1,22 @@
import 'package:livekit_client/src/support/websocket.dart';
class MockWebSocket extends LiveKitWebSocket {
@override
void send(List<int> data) {}
}
class MockWebSocketConnector {
WebSocketEventHandlers? handlers;
WebSocketOnData get onData => handlers!.onData!;
WebSocketOnDispose get onDispose => handlers!.onDispose!;
WebSocketOnError get onError => handlers!.onError!;
Future<LiveKitWebSocket> connect(Uri uri,
[WebSocketEventHandlers? options]) async {
handlers = options;
return MockWebSocket();
}
}