@@ -7,7 +7,6 @@ import 'package:stream_chat/src/core/http/token.dart';
|
||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||
import 'package:stream_chat/src/core/error/error.dart';
|
||||
|
||||
|
||||
///
|
||||
class AuthInterceptor extends Interceptor {
|
||||
///
|
||||
@@ -84,7 +83,7 @@ class AuthInterceptor extends Interceptor {
|
||||
);
|
||||
return handler.resolve(response);
|
||||
} on DioError catch (error) {
|
||||
return handler.reject(error);
|
||||
return handler.next(error);
|
||||
}
|
||||
}
|
||||
return handler.next(err);
|
||||
|
||||
@@ -86,8 +86,8 @@ class StreamHttpClient {
|
||||
/// [StreamHttpClient] instance dequeue the request task。
|
||||
void unlock() => httpClient.unlock();
|
||||
|
||||
///Clear the current [StreamHttpClient] instance waiting queue.
|
||||
void clear() => httpClient.close();
|
||||
/// Clear the current [StreamHttpClient] instance waiting queue.
|
||||
void clear() => httpClient.clear();
|
||||
|
||||
/// Shuts down the [StreamHttpClient].
|
||||
///
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late ConnectionIdManager connectionIdManager;
|
||||
|
||||
setUp(() {
|
||||
connectionIdManager = ConnectionIdManager();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
connectionIdManager.reset();
|
||||
});
|
||||
|
||||
test('`setConnectionId` should set connectionId', () {
|
||||
expect(connectionIdManager.connectionId, isNull);
|
||||
expect(connectionIdManager.hasConnectionId, isFalse);
|
||||
|
||||
const connectionId = 'test-connection-id';
|
||||
connectionIdManager.setConnectionId(connectionId);
|
||||
|
||||
expect(connectionIdManager.connectionId, connectionId);
|
||||
expect(connectionIdManager.hasConnectionId, isTrue);
|
||||
});
|
||||
|
||||
test('`reset` should clear the connectionId', () {
|
||||
const connectionId = 'test-connection-id';
|
||||
connectionIdManager.setConnectionId(connectionId);
|
||||
|
||||
expect(connectionIdManager.connectionId, connectionId);
|
||||
expect(connectionIdManager.hasConnectionId, isTrue);
|
||||
|
||||
connectionIdManager.reset();
|
||||
|
||||
expect(connectionIdManager.connectionId, isNull);
|
||||
expect(connectionIdManager.hasConnectionId, isFalse);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||
import 'package:stream_chat/src/core/http/token.dart';
|
||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../../mocks.dart';
|
||||
|
||||
void main() {
|
||||
late StreamHttpClient client;
|
||||
late TokenManager tokenManager;
|
||||
late AuthInterceptor authInterceptor;
|
||||
|
||||
setUp(() {
|
||||
client = MockHttpClient();
|
||||
tokenManager = MockTokenManager();
|
||||
authInterceptor = AuthInterceptor(client, tokenManager);
|
||||
});
|
||||
|
||||
test(
|
||||
'`onRequest` should add userId, authToken, authType in the request',
|
||||
() async {
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final handler = RequestInterceptorHandler();
|
||||
|
||||
final headers = options.headers;
|
||||
final queryParams = options.queryParameters;
|
||||
expect(headers.containsKey('Authorization'), isFalse);
|
||||
expect(headers.containsKey('stream-auth-type'), isFalse);
|
||||
expect(queryParams.containsKey('user_id'), isFalse);
|
||||
|
||||
final token = Token.development('test-user-id');
|
||||
when(() => tokenManager.loadToken(refresh: any(named: 'refresh')))
|
||||
.thenAnswer((_) async => token);
|
||||
|
||||
authInterceptor.onRequest(options, handler);
|
||||
|
||||
final updatedOptions = (await handler.future).data as RequestOptions;
|
||||
final updateHeaders = updatedOptions.headers;
|
||||
final updatedQueryParams = updatedOptions.queryParameters;
|
||||
|
||||
expect(updateHeaders.containsKey('Authorization'), isTrue);
|
||||
expect(updateHeaders['Authorization'], token.rawValue);
|
||||
expect(updateHeaders.containsKey('stream-auth-type'), isTrue);
|
||||
expect(updateHeaders['stream-auth-type'], token.authType.raw);
|
||||
expect(updatedQueryParams.containsKey('user_id'), isTrue);
|
||||
expect(updatedQueryParams['user_id'], token.userId);
|
||||
|
||||
verify(() => tokenManager.loadToken(refresh: any(named: 'refresh')))
|
||||
.called(1);
|
||||
verifyNoMoreInteractions(tokenManager);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`onRequest` should reject with error if `tokenManager.loadToken` throws',
|
||||
() async {
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final handler = RequestInterceptorHandler();
|
||||
|
||||
authInterceptor.onRequest(options, handler);
|
||||
|
||||
try {
|
||||
await handler.future;
|
||||
} catch (e) {
|
||||
// need to cast it as the type is private in dio
|
||||
var error = (e as dynamic).data;
|
||||
expect(error, isA<StreamChatDioError>());
|
||||
error = (error as StreamChatDioError).error;
|
||||
expect(error.code, ChatErrorCode.undefinedToken.code);
|
||||
expect(error.message, ChatErrorCode.undefinedToken.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test('`onError` should retry the request with refreshed token', () async {
|
||||
const path = 'test-request-path';
|
||||
final options = RequestOptions(path: path);
|
||||
const code = ChatErrorCode.tokenExpired;
|
||||
final errorResponse = ErrorResponse()
|
||||
..code = code.code
|
||||
..message = code.message;
|
||||
final response = Response(
|
||||
requestOptions: options,
|
||||
data: errorResponse.toJson(),
|
||||
);
|
||||
final err = DioError(requestOptions: options, response: response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
|
||||
when(() => tokenManager.isStatic).thenReturn(false);
|
||||
|
||||
when(() => client.lock()).thenReturn(() {});
|
||||
|
||||
final token = Token.development('test-user-id');
|
||||
when(() => tokenManager.loadToken(refresh: true))
|
||||
.thenAnswer((_) async => token);
|
||||
|
||||
when(() => client.unlock()).thenReturn(() {});
|
||||
|
||||
when(() => client.request(
|
||||
path,
|
||||
data: options.data,
|
||||
onReceiveProgress: options.onReceiveProgress,
|
||||
onSendProgress: options.onSendProgress,
|
||||
queryParameters: options.queryParameters,
|
||||
cancelToken: options.cancelToken,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => Response(
|
||||
requestOptions: options,
|
||||
statusCode: 200,
|
||||
));
|
||||
|
||||
authInterceptor.onError(err, handler);
|
||||
|
||||
final res = await handler.future;
|
||||
|
||||
var data = res.data;
|
||||
expect(data, isA<Response>());
|
||||
data = data as Response;
|
||||
expect(data, isNotNull);
|
||||
expect(data.statusCode, 200);
|
||||
expect(data.requestOptions.path, path);
|
||||
|
||||
verify(() => tokenManager.isStatic).called(1);
|
||||
|
||||
verify(() => client.lock()).called(1);
|
||||
|
||||
verify(() => tokenManager.loadToken(refresh: true)).called(1);
|
||||
verifyNoMoreInteractions(tokenManager);
|
||||
|
||||
verify(() => client.unlock()).called(1);
|
||||
verify(() => client.request(
|
||||
path,
|
||||
data: options.data,
|
||||
onReceiveProgress: options.onReceiveProgress,
|
||||
onSendProgress: options.onSendProgress,
|
||||
queryParameters: options.queryParameters,
|
||||
cancelToken: options.cancelToken,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
});
|
||||
|
||||
test(
|
||||
'`onError` should reject with error if retried request throws',
|
||||
() async {
|
||||
const path = 'test-request-path';
|
||||
final options = RequestOptions(path: path);
|
||||
const code = ChatErrorCode.tokenExpired;
|
||||
final errorResponse = ErrorResponse()
|
||||
..code = code.code
|
||||
..message = code.message;
|
||||
final response = Response(
|
||||
requestOptions: options,
|
||||
data: errorResponse.toJson(),
|
||||
);
|
||||
final err = DioError(requestOptions: options, response: response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
|
||||
when(() => tokenManager.isStatic).thenReturn(false);
|
||||
|
||||
when(() => client.lock()).thenReturn(() {});
|
||||
|
||||
final token = Token.development('test-user-id');
|
||||
when(() => tokenManager.loadToken(refresh: true))
|
||||
.thenAnswer((_) async => token);
|
||||
|
||||
when(() => client.unlock()).thenReturn(() {});
|
||||
|
||||
when(() => client.request(
|
||||
path,
|
||||
data: options.data,
|
||||
onReceiveProgress: options.onReceiveProgress,
|
||||
onSendProgress: options.onSendProgress,
|
||||
queryParameters: options.queryParameters,
|
||||
cancelToken: options.cancelToken,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(err);
|
||||
|
||||
authInterceptor.onError(err, handler);
|
||||
|
||||
try {
|
||||
await handler.future;
|
||||
} catch (e) {
|
||||
// need to cast it as the type is private in dio
|
||||
final error = (e as dynamic).data;
|
||||
expect(error, isA<DioError>());
|
||||
}
|
||||
|
||||
verify(() => tokenManager.isStatic).called(1);
|
||||
|
||||
verify(() => client.lock()).called(1);
|
||||
|
||||
verify(() => tokenManager.loadToken(refresh: true)).called(1);
|
||||
verifyNoMoreInteractions(tokenManager);
|
||||
|
||||
verify(() => client.unlock()).called(1);
|
||||
verify(() => client.request(
|
||||
path,
|
||||
data: options.data,
|
||||
onReceiveProgress: options.onReceiveProgress,
|
||||
onSendProgress: options.onSendProgress,
|
||||
queryParameters: options.queryParameters,
|
||||
cancelToken: options.cancelToken,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`onError` should reject with error if `tokenManager.isStatic` is true',
|
||||
() async {
|
||||
const path = 'test-request-path';
|
||||
final options = RequestOptions(path: path);
|
||||
const code = ChatErrorCode.tokenExpired;
|
||||
final errorResponse = ErrorResponse()
|
||||
..code = code.code
|
||||
..message = code.message;
|
||||
final response = Response(
|
||||
requestOptions: options,
|
||||
data: errorResponse.toJson(),
|
||||
);
|
||||
final err = DioError(requestOptions: options, response: response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
|
||||
when(() => tokenManager.isStatic).thenReturn(true);
|
||||
|
||||
authInterceptor.onError(err, handler);
|
||||
|
||||
try {
|
||||
await handler.future;
|
||||
} catch (e) {
|
||||
// need to cast it as the type is private in dio
|
||||
final error = (e as dynamic).data;
|
||||
expect(error, isA<DioError>());
|
||||
final response = StreamChatNetworkError.fromDioError(error);
|
||||
expect(response.errorCode, code);
|
||||
}
|
||||
|
||||
verify(() => tokenManager.isStatic).called(1);
|
||||
verifyNoMoreInteractions(tokenManager);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`onError` should reject with error if error is not a `tokenExpired error`',
|
||||
() async {
|
||||
const path = 'test-request-path';
|
||||
final options = RequestOptions(path: path);
|
||||
final response = Response(requestOptions: options);
|
||||
final err = DioError(requestOptions: options, response: response);
|
||||
final handler = ErrorInterceptorHandler();
|
||||
|
||||
authInterceptor.onError(err, handler);
|
||||
|
||||
try {
|
||||
await handler.future;
|
||||
} catch (e) {
|
||||
// need to cast it as the type is private in dio
|
||||
final error = (e as dynamic).data;
|
||||
expect(error, isA<DioError>());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../../mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ConnectionIdManager connectionIdManager;
|
||||
late ConnectionIdInterceptor connectionIdInterceptor;
|
||||
|
||||
setUp(() {
|
||||
connectionIdManager = MockConnectionIdManager();
|
||||
connectionIdInterceptor = ConnectionIdInterceptor(connectionIdManager);
|
||||
});
|
||||
|
||||
test(
|
||||
'`onRequest` should add connectionId in the request',
|
||||
() async {
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final handler = RequestInterceptorHandler();
|
||||
|
||||
final queryParams = options.queryParameters;
|
||||
expect(queryParams.containsKey('connection_id'), isFalse);
|
||||
|
||||
const connectionId = 'test-connection-id';
|
||||
when(() => connectionIdManager.hasConnectionId).thenReturn(true);
|
||||
when(() => connectionIdManager.connectionId).thenReturn(connectionId);
|
||||
|
||||
connectionIdInterceptor.onRequest(options, handler);
|
||||
|
||||
final updatedOptions = (await handler.future).data as RequestOptions;
|
||||
final updatedQueryParams = updatedOptions.queryParameters;
|
||||
|
||||
expect(updatedQueryParams.containsKey('connection_id'), isTrue);
|
||||
expect(updatedQueryParams['connection_id'], connectionId);
|
||||
|
||||
verify(() => connectionIdManager.hasConnectionId).called(1);
|
||||
verify(() => connectionIdManager.connectionId).called(1);
|
||||
verifyNoMoreInteractions(connectionIdManager);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`onRequest` should not add connectionId if `hasConnectionId` is false',
|
||||
() async {
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final handler = RequestInterceptorHandler();
|
||||
|
||||
final queryParams = options.queryParameters;
|
||||
expect(queryParams.containsKey('connection_id'), isFalse);
|
||||
|
||||
when(() => connectionIdManager.hasConnectionId).thenReturn(false);
|
||||
|
||||
connectionIdInterceptor.onRequest(options, handler);
|
||||
|
||||
final updatedOptions = (await handler.future).data as RequestOptions;
|
||||
final updatedQueryParams = updatedOptions.queryParameters;
|
||||
|
||||
expect(updatedQueryParams.containsKey('connection_id'), isFalse);
|
||||
|
||||
verify(() => connectionIdManager.hasConnectionId).called(1);
|
||||
verifyNoMoreInteractions(connectionIdManager);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:stream_chat/src/core/error/error.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('should create a new instance of StreamChatDioError', () {
|
||||
final error = StreamChatNetworkError(ChatErrorCode.inputError);
|
||||
final options = RequestOptions(path: 'test-path');
|
||||
final dioError = StreamChatDioError(
|
||||
error: error,
|
||||
requestOptions: options,
|
||||
);
|
||||
|
||||
expect(dioError, isA<DioError>());
|
||||
expect(dioError, isNotNull);
|
||||
expect(dioError.error, error);
|
||||
expect(dioError.requestOptions, options);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||
import 'package:stream_chat/src/location.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('should return the all default set params', () {
|
||||
const options = StreamHttpClientOptions();
|
||||
expect(options.location, isNull);
|
||||
expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com');
|
||||
expect(options.connectTimeout, const Duration(seconds: 6));
|
||||
expect(options.receiveTimeout, const Duration(seconds: 6));
|
||||
});
|
||||
|
||||
test('should override all the default set params', () {
|
||||
const options = StreamHttpClientOptions(
|
||||
baseUrl: 'base-url',
|
||||
connectTimeout: Duration(seconds: 3),
|
||||
receiveTimeout: Duration(seconds: 3),
|
||||
);
|
||||
expect(options.location, isNull);
|
||||
expect(options.baseUrl, 'base-url');
|
||||
expect(options.connectTimeout, const Duration(seconds: 3));
|
||||
expect(options.receiveTimeout, const Duration(seconds: 3));
|
||||
});
|
||||
|
||||
group('should create baseUrl according to provided location', () {
|
||||
test('us-east', () {
|
||||
const options = StreamHttpClientOptions(location: Location.usEast);
|
||||
expect(options.location, isNotNull);
|
||||
expect(options.baseUrl, 'https://chat-proxy-us-east.stream-io-api.com');
|
||||
});
|
||||
test('eu-west', () {
|
||||
const options = StreamHttpClientOptions(location: Location.euWest);
|
||||
expect(options.location, isNotNull);
|
||||
expect(options.baseUrl, 'https://chat-proxy-dublin.stream-io-api.com');
|
||||
});
|
||||
test('mumbai', () {
|
||||
const options = StreamHttpClientOptions(location: Location.mumbai);
|
||||
expect(options.location, isNotNull);
|
||||
expect(options.baseUrl, 'https://chat-proxy-mumbai.stream-io-api.com');
|
||||
});
|
||||
test('sydney', () {
|
||||
const options = StreamHttpClientOptions(location: Location.sydney);
|
||||
expect(options.location, isNotNull);
|
||||
expect(options.baseUrl, 'https://chat-proxy-sydney.stream-io-api.com');
|
||||
});
|
||||
test('singapore', () {
|
||||
const options = StreamHttpClientOptions(location: Location.singapore);
|
||||
expect(options.location, isNotNull);
|
||||
expect(options.baseUrl, 'https://chat-proxy-singapore.stream-io-api.com');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/api/responses.dart';
|
||||
import 'package:stream_chat/src/core/error/error.dart';
|
||||
import 'package:stream_chat/src/core/http/connection_id_manager.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_http_client.dart';
|
||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../mocks.dart';
|
||||
|
||||
void main() {
|
||||
Response successResponse(String path) => Response(
|
||||
requestOptions: RequestOptions(path: path),
|
||||
statusCode: 200,
|
||||
);
|
||||
|
||||
DioError throwableError(
|
||||
String path, {
|
||||
StreamChatNetworkError? error,
|
||||
bool streamChatDioError = false,
|
||||
}) {
|
||||
if (streamChatDioError) assert(error != null, '');
|
||||
final options = RequestOptions(path: path);
|
||||
final data = ErrorResponse()
|
||||
..code = error?.code
|
||||
..statusCode = error?.statusCode
|
||||
..message = error?.message;
|
||||
DioError? dioError;
|
||||
if (streamChatDioError) {
|
||||
dioError = StreamChatDioError(error: error!, requestOptions: options);
|
||||
} else {
|
||||
dioError = DioError(
|
||||
error: error,
|
||||
requestOptions: options,
|
||||
response: Response(
|
||||
requestOptions: options,
|
||||
statusCode: data.statusCode,
|
||||
data: data.toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return dioError;
|
||||
}
|
||||
|
||||
test('AuthInterceptor should be added if tokenManager is provided', () {
|
||||
const apiKey = 'api-key';
|
||||
final client = StreamHttpClient(apiKey, tokenManager: TokenManager());
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(client.httpClient.interceptors.first, isA<AuthInterceptor>());
|
||||
});
|
||||
|
||||
test(
|
||||
'connectionIdInterceptor should be added if connectionIdManager is provided',
|
||||
() {
|
||||
const apiKey = 'api-key';
|
||||
final client = StreamHttpClient(
|
||||
apiKey,
|
||||
connectionIdManager: ConnectionIdManager(),
|
||||
);
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(
|
||||
client.httpClient.interceptors.first,
|
||||
isA<ConnectionIdInterceptor>(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('loggingInterceptor should be added if logger is provided', () {
|
||||
const apiKey = 'api-key';
|
||||
final client = StreamHttpClient(
|
||||
apiKey,
|
||||
logger: Logger('test-logger'),
|
||||
);
|
||||
|
||||
expect(client.httpClient.interceptors.length, 1);
|
||||
expect(
|
||||
client.httpClient.interceptors.first,
|
||||
isA<LoggingInterceptor>(),
|
||||
);
|
||||
});
|
||||
|
||||
test('loggingInterceptor should log requests', () async {
|
||||
const apiKey = 'api-key';
|
||||
final logger = MockLogger();
|
||||
final client = StreamHttpClient(apiKey, logger: logger);
|
||||
|
||||
try {
|
||||
await client.get('path');
|
||||
} catch (_) {}
|
||||
|
||||
verify(() => logger.info(any())).called(16);
|
||||
});
|
||||
|
||||
test('loggingInterceptor should log error', () async {
|
||||
const apiKey = 'api-key';
|
||||
final logger = MockLogger();
|
||||
final client = StreamHttpClient(apiKey, logger: logger);
|
||||
|
||||
try {
|
||||
await client.get('path');
|
||||
} catch (_) {}
|
||||
|
||||
verify(() => logger.severe(any())).called(8);
|
||||
});
|
||||
|
||||
test('`.lock` should lock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key');
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
client.lock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isTrue);
|
||||
});
|
||||
|
||||
test('`.unlock` should unlock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key');
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
client.lock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isTrue);
|
||||
client.unlock();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
});
|
||||
|
||||
test('`.clear` should clear and unlock the dio client', () async {
|
||||
final client = StreamHttpClient('api-key')..clear();
|
||||
expect(client.httpClient.interceptors.requestLock.locked, isFalse);
|
||||
});
|
||||
|
||||
test('`.close` should close the dio client', () async {
|
||||
final client = StreamHttpClient('api-key')..close(force: true);
|
||||
try {
|
||||
await client.get('path');
|
||||
} on StreamChatNetworkError catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e.message, "Dio can't establish new connection after closed.");
|
||||
}
|
||||
});
|
||||
|
||||
test('`.get` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-get-api-path';
|
||||
when(() => dio.get(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.get(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.get(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test('`.get` should throw an instance of `StreamChatNetworkError`', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-get-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.get(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.get(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.get(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test('`.post` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-post-api-path';
|
||||
when(() => dio.post(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.post(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.post(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.post` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-post-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.post(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.post(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.post(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.delete` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-delete-api-path';
|
||||
when(() => dio.delete(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.delete(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.delete(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.delete` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-delete-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.delete(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.delete(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.delete(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.patch` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-patch-api-path';
|
||||
when(() => dio.patch(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.patch(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.patch(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.patch` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-patch-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.patch(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.patch(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.patch(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.put` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-put-api-path';
|
||||
when(() => dio.put(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.put(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.put(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.put` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-put-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.put(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.put(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.put(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.postFile` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-delete-api-path';
|
||||
final file = MultipartFile.fromBytes([]);
|
||||
|
||||
when(() => dio.post(
|
||||
path,
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.postFile(path, file);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.post(
|
||||
path,
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.postFile` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-post-file-api-path';
|
||||
final file = MultipartFile.fromBytes([]);
|
||||
|
||||
final error = throwableError(
|
||||
path,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.post(
|
||||
path,
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.postFile(path, file);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, StreamChatNetworkError.fromDioError(error));
|
||||
}
|
||||
|
||||
verify(() => dio.post(
|
||||
path,
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.request` should return response successfully', () async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-request-api-path';
|
||||
when(() => dio.request(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenAnswer((_) async => successResponse(path));
|
||||
|
||||
final res = await client.request(path);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.statusCode, 200);
|
||||
expect(res.requestOptions.path, path);
|
||||
|
||||
verify(() => dio.request(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.request` should throw an instance of `StreamChatNetworkError`',
|
||||
() async {
|
||||
final dio = MockDio();
|
||||
final client = StreamHttpClient('api-key', dio: dio);
|
||||
|
||||
const path = 'test-put-api-path';
|
||||
final error = throwableError(
|
||||
path,
|
||||
streamChatDioError: true,
|
||||
error: StreamChatNetworkError(ChatErrorCode.internalSystemError),
|
||||
);
|
||||
when(() => dio.request(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).thenThrow(error);
|
||||
|
||||
try {
|
||||
await client.request(path);
|
||||
} catch (e) {
|
||||
expect(e, isA<StreamChatNetworkError>());
|
||||
expect(e, error.error);
|
||||
}
|
||||
|
||||
verify(() => dio.request(
|
||||
path,
|
||||
options: any(named: 'options'),
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(dio);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'package:stream_chat/src/core/http/token.dart';
|
||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late TokenManager tokenManager;
|
||||
|
||||
setUp(() {
|
||||
tokenManager = TokenManager();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tokenManager.reset();
|
||||
});
|
||||
|
||||
test('`setTokenOrProvider` should set token', () async {
|
||||
expect(tokenManager.userId, isNull);
|
||||
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
final returnedToken = await tokenManager.setTokenOrProvider(
|
||||
userId,
|
||||
token: token,
|
||||
);
|
||||
|
||||
expect(returnedToken, token);
|
||||
expect(tokenManager.userId, userId);
|
||||
expect(tokenManager.isStatic, isTrue);
|
||||
});
|
||||
|
||||
test('`setTokenOrProvider` should set tokenProvider', () async {
|
||||
expect(tokenManager.userId, isNull);
|
||||
|
||||
const userId = 'test-user-id';
|
||||
Future<String> tokenProvider(String userId) async =>
|
||||
Token.development(userId).rawValue;
|
||||
final returnedToken = await tokenManager.setTokenOrProvider(
|
||||
userId,
|
||||
provider: tokenProvider,
|
||||
);
|
||||
|
||||
expect(returnedToken, isNotNull);
|
||||
expect(tokenManager.userId, userId);
|
||||
expect(tokenManager.isStatic, isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'`setTokenOrProvider` should throw if both token and provider is not provided',
|
||||
() async {
|
||||
expect(tokenManager.userId, isNull);
|
||||
|
||||
const userId = 'test-user-id';
|
||||
try {
|
||||
await tokenManager.setTokenOrProvider(userId);
|
||||
} catch (e) {
|
||||
expect(e, isA<AssertionError>());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`setTokenOrProvider` should throw if both token and provider is provided',
|
||||
() async {
|
||||
expect(tokenManager.userId, isNull);
|
||||
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
Future<String> tokenProvider(String userId) async =>
|
||||
Token.development(userId).rawValue;
|
||||
try {
|
||||
await tokenManager.setTokenOrProvider(
|
||||
userId,
|
||||
token: token,
|
||||
provider: tokenProvider,
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e, isA<AssertionError>());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`.loadToken` should return token set via `setToken`',
|
||||
() async {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
await tokenManager.setTokenOrProvider(userId, token: token);
|
||||
|
||||
final returnedToken = await tokenManager.loadToken();
|
||||
expect(returnedToken, token);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`.loadToken` should return token set via `setProvider`',
|
||||
() async {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
Future<String> tokenProvider(String userId) async => token.rawValue;
|
||||
await tokenManager.setTokenOrProvider(userId, provider: tokenProvider);
|
||||
|
||||
final returnedToken = await tokenManager.loadToken();
|
||||
expect(returnedToken, token);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'`.loadToken` should return refreshed token set via `setProvider`',
|
||||
() async {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
final refreshToken = Token.development(userId);
|
||||
|
||||
var refresh = false;
|
||||
|
||||
Future<String> tokenProvider(String userId) async {
|
||||
if (refresh) return refreshToken.rawValue;
|
||||
return token.rawValue;
|
||||
}
|
||||
|
||||
await tokenManager.setTokenOrProvider(userId, provider: tokenProvider);
|
||||
|
||||
final returnedToken = await tokenManager.loadToken();
|
||||
expect(returnedToken, token);
|
||||
|
||||
refresh = true;
|
||||
final returnedRefreshToken = await tokenManager.loadToken(refresh: true);
|
||||
expect(returnedRefreshToken, refreshToken);
|
||||
},
|
||||
);
|
||||
|
||||
test('`.reset` should reset the tokenManager', () async {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
await tokenManager.setTokenOrProvider(userId, token: token);
|
||||
expect(tokenManager.userId, userId);
|
||||
|
||||
tokenManager.reset();
|
||||
expect(tokenManager.userId, isNull);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:stream_chat/src/core/http/token.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('`.anonymous` should create anonymous token with passed userId', () {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.anonymous(userId: userId);
|
||||
expect(token, isNotNull);
|
||||
expect(token.userId, userId);
|
||||
expect(token.rawValue, isEmpty);
|
||||
expect(token.authType, AuthType.anonymous);
|
||||
expect(token.authType.raw, AuthType.anonymous.raw);
|
||||
});
|
||||
|
||||
test('`.fromRawValue` should create token from rawValue', () {
|
||||
const userId = 'test-user-id';
|
||||
final devToken = Token.development(userId);
|
||||
final token = Token.fromRawValue(devToken.rawValue);
|
||||
expect(token, devToken);
|
||||
});
|
||||
|
||||
test('`.fromRawValue` should throw if does not contain `user_id`', () {
|
||||
const badToken = 'bad-token-without-a-user-id';
|
||||
try {
|
||||
Token.fromRawValue(badToken);
|
||||
} catch (e) {
|
||||
expect(e, isA<ArgumentError>());
|
||||
}
|
||||
});
|
||||
|
||||
test('`.development` should create a dev-token with provided user-id', () {
|
||||
const userId = 'test-user-id';
|
||||
final token = Token.development(userId);
|
||||
expect(token, isNotNull);
|
||||
expect(token.userId, userId);
|
||||
expect(token.rawValue, isNotEmpty);
|
||||
expect(token.authType, AuthType.jwt);
|
||||
expect(token.authType.raw, AuthType.jwt.raw);
|
||||
});
|
||||
|
||||
test(
|
||||
'`.guest` should create a guest-token with provided user and provider',
|
||||
() async {
|
||||
final user = User(id: 'test-user-id');
|
||||
Future<String> provider(User user) async =>
|
||||
Token.development(user.id).rawValue;
|
||||
|
||||
final token = await Token.guest(user, provider);
|
||||
expect(token, isNotNull);
|
||||
expect(token.userId, user.id);
|
||||
expect(token.rawValue, isNotEmpty);
|
||||
expect(token.authType, AuthType.jwt);
|
||||
expect(token.authType.raw, AuthType.jwt.raw);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user