add websocket_test.dart
This commit is contained in:
@@ -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?
|
||||
|
||||
@@ -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
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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&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());
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user