mirror of https://github.com/grpc/grpc-dart.git
Update client_xhr_transport_test to avoid dart:html, updating xhr_transport to support testability
This commit is contained in:
parent
93909b7668
commit
c7b912546a
|
@ -146,6 +146,104 @@ class XhrTransportStream implements GrpcTransportStream {
|
|||
}
|
||||
}
|
||||
|
||||
// XMLHttpRequest is an extension type and can't be extended or implemented.
|
||||
// This interface is used to allow for mocking XMLHttpRequest in tests of
|
||||
// XhrClientConnection.
|
||||
@visibleForTesting
|
||||
abstract interface class IXMLHttpRequest {
|
||||
Stream<Event> get onReadyStateChange;
|
||||
Stream<ProgressEvent> get onProgress;
|
||||
Stream<ProgressEvent> get onError;
|
||||
int get readyState;
|
||||
JSAny? get response;
|
||||
String get responseText;
|
||||
Map<String, String> get responseHeaders;
|
||||
int get status;
|
||||
|
||||
set responseType(String responseType);
|
||||
set withCredentials(bool withCredentials);
|
||||
|
||||
void open(
|
||||
String method,
|
||||
String url, [
|
||||
// external default is true
|
||||
bool async = true,
|
||||
String? username,
|
||||
String? password,
|
||||
]);
|
||||
void overrideMimeType(String mimeType);
|
||||
void send([JSAny? body]);
|
||||
void setRequestHeader(String header, String value);
|
||||
|
||||
// This method should only be used in production code.
|
||||
XMLHttpRequest toXMLHttpRequest();
|
||||
}
|
||||
|
||||
// IXMLHttpRequest that delegates to a real XMLHttpRequest.
|
||||
class XMLHttpRequestImpl implements IXMLHttpRequest {
|
||||
final XMLHttpRequest _xhr = XMLHttpRequest();
|
||||
|
||||
XMLHttpRequestImpl();
|
||||
|
||||
@override
|
||||
Stream<Event> get onReadyStateChange => _xhr.onReadyStateChange;
|
||||
@override
|
||||
Stream<ProgressEvent> get onProgress => _xhr.onProgress;
|
||||
@override
|
||||
Stream<ProgressEvent> get onError => _xhr.onError;
|
||||
@override
|
||||
int get readyState => _xhr.readyState;
|
||||
@override
|
||||
Map<String, String> get responseHeaders => _xhr.responseHeaders;
|
||||
@override
|
||||
JSAny? get response => _xhr.response;
|
||||
@override
|
||||
String get responseText => _xhr.responseText;
|
||||
@override
|
||||
int get status => _xhr.status;
|
||||
|
||||
@override
|
||||
set responseType(String responseType) {
|
||||
_xhr.responseType = responseType;
|
||||
}
|
||||
|
||||
@override
|
||||
set withCredentials(bool withCredentials) {
|
||||
_xhr.withCredentials = withCredentials;
|
||||
}
|
||||
|
||||
@override
|
||||
void open(
|
||||
String method,
|
||||
String url, [
|
||||
bool async = true,
|
||||
String? username,
|
||||
String? password,
|
||||
]) {
|
||||
_xhr.open(method, url, async, username, password);
|
||||
}
|
||||
|
||||
@override
|
||||
void overrideMimeType(String mimeType) {
|
||||
_xhr.overrideMimeType(mimeType);
|
||||
}
|
||||
|
||||
@override
|
||||
void setRequestHeader(String header, String value) {
|
||||
_xhr.setRequestHeader(header, value);
|
||||
}
|
||||
|
||||
@override
|
||||
void send([JSAny? body]) {
|
||||
_xhr.send(body);
|
||||
}
|
||||
|
||||
@override
|
||||
XMLHttpRequest toXMLHttpRequest() {
|
||||
return _xhr;
|
||||
}
|
||||
}
|
||||
|
||||
class XhrClientConnection implements ClientConnection {
|
||||
final Uri uri;
|
||||
|
||||
|
@ -160,7 +258,7 @@ class XhrClientConnection implements ClientConnection {
|
|||
String get scheme => uri.scheme;
|
||||
|
||||
void _initializeRequest(
|
||||
XMLHttpRequest request, Map<String, String> metadata) {
|
||||
IXMLHttpRequest request, Map<String, String> metadata) {
|
||||
metadata.forEach(request.setRequestHeader);
|
||||
// Overriding the mimetype allows us to stream and parse the data
|
||||
request.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
|
@ -168,7 +266,7 @@ class XhrClientConnection implements ClientConnection {
|
|||
}
|
||||
|
||||
@visibleForTesting
|
||||
XMLHttpRequest createHttpRequest() => XMLHttpRequest();
|
||||
IXMLHttpRequest createHttpRequest() => XMLHttpRequestImpl();
|
||||
|
||||
@override
|
||||
GrpcTransportStream makeRequest(String path, Duration? timeout,
|
||||
|
@ -196,11 +294,17 @@ class XhrClientConnection implements ClientConnection {
|
|||
_initializeRequest(request, metadata);
|
||||
|
||||
final transportStream =
|
||||
XhrTransportStream(request, onError: onError, onDone: _removeStream);
|
||||
_createXhrTransportStream(request, onError, _removeStream);
|
||||
_requests.add(transportStream);
|
||||
return transportStream;
|
||||
}
|
||||
|
||||
XhrTransportStream _createXhrTransportStream(IXMLHttpRequest request,
|
||||
ErrorHandler onError, void Function(XhrTransportStream stream) onDone) {
|
||||
return XhrTransportStream(request.toXMLHttpRequest(),
|
||||
onError: onError, onDone: onDone);
|
||||
}
|
||||
|
||||
void _removeStream(XhrTransportStream stream) {
|
||||
_requests.remove(stream);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:grpc/src/client/call.dart';
|
||||
|
@ -26,12 +26,13 @@ import 'package:grpc/src/shared/status.dart';
|
|||
import 'package:mockito/mockito.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web/web.dart';
|
||||
|
||||
final readyStateChangeEvent =
|
||||
Event('readystatechange', canBubble: false, cancelable: false);
|
||||
Event('readystatechange', EventInit(bubbles: false, cancelable: false));
|
||||
final progressEvent = ProgressEvent('onloadstart');
|
||||
|
||||
class MockHttpRequest extends Mock implements HttpRequest {
|
||||
class MockHttpRequest extends Mock implements IXMLHttpRequest {
|
||||
MockHttpRequest({int? code}) : status = code ?? 200;
|
||||
// ignore: close_sinks
|
||||
StreamController<Event> readyStateChangeController =
|
||||
|
@ -52,6 +53,10 @@ class MockHttpRequest extends Mock implements HttpRequest {
|
|||
@override
|
||||
final int status;
|
||||
|
||||
// Some test code expects to call this
|
||||
set readyState(int state);
|
||||
set responseText(String text);
|
||||
|
||||
@override
|
||||
int get readyState =>
|
||||
super.noSuchMethod(Invocation.getter(#readyState), returnValue: -1);
|
||||
|
@ -71,7 +76,7 @@ class MockXhrClientConnection extends XhrClientConnection {
|
|||
final int _statusCode;
|
||||
|
||||
@override
|
||||
HttpRequest createHttpRequest() {
|
||||
IXMLHttpRequest createHttpRequest() {
|
||||
final request = MockHttpRequest(code: _statusCode);
|
||||
latestRequest = request;
|
||||
return request;
|
||||
|
@ -208,8 +213,7 @@ void main() {
|
|||
await stream.terminate();
|
||||
|
||||
final expectedData = frame(data);
|
||||
expect(verify(connection.latestRequest.send(captureAny)).captured.single,
|
||||
expectedData);
|
||||
verify(connection.latestRequest.send(expectedData.toJSBox));
|
||||
});
|
||||
|
||||
test('Stream handles headers properly', () async {
|
||||
|
@ -226,15 +230,15 @@ void main() {
|
|||
|
||||
when(transport.latestRequest.responseHeaders).thenReturn(responseHeaders);
|
||||
when(transport.latestRequest.response)
|
||||
.thenReturn(String.fromCharCodes(frame(<int>[])));
|
||||
.thenReturn(String.fromCharCodes(frame(<int>[])).toJS);
|
||||
|
||||
// Set expectation for request readyState and generate two readyStateChange
|
||||
// events, so that incomingMessages stream completes.
|
||||
final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE];
|
||||
when(transport.latestRequest.readyState)
|
||||
.thenAnswer((_) => readyStates.removeAt(0));
|
||||
final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE];
|
||||
transport.latestRequest.readyState = readyStates[0];
|
||||
transport.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
transport.latestRequest.readyState = readyStates[1];
|
||||
transport.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
|
||||
|
@ -267,16 +271,15 @@ void main() {
|
|||
final encodedString = String.fromCharCodes(encodedTrailers);
|
||||
|
||||
when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders);
|
||||
when(connection.latestRequest.response).thenReturn(encodedString);
|
||||
when(connection.latestRequest.response).thenReturn(encodedString.toJS);
|
||||
|
||||
// Set expectation for request readyState and generate events so that
|
||||
// incomingMessages stream completes.
|
||||
final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE];
|
||||
when(connection.latestRequest.readyState)
|
||||
.thenAnswer((_) => readyStates.removeAt(0));
|
||||
connection.latestRequest.readyState = XMLHttpRequest.HEADERS_RECEIVED;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
connection.latestRequest.progressController.add(progressEvent);
|
||||
connection.latestRequest.readyState = XMLHttpRequest.DONE;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
|
||||
|
@ -303,16 +306,14 @@ void main() {
|
|||
final encodedString = String.fromCharCodes(encoded);
|
||||
|
||||
when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders);
|
||||
when(connection.latestRequest.response).thenReturn(encodedString);
|
||||
|
||||
when(connection.latestRequest.response).thenReturn(encodedString.toJS);
|
||||
// Set expectation for request readyState and generate events so that
|
||||
// incomingMessages stream completes.
|
||||
final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE];
|
||||
when(connection.latestRequest.readyState)
|
||||
.thenAnswer((_) => readyStates.removeAt(0));
|
||||
connection.latestRequest.readyState = XMLHttpRequest.HEADERS_RECEIVED;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
connection.latestRequest.progressController.add(progressEvent);
|
||||
connection.latestRequest.readyState = XMLHttpRequest.DONE;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
|
||||
|
@ -338,16 +339,15 @@ void main() {
|
|||
final data = List<int>.filled(10, 224);
|
||||
when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders);
|
||||
when(connection.latestRequest.response)
|
||||
.thenReturn(String.fromCharCodes(frame(data)));
|
||||
.thenReturn(String.fromCharCodes(frame(data)).toJS);
|
||||
|
||||
// Set expectation for request readyState and generate events, so that
|
||||
// incomingMessages stream completes.
|
||||
final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE];
|
||||
when(connection.latestRequest.readyState)
|
||||
.thenAnswer((_) => readyStates.removeAt(0));
|
||||
connection.latestRequest.readyState = XMLHttpRequest.HEADERS_RECEIVED;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
connection.latestRequest.progressController.add(progressEvent);
|
||||
connection.latestRequest.readyState = XMLHttpRequest.DONE;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
|
||||
|
@ -369,8 +369,8 @@ void main() {
|
|||
const errorDetails = 'error details';
|
||||
when(connection.latestRequest.responseHeaders)
|
||||
.thenReturn({'content-type': 'application/grpc+proto'});
|
||||
when(connection.latestRequest.readyState).thenReturn(HttpRequest.DONE);
|
||||
when(connection.latestRequest.responseText).thenReturn(errorDetails);
|
||||
connection.latestRequest.readyState = XMLHttpRequest.DONE;
|
||||
connection.latestRequest.responseText = errorDetails;
|
||||
connection.latestRequest.readyStateChangeController
|
||||
.add(readyStateChangeEvent);
|
||||
await errorReceived.future;
|
||||
|
@ -398,7 +398,7 @@ void main() {
|
|||
|
||||
when(connection.latestRequest.responseHeaders).thenReturn(metadata);
|
||||
when(connection.latestRequest.readyState)
|
||||
.thenReturn(HttpRequest.HEADERS_RECEIVED);
|
||||
.thenReturn(XMLHttpRequest.HEADERS_RECEIVED);
|
||||
|
||||
// At first invocation the response should be the the first message, after
|
||||
// that first + last messages.
|
||||
|
@ -406,12 +406,12 @@ void main() {
|
|||
when(connection.latestRequest.response).thenAnswer((_) {
|
||||
if (first) {
|
||||
first = false;
|
||||
return encodedStrings[0];
|
||||
return encodedStrings[0].toJS;
|
||||
}
|
||||
return encodedStrings[0] + encodedStrings[1];
|
||||
return (encodedStrings[0] + encodedStrings[1]).toJS;
|
||||
});
|
||||
|
||||
final readyStates = [HttpRequest.HEADERS_RECEIVED, HttpRequest.DONE];
|
||||
final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE];
|
||||
when(connection.latestRequest.readyState)
|
||||
.thenAnswer((_) => readyStates.removeAt(0));
|
||||
|
||||
|
|
Loading…
Reference in New Issue