mirror of https://github.com/grpc/grpc-dart.git
Add basic server tests. (#27)
This commit is contained in:
parent
0a0a9ffc89
commit
1d12c5b87d
|
@ -11,7 +11,6 @@ dependencies:
|
||||||
grpc:
|
grpc:
|
||||||
path: ../../
|
path: ../../
|
||||||
protobuf: ^0.5.4
|
protobuf: ^0.5.4
|
||||||
http2: ^0.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^0.12.0
|
test: ^0.12.0
|
||||||
|
|
|
@ -11,7 +11,6 @@ dependencies:
|
||||||
grpc:
|
grpc:
|
||||||
path: ../../
|
path: ../../
|
||||||
protobuf: ^0.5.4
|
protobuf: ^0.5.4
|
||||||
http2: ^0.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^0.12.0
|
test: ^0.12.0
|
||||||
|
|
|
@ -12,7 +12,6 @@ dependencies:
|
||||||
grpc:
|
grpc:
|
||||||
path: ../
|
path: ../
|
||||||
protobuf: ^0.5.4
|
protobuf: ^0.5.4
|
||||||
http2: ^0.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^0.12.0
|
test: ^0.12.0
|
||||||
|
|
|
@ -139,18 +139,17 @@ class ClientCall<Q, R> implements Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> _initiateCall() async {
|
static List<Header> createCallHeaders(String path, String authority,
|
||||||
final connection = await _channel.connect();
|
{String timeout, Map<String, String> metadata}) {
|
||||||
final timeout = options?.timeout ?? _channel.options?.timeout;
|
|
||||||
// TODO(jakobr): Populate HTTP-specific headers in connection?
|
// TODO(jakobr): Populate HTTP-specific headers in connection?
|
||||||
final headers = <Header>[
|
final headers = <Header>[
|
||||||
_methodPost,
|
_methodPost,
|
||||||
_schemeHttp,
|
_schemeHttp,
|
||||||
new Header.ascii(':path', _method.path),
|
new Header.ascii(':path', path),
|
||||||
new Header.ascii(':authority', _channel.host),
|
new Header.ascii(':authority', authority),
|
||||||
];
|
];
|
||||||
if (timeout != null) {
|
if (timeout != null) {
|
||||||
headers.add(new Header.ascii('grpc-timeout', toTimeoutString(timeout)));
|
headers.add(new Header.ascii('grpc-timeout', timeout));
|
||||||
}
|
}
|
||||||
headers.addAll([
|
headers.addAll([
|
||||||
_contentTypeGrpc,
|
_contentTypeGrpc,
|
||||||
|
@ -158,14 +157,23 @@ class ClientCall<Q, R> implements Response {
|
||||||
_grpcAcceptEncoding,
|
_grpcAcceptEncoding,
|
||||||
_userAgent,
|
_userAgent,
|
||||||
]);
|
]);
|
||||||
|
metadata?.forEach((key, value) {
|
||||||
|
headers.add(new Header.ascii(key, value));
|
||||||
|
});
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Null> _initiateCall() async {
|
||||||
|
final connection = await _channel.connect();
|
||||||
|
final timeout = options?.timeout ?? _channel.options?.timeout;
|
||||||
|
final timeoutString = timeout != null ? toTimeoutString(timeout) : null;
|
||||||
// TODO(jakobr): Flip this around, and have the Channel create the call
|
// TODO(jakobr): Flip this around, and have the Channel create the call
|
||||||
// object and apply options (including the above TODO).
|
// object and apply options (including the above TODO).
|
||||||
final customMetadata = <String, String>{};
|
final customMetadata = <String, String>{};
|
||||||
customMetadata.addAll(_channel.options?.metadata ?? {});
|
customMetadata.addAll(_channel.options?.metadata ?? {});
|
||||||
customMetadata.addAll(options?.metadata ?? {});
|
customMetadata.addAll(options?.metadata ?? {});
|
||||||
customMetadata.forEach((key, value) {
|
final headers = createCallHeaders(_method.path, _channel.host,
|
||||||
headers.add(new Header.ascii(key, value));
|
timeout: timeoutString, metadata: customMetadata);
|
||||||
});
|
|
||||||
_stream = connection.makeRequest(headers);
|
_stream = connection.makeRequest(headers);
|
||||||
_requests.stream
|
_requests.stream
|
||||||
.map(_method.requestSerializer)
|
.map(_method.requestSerializer)
|
||||||
|
|
|
@ -76,9 +76,7 @@ class Server {
|
||||||
_server.listen((socket) {
|
_server.listen((socket) {
|
||||||
final connection = new ServerTransportConnection.viaSocket(socket);
|
final connection = new ServerTransportConnection.viaSocket(socket);
|
||||||
_connections.add(connection);
|
_connections.add(connection);
|
||||||
connection.incomingStreams.listen((stream) {
|
connection.incomingStreams.listen(serveStream, onError: (error) {
|
||||||
new ServerHandler(lookupService, stream).handle();
|
|
||||||
}, onError: (error) {
|
|
||||||
print('Connection error: $error');
|
print('Connection error: $error');
|
||||||
}, onDone: () {
|
}, onDone: () {
|
||||||
_connections.remove(connection);
|
_connections.remove(connection);
|
||||||
|
@ -88,6 +86,10 @@ class Server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void serveStream(ServerTransportStream stream) {
|
||||||
|
new ServerHandler(lookupService, stream).handle();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Null> shutdown() {
|
Future<Null> shutdown() {
|
||||||
final done = _connections.map((connection) => connection.finish()).toList();
|
final done = _connections.map((connection) => connection.finish()).toList();
|
||||||
if (_server != null) {
|
if (_server != null) {
|
||||||
|
@ -198,6 +200,7 @@ class ServerHandler {
|
||||||
void _onDataIdle(GrpcMessage message) {
|
void _onDataIdle(GrpcMessage message) {
|
||||||
if (message is! GrpcMetadata) {
|
if (message is! GrpcMetadata) {
|
||||||
_sendError(new GrpcError.unimplemented('Expected header frame'));
|
_sendError(new GrpcError.unimplemented('Expected header frame'));
|
||||||
|
_sinkIncoming();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final headerMessage = message
|
final headerMessage = message
|
||||||
|
@ -222,6 +225,28 @@ class ServerHandler {
|
||||||
_startStreamingRequest();
|
_startStreamingRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<T> _toSingleFuture<T>(Stream<T> stream) {
|
||||||
|
T _ensureOnlyOneRequest(T previous, T element) {
|
||||||
|
if (previous != null) {
|
||||||
|
throw new GrpcError.unimplemented('More than one request received');
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
T _ensureOneRequest(T value) {
|
||||||
|
if (value == null)
|
||||||
|
throw new GrpcError.unimplemented('No requests received');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
final future =
|
||||||
|
stream.fold(null, _ensureOnlyOneRequest).then(_ensureOneRequest);
|
||||||
|
// Make sure errors on the future aren't unhandled, but return the original
|
||||||
|
// future so the request handler can also get the error.
|
||||||
|
future.catchError((_) {});
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
void _startStreamingRequest() {
|
void _startStreamingRequest() {
|
||||||
_incomingSubscription.pause();
|
_incomingSubscription.pause();
|
||||||
_requests = new StreamController(
|
_requests = new StreamController(
|
||||||
|
@ -236,17 +261,20 @@ class ServerHandler {
|
||||||
if (_descriptor.streamingRequest) {
|
if (_descriptor.streamingRequest) {
|
||||||
_responses = _descriptor.handler(context, _requests.stream);
|
_responses = _descriptor.handler(context, _requests.stream);
|
||||||
} else {
|
} else {
|
||||||
_responses = _descriptor.handler(context, _requests.stream.single);
|
_responses =
|
||||||
|
_descriptor.handler(context, _toSingleFuture(_requests.stream));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Future response;
|
Future response;
|
||||||
if (_descriptor.streamingRequest) {
|
if (_descriptor.streamingRequest) {
|
||||||
response = _descriptor.handler(context, _requests.stream);
|
response = _descriptor.handler(context, _requests.stream);
|
||||||
} else {
|
} else {
|
||||||
response = _descriptor.handler(context, _requests.stream.single);
|
response =
|
||||||
|
_descriptor.handler(context, _toSingleFuture(_requests.stream));
|
||||||
}
|
}
|
||||||
_responses = response.asStream();
|
_responses = response.asStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
_responseSubscription = _responses.listen(_onResponse,
|
_responseSubscription = _responses.listen(_onResponse,
|
||||||
onError: _onResponseError,
|
onError: _onResponseError,
|
||||||
onDone: _onResponseDone,
|
onDone: _onResponseDone,
|
||||||
|
@ -259,9 +287,10 @@ class ServerHandler {
|
||||||
|
|
||||||
void _onDataActive(GrpcMessage message) {
|
void _onDataActive(GrpcMessage message) {
|
||||||
if (message is! GrpcData) {
|
if (message is! GrpcData) {
|
||||||
_sendError(new GrpcError.unimplemented('Expected data frame'));
|
final error = new GrpcError.unimplemented('Expected request');
|
||||||
|
_sendError(error);
|
||||||
_requests
|
_requests
|
||||||
..addError(new GrpcError.unimplemented('No request received'))
|
..addError(error)
|
||||||
..close();
|
..close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -272,6 +301,7 @@ class ServerHandler {
|
||||||
_requests
|
_requests
|
||||||
..addError(error)
|
..addError(error)
|
||||||
..close();
|
..close();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jakobr): Cast should not be necessary here.
|
// TODO(jakobr): Cast should not be necessary here.
|
||||||
|
@ -296,10 +326,10 @@ class ServerHandler {
|
||||||
|
|
||||||
void _onResponse(response) {
|
void _onResponse(response) {
|
||||||
try {
|
try {
|
||||||
|
final bytes = _descriptor.responseSerializer(response);
|
||||||
if (!_headersSent) {
|
if (!_headersSent) {
|
||||||
_sendHeaders();
|
_sendHeaders();
|
||||||
}
|
}
|
||||||
final bytes = _descriptor.responseSerializer(response);
|
|
||||||
_stream.sendData(GrpcHttpEncoder.frame(bytes));
|
_stream.sendData(GrpcHttpEncoder.frame(bytes));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
final grpcError =
|
final grpcError =
|
||||||
|
@ -312,7 +342,6 @@ class ServerHandler {
|
||||||
}
|
}
|
||||||
_sendError(grpcError);
|
_sendError(grpcError);
|
||||||
_cancelResponseSubscription();
|
_cancelResponseSubscription();
|
||||||
_sinkIncoming();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,6 +359,7 @@ class ServerHandler {
|
||||||
|
|
||||||
void _sendHeaders() {
|
void _sendHeaders() {
|
||||||
if (_headersSent) throw new GrpcError.internal('Headers already sent');
|
if (_headersSent) throw new GrpcError.internal('Headers already sent');
|
||||||
|
|
||||||
final headersMap = <String, String>{};
|
final headersMap = <String, String>{};
|
||||||
headersMap.addAll(_customHeaders);
|
headersMap.addAll(_customHeaders);
|
||||||
_customHeaders = null;
|
_customHeaders = null;
|
||||||
|
@ -369,6 +399,7 @@ class ServerHandler {
|
||||||
_stream.sendHeaders(trailers, endStream: true);
|
_stream.sendHeaders(trailers, endStream: true);
|
||||||
// We're done!
|
// We're done!
|
||||||
_cancelResponseSubscription();
|
_cancelResponseSubscription();
|
||||||
|
_sinkIncoming();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- All states, incoming error / stream closed --
|
// -- All states, incoming error / stream closed --
|
||||||
|
@ -381,6 +412,8 @@ class ServerHandler {
|
||||||
_requests.addError(new GrpcError.cancelled('Cancelled'));
|
_requests.addError(new GrpcError.cancelled('Cancelled'));
|
||||||
}
|
}
|
||||||
_cancelResponseSubscription();
|
_cancelResponseSubscription();
|
||||||
|
_incomingSubscription.cancel();
|
||||||
|
_stream.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDoneError() {
|
void _onDoneError() {
|
||||||
|
@ -390,7 +423,7 @@ class ServerHandler {
|
||||||
|
|
||||||
void _onDoneExpected() {
|
void _onDoneExpected() {
|
||||||
if (!(_hasReceivedRequest || _descriptor.streamingRequest)) {
|
if (!(_hasReceivedRequest || _descriptor.streamingRequest)) {
|
||||||
final error = new GrpcError.unimplemented('Expected request message');
|
final error = new GrpcError.unimplemented('No request received');
|
||||||
_sendError(error);
|
_sendError(error);
|
||||||
_requests.addError(error);
|
_requests.addError(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
async: ^1.13.3
|
async: ^1.13.3
|
||||||
meta: ^1.0.5
|
meta: ^1.0.5
|
||||||
http2: ^0.1.2
|
http2: ^0.1.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
mockito: ^2.0.2
|
mockito: ^2.0.2
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:grpc/src/status.dart';
|
import 'package:grpc/src/status.dart';
|
||||||
import 'package:grpc/src/streams.dart';
|
|
||||||
import 'package:http2/transport.dart';
|
import 'package:http2/transport.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import 'src/client_utils.dart';
|
import 'src/client_utils.dart';
|
||||||
|
import 'src/utils.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const dummyValue = 0;
|
const dummyValue = 0;
|
||||||
|
@ -25,14 +25,12 @@ void main() {
|
||||||
harness.tearDown();
|
harness.tearDown();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unary calls work end-to-end', () async {
|
test('Unary calls work on the client', () async {
|
||||||
const requestValue = 17;
|
const requestValue = 17;
|
||||||
const responseValue = 19;
|
const responseValue = 19;
|
||||||
|
|
||||||
void _handleRequest(StreamMessage message) {
|
void handleRequest(StreamMessage message) {
|
||||||
expect(message, new isInstanceOf<DataStreamMessage>());
|
final data = validateDataMessage(message);
|
||||||
expect(message.endStream, false);
|
|
||||||
final data = new GrpcHttpDecoder().convert(message) as GrpcData;
|
|
||||||
expect(mockDecode(data.data), requestValue);
|
expect(mockDecode(data.data), requestValue);
|
||||||
|
|
||||||
harness
|
harness
|
||||||
|
@ -45,20 +43,18 @@ void main() {
|
||||||
clientCall: harness.client.unary(requestValue),
|
clientCall: harness.client.unary(requestValue),
|
||||||
expectedResult: responseValue,
|
expectedResult: responseValue,
|
||||||
expectedPath: '/Test/Unary',
|
expectedPath: '/Test/Unary',
|
||||||
serverHandlers: [_handleRequest],
|
serverHandlers: [handleRequest],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Client-streaming calls work end-to-end', () async {
|
test('Client-streaming calls work on the client', () async {
|
||||||
const requests = const [17, 3];
|
const requests = const [17, 3];
|
||||||
const response = 12;
|
const response = 12;
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
void handleRequest(StreamMessage message) {
|
void handleRequest(StreamMessage message) {
|
||||||
expect(message, new isInstanceOf<DataStreamMessage>());
|
final data = validateDataMessage(message);
|
||||||
expect(message.endStream, false);
|
|
||||||
final data = new GrpcHttpDecoder().convert(message) as GrpcData;
|
|
||||||
expect(mockDecode(data.data), requests[index++]);
|
expect(mockDecode(data.data), requests[index++]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,14 +75,12 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Server-streaming calls work end-to-end', () async {
|
test('Server-streaming calls work on the client', () async {
|
||||||
const request = 4;
|
const request = 4;
|
||||||
const responses = const [3, 17, 9];
|
const responses = const [3, 17, 9];
|
||||||
|
|
||||||
void handleRequest(StreamMessage message) {
|
void handleRequest(StreamMessage message) {
|
||||||
expect(message, new isInstanceOf<DataStreamMessage>());
|
final data = validateDataMessage(message);
|
||||||
expect(message.endStream, false);
|
|
||||||
final data = new GrpcHttpDecoder().convert(message) as GrpcData;
|
|
||||||
expect(mockDecode(data.data), request);
|
expect(mockDecode(data.data), request);
|
||||||
|
|
||||||
harness.sendResponseHeader();
|
harness.sendResponseHeader();
|
||||||
|
@ -102,16 +96,14 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Bidirectional calls work end-to-end', () async {
|
test('Bidirectional calls work on the client', () async {
|
||||||
const requests = const [1, 15, 7];
|
const requests = const [1, 15, 7];
|
||||||
const responses = const [3, 17, 9];
|
const responses = const [3, 17, 9];
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
void handleRequest(StreamMessage message) {
|
void handleRequest(StreamMessage message) {
|
||||||
expect(message, new isInstanceOf<DataStreamMessage>());
|
final data = validateDataMessage(message);
|
||||||
expect(message.endStream, false);
|
|
||||||
final data = new GrpcHttpDecoder().convert(message) as GrpcData;
|
|
||||||
expect(mockDecode(data.data), requests[index]);
|
expect(mockDecode(data.data), requests[index]);
|
||||||
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:grpc/grpc.dart';
|
||||||
|
import 'package:grpc/src/status.dart';
|
||||||
|
import 'package:http2/transport.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/server_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const dummyValue = 17;
|
||||||
|
|
||||||
|
ServerHarness harness;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
harness = new ServerHarness()..setUp();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
harness.tearDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Unary calls work on the server', () async {
|
||||||
|
const expectedRequest = 5;
|
||||||
|
const expectedResponse = 7;
|
||||||
|
Future<int> methodHandler(ServiceCall call, Future<int> request) async {
|
||||||
|
expect(await request, expectedRequest);
|
||||||
|
return expectedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.unaryHandler = methodHandler
|
||||||
|
..runTest('/Test/Unary', [expectedRequest], [expectedResponse]);
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Client-streaming calls work on the server', () async {
|
||||||
|
const expectedRequests = const [5, 3, 17];
|
||||||
|
const expectedResponse = 12;
|
||||||
|
Future<int> methodHandler(ServiceCall call, Stream<int> request) async {
|
||||||
|
expect(await request.toList(), expectedRequests);
|
||||||
|
return expectedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.clientStreamingHandler = methodHandler
|
||||||
|
..runTest('/Test/ClientStreaming', expectedRequests, [expectedResponse]);
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server-streaming calls work on the server', () async {
|
||||||
|
const expectedRequest = 5;
|
||||||
|
const expectedResponses = const [7, 9, 1];
|
||||||
|
|
||||||
|
Stream<int> methodHandler(ServiceCall call, Future<int> request) async* {
|
||||||
|
expect(await request, expectedRequest);
|
||||||
|
for (final value in expectedResponses) {
|
||||||
|
yield value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.serverStreamingHandler = methodHandler
|
||||||
|
..runTest('/Test/ServerStreaming', [expectedRequest], expectedResponses);
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bidirectional calls work on the server', () async {
|
||||||
|
const expectedRequests = const [3, 1, 7];
|
||||||
|
final expectedResponses = expectedRequests.map((v) => v + 5).toList();
|
||||||
|
|
||||||
|
Stream<int> methodHandler(ServiceCall call, Stream<int> request) async* {
|
||||||
|
yield* request.map((value) => value + 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.bidirectionalHandler = methodHandler
|
||||||
|
..runTest('/Test/Bidirectional', expectedRequests, expectedResponses);
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns error on missing call header', () async {
|
||||||
|
harness
|
||||||
|
..expectErrorResponse(StatusCode.unimplemented, 'Expected header frame')
|
||||||
|
..sendData(dummyValue);
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns error on invalid path', () async {
|
||||||
|
harness
|
||||||
|
..expectErrorResponse(StatusCode.unimplemented, 'Invalid path')
|
||||||
|
..sendRequestHeader('InvalidPath');
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns error on unimplemented path', () async {
|
||||||
|
harness
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.unimplemented, 'Path /Test/NotFound not found')
|
||||||
|
..sendRequestHeader('/Test/NotFound');
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Returns a service method handler that verifies that awaiting the request
|
||||||
|
/// throws a specific error.
|
||||||
|
Future<int> Function(ServiceCall call, Future<int> request) expectError(
|
||||||
|
expectedError) {
|
||||||
|
return expectAsync2((ServiceCall call, Future<int> request) async {
|
||||||
|
try {
|
||||||
|
final result = await request;
|
||||||
|
registerException('Did not throw');
|
||||||
|
return result;
|
||||||
|
} catch (caughtError) {
|
||||||
|
try {
|
||||||
|
expect(caughtError, expectedError);
|
||||||
|
} catch (failure, stack) {
|
||||||
|
registerException(failure, stack);
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}, count: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a service method handler that verifies that awaiting the request
|
||||||
|
/// stream throws a specific error.
|
||||||
|
Stream<int> Function(ServiceCall call, Stream<int> request)
|
||||||
|
expectErrorStreaming(expectedError) {
|
||||||
|
return (ServiceCall call, Stream<int> request) async* {
|
||||||
|
try {
|
||||||
|
await for (var entry in request) {
|
||||||
|
yield entry;
|
||||||
|
}
|
||||||
|
registerException('Did not throw');
|
||||||
|
} catch (caughtError) {
|
||||||
|
try {
|
||||||
|
expect(caughtError, expectedError);
|
||||||
|
} catch (failure, stack) {
|
||||||
|
registerException(failure, stack);
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Server returns error on missing request for unary call', () async {
|
||||||
|
harness
|
||||||
|
..service.unaryHandler =
|
||||||
|
expectError(new GrpcError.unimplemented('No request received'))
|
||||||
|
..expectErrorResponse(StatusCode.unimplemented, 'No request received')
|
||||||
|
..sendRequestHeader('/Test/Unary')
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns error if multiple headers are received for unary call',
|
||||||
|
() async {
|
||||||
|
harness
|
||||||
|
..service.unaryHandler =
|
||||||
|
expectError(new GrpcError.unimplemented('Expected request'))
|
||||||
|
..expectErrorResponse(StatusCode.unimplemented, 'Expected request')
|
||||||
|
..sendRequestHeader('/Test/Unary')
|
||||||
|
..toServer.add(new HeadersStreamMessage([]))
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns error on too many requests for unary call', () async {
|
||||||
|
harness
|
||||||
|
..service.unaryHandler =
|
||||||
|
expectError(new GrpcError.unimplemented('Too many requests'))
|
||||||
|
..expectErrorResponse(StatusCode.unimplemented, 'Too many requests')
|
||||||
|
..sendRequestHeader('/Test/Unary')
|
||||||
|
..sendData(dummyValue)
|
||||||
|
..sendData(dummyValue)
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns request deserialization errors', () async {
|
||||||
|
harness
|
||||||
|
..service.bidirectionalHandler = expectErrorStreaming(
|
||||||
|
new GrpcError.internal('Error deserializing request: Failed'))
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.internal, 'Error deserializing request: Failed')
|
||||||
|
..sendRequestHeader('/Test/RequestError')
|
||||||
|
..sendData(dummyValue)
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server returns response serialization errors', () async {
|
||||||
|
harness
|
||||||
|
..service.bidirectionalHandler = expectErrorStreaming(
|
||||||
|
new GrpcError.internal('Error sending response: Failed'))
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.internal, 'Error sending response: Failed')
|
||||||
|
..sendRequestHeader('/Test/ResponseError')
|
||||||
|
..sendData(dummyValue)
|
||||||
|
..sendData(dummyValue)
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Header can only be sent once', () async {
|
||||||
|
Future<int> methodHandler(ServiceCall call, Future<int> request) async {
|
||||||
|
call.sendHeaders();
|
||||||
|
call.sendHeaders();
|
||||||
|
return await request;
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.unaryHandler = methodHandler
|
||||||
|
..expectTrailingErrorResponse(StatusCode.internal, 'Headers already sent')
|
||||||
|
..sendRequestHeader('/Test/Unary');
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Server receives cancel', () async {
|
||||||
|
final success = new Completer<bool>();
|
||||||
|
|
||||||
|
Future<int> methodHandler(ServiceCall call, Future<int> request) async {
|
||||||
|
try {
|
||||||
|
final result = await request;
|
||||||
|
registerException('Did not throw');
|
||||||
|
return result;
|
||||||
|
} catch (caughtError) {
|
||||||
|
try {
|
||||||
|
expect(caughtError, new GrpcError.cancelled('Cancelled'));
|
||||||
|
expect(call.isCanceled, isTrue);
|
||||||
|
success.complete(true);
|
||||||
|
} catch (failure, stack) {
|
||||||
|
registerException(failure, stack);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!success.isCompleted) {
|
||||||
|
success.complete(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dummyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..service.unaryHandler = methodHandler
|
||||||
|
..fromServer.stream.listen(expectAsync1((_) {}, count: 0),
|
||||||
|
onError: expectAsync1((error) {
|
||||||
|
expect(error, 'TERMINATED');
|
||||||
|
}, count: 1),
|
||||||
|
onDone: expectAsync0(() {}, count: 1))
|
||||||
|
..sendRequestHeader('/Test/Unary')
|
||||||
|
..toServer.addError('CANCEL');
|
||||||
|
|
||||||
|
expect(await success.future, isTrue);
|
||||||
|
await harness.toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Server returns error if request stream is closed before sending anything',
|
||||||
|
() async {
|
||||||
|
harness
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.unavailable, 'Request stream closed unexpectedly')
|
||||||
|
..toServer.close();
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:grpc/src/streams.dart';
|
import 'package:grpc/src/streams.dart';
|
||||||
import 'package:http2/transport.dart';
|
import 'package:http2/transport.dart';
|
||||||
|
@ -12,6 +11,8 @@ import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
|
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
class MockConnection extends Mock implements ClientTransportConnection {}
|
class MockConnection extends Mock implements ClientTransportConnection {}
|
||||||
|
|
||||||
class MockStream extends Mock implements ClientTransportStream {}
|
class MockStream extends Mock implements ClientTransportStream {}
|
||||||
|
@ -20,9 +21,6 @@ class MockChannel extends Mock implements ClientChannel {}
|
||||||
|
|
||||||
typedef ServerMessageHandler = void Function(StreamMessage message);
|
typedef ServerMessageHandler = void Function(StreamMessage message);
|
||||||
|
|
||||||
List<int> mockEncode(int value) => new List.filled(value, 0);
|
|
||||||
int mockDecode(List<int> value) => value.length;
|
|
||||||
|
|
||||||
class TestClient {
|
class TestClient {
|
||||||
final ClientChannel _channel;
|
final ClientChannel _channel;
|
||||||
|
|
||||||
|
@ -73,8 +71,8 @@ class ClientHarness {
|
||||||
MockChannel channel;
|
MockChannel channel;
|
||||||
MockStream stream;
|
MockStream stream;
|
||||||
|
|
||||||
StreamController fromClient;
|
StreamController<StreamMessage> fromClient;
|
||||||
StreamController toClient;
|
StreamController<StreamMessage> toClient;
|
||||||
|
|
||||||
TestClient client;
|
TestClient client;
|
||||||
|
|
||||||
|
@ -112,33 +110,12 @@ class ClientHarness {
|
||||||
if (closeStream) toClient.close();
|
if (closeStream) toClient.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateHeaders(List<Header> headers,
|
|
||||||
{String path, Map<String, String> customHeaders}) {
|
|
||||||
final headerMap = new Map.fromIterable(headers,
|
|
||||||
key: (h) => ASCII.decode(h.name), value: (h) => ASCII.decode(h.value));
|
|
||||||
expect(headerMap[':method'], 'POST');
|
|
||||||
expect(headerMap[':scheme'], 'http');
|
|
||||||
if (path != null) {
|
|
||||||
expect(headerMap[':path'], path);
|
|
||||||
}
|
|
||||||
expect(headerMap[':authority'], 'test');
|
|
||||||
expect(headerMap['grpc-timeout'], isNull);
|
|
||||||
expect(headerMap['content-type'], 'application/grpc');
|
|
||||||
expect(headerMap['te'], 'trailers');
|
|
||||||
expect(headerMap['grpc-accept-encoding'], 'identity');
|
|
||||||
expect(headerMap['user-agent'], startsWith('dart-grpc/'));
|
|
||||||
|
|
||||||
customHeaders?.forEach((key, value) {
|
|
||||||
expect(headerMap[key], value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Null> runTest(
|
Future<Null> runTest(
|
||||||
{Future clientCall,
|
{Future clientCall,
|
||||||
dynamic expectedResult,
|
dynamic expectedResult,
|
||||||
String expectedPath,
|
String expectedPath,
|
||||||
Map<String, String> expectedCustomHeaders,
|
Map<String, String> expectedCustomHeaders,
|
||||||
List<ServerMessageHandler> serverHandlers = const [],
|
List<MessageHandler> serverHandlers = const [],
|
||||||
Function doneHandler,
|
Function doneHandler,
|
||||||
bool expectDone = true}) async {
|
bool expectDone = true}) async {
|
||||||
int serverHandlerIndex = 0;
|
int serverHandlerIndex = 0;
|
||||||
|
@ -160,7 +137,7 @@ class ClientHarness {
|
||||||
|
|
||||||
final List<Header> capturedHeaders =
|
final List<Header> capturedHeaders =
|
||||||
verify(connection.makeRequest(captureAny)).captured.single;
|
verify(connection.makeRequest(captureAny)).captured.single;
|
||||||
validateHeaders(capturedHeaders,
|
validateRequestHeaders(capturedHeaders,
|
||||||
path: expectedPath, customHeaders: expectedCustomHeaders);
|
path: expectedPath, customHeaders: expectedCustomHeaders);
|
||||||
|
|
||||||
await clientSubscription.cancel();
|
await clientSubscription.cancel();
|
||||||
|
@ -180,7 +157,7 @@ class ClientHarness {
|
||||||
dynamic expectedException,
|
dynamic expectedException,
|
||||||
String expectedPath,
|
String expectedPath,
|
||||||
Map<String, String> expectedCustomHeaders,
|
Map<String, String> expectedCustomHeaders,
|
||||||
List<ServerMessageHandler> serverHandlers = const [],
|
List<MessageHandler> serverHandlers = const [],
|
||||||
bool expectDone = true}) async {
|
bool expectDone = true}) async {
|
||||||
return runTest(
|
return runTest(
|
||||||
clientCall: expectThrows(clientCall, expectedException),
|
clientCall: expectThrows(clientCall, expectedException),
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:grpc/src/streams.dart';
|
||||||
|
import 'package:http2/transport.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'package:grpc/grpc.dart';
|
||||||
|
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
|
class TestService extends Service {
|
||||||
|
@override
|
||||||
|
String get $name => 'Test';
|
||||||
|
|
||||||
|
Future<int> Function(ServiceCall call, Future<int> request) unaryHandler;
|
||||||
|
Future<int> Function(ServiceCall call, Stream<int> request)
|
||||||
|
clientStreamingHandler;
|
||||||
|
Stream<int> Function(ServiceCall call, Future<int> request)
|
||||||
|
serverStreamingHandler;
|
||||||
|
Stream<int> Function(ServiceCall call, Stream<int> request)
|
||||||
|
bidirectionalHandler;
|
||||||
|
|
||||||
|
TestService() {
|
||||||
|
$addMethod(ServerHarness.createMethod('Unary', _unary, false, false));
|
||||||
|
$addMethod(ServerHarness.createMethod(
|
||||||
|
'ClientStreaming', _clientStreaming, true, false));
|
||||||
|
$addMethod(ServerHarness.createMethod(
|
||||||
|
'ServerStreaming', _serverStreaming, false, true));
|
||||||
|
$addMethod(ServerHarness.createMethod(
|
||||||
|
'Bidirectional', _bidirectional, true, true));
|
||||||
|
$addMethod(new ServiceMethod('RequestError', _bidirectional, true, true,
|
||||||
|
(List<int> value) => throw 'Failed', mockEncode));
|
||||||
|
$addMethod(new ServiceMethod('ResponseError', _bidirectional, true, true,
|
||||||
|
mockDecode, (int value) => throw 'Failed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _unary(ServiceCall call, Future<int> request) async {
|
||||||
|
if (unaryHandler == null) {
|
||||||
|
fail('Should not invoke Unary');
|
||||||
|
}
|
||||||
|
return unaryHandler(call, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _clientStreaming(ServiceCall call, Stream<int> request) async {
|
||||||
|
if (clientStreamingHandler == null) {
|
||||||
|
fail('Should not invoke ClientStreaming');
|
||||||
|
}
|
||||||
|
return clientStreamingHandler(call, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int> _serverStreaming(ServiceCall call, Future<int> request) async* {
|
||||||
|
if (serverStreamingHandler == null) {
|
||||||
|
fail('Should not invoke ServerStreaming');
|
||||||
|
}
|
||||||
|
yield* serverStreamingHandler(call, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<int> _bidirectional(ServiceCall call, Stream<int> request) async* {
|
||||||
|
if (bidirectionalHandler == null) {
|
||||||
|
fail('Should not invoke Bidirectional');
|
||||||
|
}
|
||||||
|
yield* bidirectionalHandler(call, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestServerStream extends ServerTransportStream {
|
||||||
|
final Stream<StreamMessage> incomingMessages;
|
||||||
|
final StreamSink<StreamMessage> outgoingMessages;
|
||||||
|
|
||||||
|
TestServerStream(this.incomingMessages, this.outgoingMessages);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id => -1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void terminate() {
|
||||||
|
outgoingMessages.addError('TERMINATED');
|
||||||
|
outgoingMessages.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set onTerminated(void value(int)) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get canPush => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServerTransportStream push(List<Header> requestHeaders) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerHarness {
|
||||||
|
final toServer = new StreamController<StreamMessage>();
|
||||||
|
final fromServer = new StreamController<StreamMessage>();
|
||||||
|
final service = new TestService();
|
||||||
|
final server = new Server();
|
||||||
|
|
||||||
|
static ServiceMethod<int, int> createMethod(String name,
|
||||||
|
Function methodHandler, bool clientStreaming, bool serverStreaming) {
|
||||||
|
return new ServiceMethod<int, int>(name, methodHandler, clientStreaming,
|
||||||
|
serverStreaming, mockDecode, mockEncode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUp() {
|
||||||
|
server.addService(service);
|
||||||
|
final stream = new TestServerStream(toServer.stream, fromServer.sink);
|
||||||
|
server.serveStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown() {
|
||||||
|
fromServer.close();
|
||||||
|
toServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupTest(List<MessageHandler> handlers) {
|
||||||
|
int handlerIndex = 0;
|
||||||
|
void handleMessages(StreamMessage message) {
|
||||||
|
handlers[handlerIndex++](message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromServer.stream.listen(
|
||||||
|
expectAsync1(handleMessages, count: handlers.length),
|
||||||
|
onError: expectAsync1((_) {}, count: 0),
|
||||||
|
onDone: expectAsync0(() {}, count: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectErrorResponse(int status, String message) {
|
||||||
|
setupTest([errorTrailerValidator(status, message, validateHeader: true)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectTrailingErrorResponse(int status, String message) {
|
||||||
|
setupTest([
|
||||||
|
headerValidator(),
|
||||||
|
errorTrailerValidator(status, message, validateHeader: false)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRequestHeader(String path,
|
||||||
|
{String authority = 'test',
|
||||||
|
String timeout,
|
||||||
|
Map<String, String> metadata}) {
|
||||||
|
final headers = ClientCall.createCallHeaders(path, authority,
|
||||||
|
timeout: timeout, metadata: metadata);
|
||||||
|
toServer.add(new HeadersStreamMessage(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendData(int value) {
|
||||||
|
toServer
|
||||||
|
.add(new DataStreamMessage(GrpcHttpEncoder.frame(mockEncode(value))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTest(String path, List<int> requests, List<int> expectedResponses) {
|
||||||
|
void handleHeader(StreamMessage message) {
|
||||||
|
final header = validateMetadataMessage(message);
|
||||||
|
validateResponseHeaders(header.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
int responseIndex = 0;
|
||||||
|
void handleResponse(StreamMessage message) {
|
||||||
|
final response = validateDataMessage(message);
|
||||||
|
expect(mockDecode(response.data), expectedResponses[responseIndex++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTrailer(StreamMessage message) {
|
||||||
|
final trailer = validateMetadataMessage(message, endStream: true);
|
||||||
|
validateResponseTrailers(trailer.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
final handlers = [handleHeader];
|
||||||
|
for (var i = 0; i < expectedResponses.length; i++) {
|
||||||
|
handlers.add(handleResponse);
|
||||||
|
}
|
||||||
|
handlers.add(handleTrailer);
|
||||||
|
|
||||||
|
setupTest(handlers);
|
||||||
|
sendRequestHeader(path);
|
||||||
|
requests.forEach(sendData);
|
||||||
|
toServer.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:grpc/src/streams.dart';
|
||||||
|
import 'package:http2/transport.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
typedef MessageHandler = void Function(StreamMessage message);
|
||||||
|
|
||||||
|
List<int> mockEncode(int value) => new List.filled(value, 0);
|
||||||
|
|
||||||
|
int mockDecode(List<int> value) => value.length;
|
||||||
|
|
||||||
|
Map<String, String> headersToMap(List<Header> headers) =>
|
||||||
|
new Map.fromIterable(headers,
|
||||||
|
key: (h) => ASCII.decode(h.name), value: (h) => ASCII.decode(h.value));
|
||||||
|
|
||||||
|
void validateRequestHeaders(List<Header> headers,
|
||||||
|
{String path,
|
||||||
|
String authority = 'test',
|
||||||
|
String timeout,
|
||||||
|
Map<String, String> customHeaders}) {
|
||||||
|
final headerMap = headersToMap(headers);
|
||||||
|
expect(headerMap[':method'], 'POST');
|
||||||
|
expect(headerMap[':scheme'], 'http');
|
||||||
|
if (path != null) {
|
||||||
|
expect(headerMap[':path'], path);
|
||||||
|
}
|
||||||
|
expect(headerMap[':authority'], authority);
|
||||||
|
expect(headerMap['grpc-timeout'], timeout);
|
||||||
|
expect(headerMap['content-type'], 'application/grpc');
|
||||||
|
expect(headerMap['te'], 'trailers');
|
||||||
|
expect(headerMap['grpc-accept-encoding'], 'identity');
|
||||||
|
expect(headerMap['user-agent'], startsWith('dart-grpc/'));
|
||||||
|
|
||||||
|
customHeaders?.forEach((key, value) {
|
||||||
|
expect(headerMap[key], value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateResponseHeaders(Map<String, String> headers,
|
||||||
|
{int status = 200,
|
||||||
|
bool allowTrailers = false,
|
||||||
|
Map<String, String> customHeaders}) {
|
||||||
|
expect(headers[':status'], '200');
|
||||||
|
expect(headers['content-type'], startsWith('application/grpc'));
|
||||||
|
if (!allowTrailers) {
|
||||||
|
expect(headers.containsKey('grpc-status'), isFalse);
|
||||||
|
expect(headers.containsKey('grpc-message'), isFalse);
|
||||||
|
}
|
||||||
|
customHeaders?.forEach((key, value) {
|
||||||
|
expect(headers[key], value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateResponseTrailers(Map<String, String> trailers,
|
||||||
|
{int status = 0, String message, Map<String, String> customTrailers}) {
|
||||||
|
expect(trailers['grpc-status'], '$status');
|
||||||
|
if (message != null) {
|
||||||
|
expect(trailers['grpc-message'], message);
|
||||||
|
}
|
||||||
|
customTrailers?.forEach((key, value) {
|
||||||
|
expect(trailers[key], value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GrpcMetadata validateMetadataMessage(StreamMessage message,
|
||||||
|
{bool endStream = false}) {
|
||||||
|
expect(message, new isInstanceOf<HeadersStreamMessage>());
|
||||||
|
expect(message.endStream, endStream);
|
||||||
|
|
||||||
|
final decoded = new GrpcHttpDecoder().convert(message);
|
||||||
|
expect(decoded, new isInstanceOf<GrpcMetadata>());
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrpcData validateDataMessage(StreamMessage message, {bool endStream = false}) {
|
||||||
|
expect(message, new isInstanceOf<DataStreamMessage>());
|
||||||
|
expect(message.endStream, endStream);
|
||||||
|
|
||||||
|
final decoded = new GrpcHttpDecoder().convert(message);
|
||||||
|
expect(decoded, new isInstanceOf<GrpcData>());
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Function(StreamMessage message) headerValidator() {
|
||||||
|
return (StreamMessage message) {
|
||||||
|
final header = validateMetadataMessage(message, endStream: false);
|
||||||
|
validateResponseHeaders(header.metadata, allowTrailers: true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Function(StreamMessage message) errorTrailerValidator(
|
||||||
|
int status, String statusMessage,
|
||||||
|
{bool validateHeader = false}) {
|
||||||
|
return (StreamMessage message) {
|
||||||
|
final trailer = validateMetadataMessage(message, endStream: true);
|
||||||
|
if (validateHeader) {
|
||||||
|
validateResponseHeaders(trailer.metadata, allowTrailers: true);
|
||||||
|
}
|
||||||
|
validateResponseTrailers(trailer.metadata,
|
||||||
|
status: status, message: statusMessage);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue