add websocket_test.dart

This commit is contained in:
Sahil Kumar
2021-06-03 18:06:56 +05:30
parent d758f3df1d
commit 1626d7432c
8 changed files with 400 additions and 57 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ analyzer:
- example/**
- lib/src/emoji
- lib/**/*.freezed.dart
- test/**
# - test/**
linter:
rules:
@@ -18,7 +18,7 @@ class _BaseResponse {
}
/// Model response for [StreamChatNetworkError] data
@JsonSerializable(createToJson: false)
@JsonSerializable()
class ErrorResponse extends _BaseResponse {
///
int? code;
@@ -37,6 +37,9 @@ class ErrorResponse extends _BaseResponse {
static ErrorResponse fromJson(Map<String, dynamic> json) =>
_$ErrorResponseFromJson(json);
/// Serialize to json
Map<String, dynamic> toJson() => _$ErrorResponseToJson(this);
@override
String toString() => 'ErrorResponse(code: $code, '
'message: $message, '
@@ -15,6 +15,15 @@ ErrorResponse _$ErrorResponseFromJson(Map<String, dynamic> json) {
..moreInfo = json['more_info'] as String?;
}
Map<String, dynamic> _$ErrorResponseToJson(ErrorResponse instance) =>
<String, dynamic>{
'duration': instance.duration,
'code': instance.code,
'message': instance.message,
'StatusCode': instance.statusCode,
'more_info': instance.moreInfo,
};
SyncResponse _$SyncResponseFromJson(Map<String, dynamic> json) {
return SyncResponse()
..duration = json['duration'] as String?
+1 -1
View File
@@ -30,4 +30,4 @@ dev_dependencies:
freezed: ^0.14.1+3
json_serializable: ^4.1.0
mocktail: ^0.1.1
test: ^1.16.8
test: ^1.16.8
+27
View File
@@ -0,0 +1,27 @@
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/core/http/token.dart';
import 'package:stream_chat/src/core/http/token_manager.dart';
class FakeTokenManager extends Fake implements TokenManager {
final token = Token.development('test-user-id');
@override
bool get isStatic => true;
@override
String? get userId => token.userId;
@override
Future<Token> loadToken({bool refresh = false}) async => token;
@override
Future<Token> setTokenOrProvider(
String userId, {
Token? token,
TokenProvider? provider,
}) async =>
this.token;
@override
void reset() {}
}
+6
View File
@@ -0,0 +1,6 @@
import 'package:mocktail/mocktail.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class MockWebSocketChannel extends Mock implements WebSocketChannel {}
class MockWebSocketSink extends Mock implements WebSocketSink {}
@@ -0,0 +1,344 @@
import 'dart:async';
import 'dart:convert';
import 'package:stream_chat/src/core/http/token_manager.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:test/test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/ws/websocket.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../fakes.dart';
import '../mocks.dart';
void main() {
late TokenManager tokenManager;
late WebSocketChannel webSocketChannel;
late WebSocketSink webSocketSink;
late WebSocket webSocket;
setUp(() {
tokenManager = FakeTokenManager();
webSocketChannel = MockWebSocketChannel();
WebSocketChannel channelProvider(
Uri uri, {
Iterable<String>? protocols,
}) =>
webSocketChannel;
webSocket = WebSocket(
apiKey: 'api-key',
baseUrl: 'base-url',
tokenManager: tokenManager,
webSocketChannelProvider: channelProvider,
);
webSocketSink = MockWebSocketSink();
when(() => webSocketChannel.sink).thenReturn(webSocketSink);
var webSocketController = StreamController<String>.broadcast();
when(() => webSocketChannel.stream).thenAnswer(
(_) => webSocketController.stream,
);
when(() => webSocketSink.add(any())).thenAnswer((invocation) {
webSocketController.add(invocation.positionalArguments.first);
});
when(() => webSocketSink.close(any(), any())).thenAnswer(
(_) {
final res = webSocketController.close();
// re-initializing for future events
webSocketController = StreamController<String>.broadcast();
return res;
},
);
});
tearDown(() {
tokenManager.reset();
webSocket.disconnect();
});
test('`connect` successfully with the provided user', () async {
final user = OwnUser(id: 'test-user');
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final event = await webSocket.connect(user);
expect(event.type, EventType.healthCheck);
expect(event.connectionId, connectionId);
expect(event.me, isNotNull);
expect(event.me!.id, user.id);
addTearDown(timer.cancel);
});
test('`connect` should throw if already in connection attempt', () async {
final user = OwnUser(id: 'test-user');
webSocket.connect(user);
try {
// calling again before previous attempt finishes
await webSocket.connect(user);
} catch (e) {
expect(e, isA<StreamWebSocketError>());
}
});
test('`connect` should throw if `onMessage` contains error', () async {
final user = OwnUser(id: 'test-user');
final error = ErrorResponse()
..code = 333
..message = 'Invalid request';
// Sends error event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
webSocketSink.add(json.encode({'error': error}));
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
ConnectionStatus.disconnected,
]),
);
try {
await webSocket.connect(user);
} catch (e) {
expect(e, isA<StreamWebSocketError>());
final err = e as StreamWebSocketError;
expect(err.code, error.code);
expect(err.message, error.message);
}
addTearDown(timer.cancel);
});
test(
'should `reconnect` automatically '
'if `onMessage` throws error after getting connected',
() async {
final user = OwnUser(id: 'test-user');
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
ConnectionStatus.connected,
// starts reconnecting
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
await webSocket.connect(user);
final error = ErrorResponse()
..code = 333
..message = 'Invalid request';
// Sends error event to web-socket stream
webSocketSink.add(json.encode({'error': error}));
final reconnectTimer = Timer(const Duration(seconds: 3), () {
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
});
expect(webSocket.connectionId, connectionId);
addTearDown(() {
timer.cancel();
reconnectTimer.cancel();
});
},
);
test(
'`onMessage` should handle `health.check` event if `me` is null',
() async {
final user = OwnUser(id: 'test-user');
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
ConnectionStatus.connected,
]),
);
final event = await webSocket.connect(user);
expect(event.type, EventType.healthCheck);
expect(event.connectionId, connectionId);
expect(event.me, isNotNull);
expect(event.me!.id, user.id);
const newConnectionId = 'new-connection-id';
final healthCheckEvent = Event(
type: EventType.healthCheck,
connectionId: newConnectionId,
);
webSocketSink.add(json.encode(healthCheckEvent));
await Future.delayed(const Duration(milliseconds: 300));
expectLater(webSocket.connectionId, newConnectionId);
addTearDown(timer.cancel);
},
);
test('should call `onConnectionError` if web-socket stream throws', () async {
final user = OwnUser(id: 'test-user');
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
const error = StreamWebSocketError('test-error');
webSocketSink.addError(error);
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
// throws error, reconnects
ConnectionStatus.connected,
]),
);
webSocket.connect(user);
// Assuming web-socket stream will add error
// and web-socket now trying to reconnect
await Future.delayed(const Duration(seconds: 3));
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
addTearDown(timer.cancel);
});
test(
'should call `onConnectionClosed` if web-socket stream throws',
() async {
final user = OwnUser(id: 'test-user');
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
webSocketSink.close();
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
// throws error, reconnects
ConnectionStatus.connected,
]),
);
webSocket.connect(user);
// Assuming web-socket stream will add error
// and web-socket now trying to reconnect
await Future.delayed(const Duration(seconds: 3));
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
addTearDown(timer.cancel);
},
);
test('`disconnect` successfully disconnects the current user', () async {
final user = OwnUser(id: 'test-user');
const connectionId = 'test-connection-id';
// Sends connect event to web-socket stream
final timer = Timer(const Duration(milliseconds: 300), () {
final event = Event(
type: EventType.healthCheck,
connectionId: connectionId,
me: user,
);
webSocketSink.add(json.encode(event));
});
expectLater(
webSocket.connectionStatusStream,
emitsInOrder([
ConnectionStatus.disconnected,
ConnectionStatus.connecting,
ConnectionStatus.connected,
// after disconnect
ConnectionStatus.disconnected,
]),
);
final event = await webSocket.connect(user);
expect(event.type, EventType.healthCheck);
expect(event.connectionId, connectionId);
expect(event.me?.id, user.id);
webSocket.disconnect();
addTearDown(timer.cancel);
});
}
+8 -54
View File
@@ -1,26 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat/version.dart';
import 'package:test/test.dart';
import 'dart:math' as math;
// This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped
// optimize the gzip compression for this alphabet.
const _alphabet =
'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
/// Generates a random String id
/// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js
String randomId({int size = 21}) {
var id = '';
for (var i = 0; i < size; i++) {
id += _alphabet[(math.Random().nextDouble() * 64).floor() | 0];
}
return id;
}
void prepareTest() {
// https://github.com/flutter/flutter/issues/20907
@@ -31,40 +12,13 @@ void prepareTest() {
void main() {
prepareTest();
test('stream chat version matches pubspec', () async {
print(randomId());
// /// Create a new instance of [StreamChatClient]
// /// by passing the apikey obtained from your project dashboard.
// final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO);
//
// /// Set the current user. In a production scenario, this should be done using
// /// a backend to generate a user token using our server SDK.
// /// Please see the following for more information:
// /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
// await client.connectUser(
// User(
// id: 'cool-shadow-7',
// extraData: {
// 'image':
// 'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
// },
// ),
// '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''',
// );
//
// try {
// await client.banUser('asdasdas');
// } catch (e) {
// print(e);
// }
// final pubspecPath = '${Directory.current.path}/pubspec.yaml';
// final pubspec = File(pubspecPath).readAsStringSync();
// // ignore: unnecessary_string_escapes
// final regex = RegExp('version:\s*(.*)');
// final match = regex.firstMatch(pubspec);
// expect(match, isNotNull);
// expect(PACKAGE_VERSION, match?.group(1)?.trim());
test('stream chat version matches pubspec', () {
final pubspecPath = '${Directory.current.path}/pubspec.yaml';
final pubspec = File(pubspecPath).readAsStringSync();
// ignore: unnecessary_string_escapes
final regex = RegExp('version:\s*(.*)');
final match = regex.firstMatch(pubspec);
expect(match, isNotNull);
expect(PACKAGE_VERSION, match?.group(1)?.trim());
});
}