| // Copyright 2019 The Flutter Authors. 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 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| |
| import 'package:meta/meta.dart'; |
| |
| typedef ContentLengthProvider = int Function(); |
| |
| /// Signature for a callback function that will be notified whenever a |
| /// [FakeHttpClient] issues requests. |
| typedef IssueRequestCallback = void Function(FakeHttpClientRequest request); |
| |
| @immutable |
| class _Body { |
| _Body.empty() |
| : isUtf8 = true, |
| value = null, |
| bytes = Uint8List(0), |
| stream = |
| Stream<Uint8List>.fromIterable(const Iterable<Uint8List>.empty()); |
| |
| _Body.utf8(this.value) |
| : assert(value != null), |
| isUtf8 = true, |
| bytes = utf8.encode(value) as Uint8List, |
| stream = Stream<Uint8List>.fromIterable( |
| <Uint8List>[utf8.encode(value) as Uint8List]); |
| |
| _Body.rawBytes(this.bytes) |
| : assert(bytes != null), |
| isUtf8 = false, |
| value = null, |
| stream = Stream<Uint8List>.fromIterable(<Uint8List>[bytes]); |
| |
| _Body.copy(_Body other) |
| : assert(other != null), |
| isUtf8 = other.isUtf8, |
| value = other.value, |
| bytes = other.bytes, |
| stream = Stream<Uint8List>.fromIterable(<Uint8List>[other.bytes]); |
| |
| final bool isUtf8; |
| final String value; |
| final Uint8List bytes; |
| final Stream<Uint8List> stream; |
| } |
| |
| abstract class FakeTransport { |
| int get contentLength; |
| |
| HttpConnectionInfo get connectionInfo => null; |
| |
| final List<FakeCookie> cookies = <FakeCookie>[]; |
| |
| FakeHttpHeaders get headers { |
| _headers ??= FakeHttpHeaders(contentLengthProvider: () => contentLength); |
| return _headers; |
| } |
| |
| FakeHttpHeaders _headers; |
| |
| bool get persistentConnection => false; |
| } |
| |
| // TODO(tvolkert): `implements Stream<Uint8List>` once HttpClientResponse does the same |
| abstract class FakeInbound extends FakeTransport { |
| FakeInbound(String body) |
| : _body = body == null ? _Body.empty() : _Body.utf8(body); |
| |
| /// Indicates whether the body stream has been exposed to callers in any way. |
| /// Once the body stream has been exposed to callers, [body] becomes |
| /// immutable. |
| bool _isStreamExposed = false; |
| |
| /// Resets this transport so that it may be reused. |
| @mustCallSuper |
| void reset() { |
| _body = _Body.copy(_body); |
| _isStreamExposed = false; |
| } |
| |
| /// The UTF-8 encoded value of the HTTP request body, or null if this request |
| /// specifies no body. |
| /// |
| /// If the HTTP request body was set via [bodyBytes], then it's assumed that |
| /// the body is not a UTF-8 encoded string, and subsequently attempting to |
| /// access [body] will throw a [StateError]. |
| /// |
| /// Once the body stream has been exposed to callers in any way, the [body] |
| /// value becomes immutable (as does the [bodyBytes] value), and any attempt |
| /// to modify it will throw a [StateError]. |
| String get body { |
| if (!_body.isUtf8) { |
| throw StateError('body is not a valid UTF-8 string'); |
| } |
| return _body.value; |
| } |
| |
| _Body _body; |
| set body(String value) { |
| if (_isStreamExposed) { |
| throw StateError('The body of this transport has been made immutable'); |
| } |
| _body = value == null ? _Body.empty() : _Body.utf8(value); |
| } |
| |
| /// The raw bytes of the HTTP request body. |
| /// |
| /// This will never be null; if the HTTP request body is empty, this will be |
| /// the empty list. |
| /// |
| /// Setting this value directly will be assumed to be because the bytes are |
| /// not a UTF-8 encoded string, and subsequently attempting to access [body] |
| /// will throw a [StateError]. |
| /// |
| /// Once the body stream has been exposed to callers in any way, the |
| /// [bodyBytes] value becomes immutable (as does the [body] value), and any |
| /// attempt to modify it will throw a [StateError]. |
| Uint8List get bodyBytes => _body.bytes; |
| set bodyBytes(Uint8List value) { |
| if (_isStreamExposed) { |
| throw StateError('The body of this transport has been made immutable'); |
| } |
| assert(value != null); |
| _body = _Body.rawBytes(value); |
| } |
| |
| StreamSubscription<Uint8List> listen( |
| void Function(Uint8List event) onData, { |
| Function onError, |
| void Function() onDone, |
| bool cancelOnError, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream.listen( |
| onData, |
| onError: onError, |
| onDone: onDone, |
| cancelOnError: cancelOnError, |
| ); |
| } |
| |
| Future<bool> any(bool Function(Uint8List element) test) { |
| _isStreamExposed = true; |
| return _body.stream.any(test); |
| } |
| |
| Stream<Uint8List> asBroadcastStream({ |
| void Function(StreamSubscription<Uint8List> subscription) onListen, |
| void Function(StreamSubscription<Uint8List> subscription) onCancel, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream |
| .asBroadcastStream(onListen: onListen, onCancel: onCancel); |
| } |
| |
| Stream<E> asyncExpand<E>(Stream<E> Function(Uint8List event) convert) { |
| _isStreamExposed = true; |
| return _body.stream.asyncExpand<E>(convert); |
| } |
| |
| Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) { |
| _isStreamExposed = true; |
| return _body.stream.asyncMap<E>(convert); |
| } |
| |
| Stream<R> cast<R>() { |
| _isStreamExposed = true; |
| return _body.stream.cast<R>(); |
| } |
| |
| Future<bool> contains(Object needle) { |
| _isStreamExposed = true; |
| return _body.stream.contains(needle); |
| } |
| |
| Stream<Uint8List> distinct( |
| [bool Function(Uint8List previous, Uint8List next) equals]) { |
| _isStreamExposed = true; |
| return _body.stream.distinct(equals); |
| } |
| |
| Future<E> drain<E>([E futureValue]) { |
| _isStreamExposed = true; |
| return _body.stream.drain<E>(futureValue); |
| } |
| |
| Future<Uint8List> elementAt(int index) { |
| _isStreamExposed = true; |
| return _body.stream.elementAt(index); |
| } |
| |
| Future<bool> every(bool Function(Uint8List element) test) { |
| _isStreamExposed = true; |
| return _body.stream.every(test); |
| } |
| |
| Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) { |
| _isStreamExposed = true; |
| return _body.stream.expand(convert); |
| } |
| |
| Future<Uint8List> get first { |
| _isStreamExposed = true; |
| return _body.stream.first; |
| } |
| |
| Future<Uint8List> firstWhere( |
| bool Function(Uint8List element) test, { |
| List<int> Function() orElse, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream |
| .firstWhere(test, orElse: () => Uint8List.fromList(orElse())); |
| } |
| |
| Future<S> fold<S>( |
| S initialValue, S Function(S previous, Uint8List element) combine) { |
| _isStreamExposed = true; |
| return _body.stream.fold<S>(initialValue, combine); |
| } |
| |
| Future<dynamic> forEach(void Function(Uint8List element) action) { |
| _isStreamExposed = true; |
| return _body.stream.forEach(action); |
| } |
| |
| Stream<Uint8List> handleError( |
| Function onError, { |
| bool Function(dynamic error) test, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream.handleError(onError, test: test); |
| } |
| |
| bool get isBroadcast { |
| _isStreamExposed = true; |
| return _body.stream.isBroadcast; |
| } |
| |
| Future<bool> get isEmpty { |
| _isStreamExposed = true; |
| return _body.stream.isEmpty; |
| } |
| |
| Future<String> join([String separator = '']) { |
| _isStreamExposed = true; |
| return _body.stream.join(separator); |
| } |
| |
| Future<Uint8List> get last { |
| _isStreamExposed = true; |
| return _body.stream.last; |
| } |
| |
| Future<Uint8List> lastWhere( |
| bool Function(Uint8List element) test, { |
| List<int> Function() orElse, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream |
| .lastWhere(test, orElse: () => Uint8List.fromList(orElse())); |
| } |
| |
| Future<int> get length { |
| _isStreamExposed = true; |
| return _body.stream.length; |
| } |
| |
| Stream<S> map<S>(S Function(Uint8List event) convert) { |
| _isStreamExposed = true; |
| return _body.stream.map<S>(convert); |
| } |
| |
| Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) { |
| _isStreamExposed = true; |
| return _body.stream |
| .map((Uint8List list) => list.toList()) |
| .pipe(streamConsumer); |
| } |
| |
| Future<Uint8List> reduce( |
| List<int> Function(Uint8List previous, Uint8List element) combine) { |
| _isStreamExposed = true; |
| return _body.stream.reduce((Uint8List previous, Uint8List element) => |
| Uint8List.fromList(combine(previous, element))); |
| } |
| |
| Future<Uint8List> get single { |
| _isStreamExposed = true; |
| return _body.stream.single; |
| } |
| |
| Future<Uint8List> singleWhere( |
| bool Function(Uint8List element) test, { |
| List<int> Function() orElse, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream |
| .singleWhere(test, orElse: () => Uint8List.fromList(orElse())); |
| } |
| |
| Stream<Uint8List> skip(int count) { |
| _isStreamExposed = true; |
| return _body.stream.skip(count); |
| } |
| |
| Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) { |
| _isStreamExposed = true; |
| return _body.stream.skipWhile(test); |
| } |
| |
| Stream<Uint8List> take(int count) { |
| _isStreamExposed = true; |
| return _body.stream.take(count); |
| } |
| |
| Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) { |
| _isStreamExposed = true; |
| return _body.stream.takeWhile(test); |
| } |
| |
| Stream<Uint8List> timeout( |
| Duration timeLimit, { |
| void Function(EventSink<Uint8List> sink) onTimeout, |
| }) { |
| _isStreamExposed = true; |
| return _body.stream.timeout(timeLimit, onTimeout: onTimeout); |
| } |
| |
| Future<List<Uint8List>> toList() { |
| _isStreamExposed = true; |
| return _body.stream.toList(); |
| } |
| |
| Future<Set<Uint8List>> toSet() { |
| _isStreamExposed = true; |
| return _body.stream.toSet(); |
| } |
| |
| Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) { |
| _isStreamExposed = true; |
| return _body.stream |
| .map((Uint8List list) => list.toList()) |
| .transform<S>(streamTransformer); |
| } |
| |
| Stream<Uint8List> where(bool Function(Uint8List event) test) { |
| _isStreamExposed = true; |
| return _body.stream.where(test); |
| } |
| |
| @override |
| int get contentLength => _body.bytes.length; |
| |
| X509Certificate get certificate => null; |
| } |
| |
| abstract class FakeOutbound extends FakeTransport implements IOSink { |
| StringBuffer _buffer = StringBuffer(); |
| |
| String get body => _buffer.toString(); |
| |
| List<Object> get errors => _errors; |
| List<Object> _errors = <Object>[]; |
| |
| /// Whether this outbound has been closed. |
| bool get isClosed => _isClosed; |
| bool _isClosed = false; |
| |
| /// Resets this transport so that it may be reused. |
| @mustCallSuper |
| void reset() { |
| _isClosed = false; |
| _buffer = StringBuffer(); |
| _errors = <Object>[]; |
| } |
| |
| @override |
| Encoding get encoding => utf8; |
| |
| @override |
| set encoding(Encoding value) => throw UnsupportedError('Unsupported'); |
| |
| @override |
| void add(List<int> data) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.write(utf8.decode(data)); |
| } |
| |
| @override |
| void addError(Object error, [StackTrace stackTrace]) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| errors.add(error); |
| } |
| |
| @override |
| Future<dynamic> addStream(Stream<List<int>> stream) async { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.write(await utf8.decoder.bind(stream).join()); |
| } |
| |
| @override |
| void write(Object obj) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.write(obj); |
| } |
| |
| @override |
| void writeAll(Iterable<dynamic> objects, [String separator = '']) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.writeAll(objects, separator); |
| } |
| |
| @override |
| void writeCharCode(int charCode) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.writeCharCode(charCode); |
| } |
| |
| @override |
| void writeln([Object obj = '']) { |
| if (isClosed) { |
| throw StateError('Transport is closed'); |
| } |
| headers._sealed = true; |
| _buffer.writeln(obj); |
| } |
| |
| @override |
| Future<dynamic> get done async {} |
| |
| @override |
| Future<dynamic> flush() async {} |
| |
| @override |
| Future<dynamic> close() async { |
| _isClosed = true; |
| } |
| |
| bool get bufferOutput => false; |
| |
| set bufferOutput(bool value) => throw UnsupportedError('Unsupported'); |
| |
| @override |
| int get contentLength => _contentLength ?? _buffer.length; |
| int _contentLength; |
| |
| set contentLength(int value) { |
| _contentLength = value; |
| } |
| |
| set persistentConnection(bool value) => throw UnsupportedError('Unsupported'); |
| } |
| |
| class FakeCookie implements Cookie { |
| FakeCookie({ |
| this.name, |
| this.value, |
| this.domain, |
| this.path, |
| this.expires, |
| this.httpOnly, |
| this.maxAge, |
| this.secure, |
| }); |
| |
| @override |
| String name; |
| |
| @override |
| String value; |
| |
| @override |
| String domain; |
| |
| @override |
| String path; |
| |
| @override |
| DateTime expires; |
| |
| @override |
| bool httpOnly; |
| |
| @override |
| int maxAge; |
| |
| @override |
| bool secure; |
| } |
| |
| class FakeHttpHeaders implements HttpHeaders { |
| FakeHttpHeaders({this.contentLengthProvider}); |
| |
| final ContentLengthProvider contentLengthProvider; |
| final Map<String, List<String>> _values = <String, List<String>>{}; |
| bool _sealed = false; |
| |
| void _checkSealed() { |
| if (_sealed) { |
| throw StateError('HTTP headers are sealed'); |
| } |
| } |
| |
| @override |
| bool get chunkedTransferEncoding => false; |
| |
| @override |
| set chunkedTransferEncoding(bool value) => |
| throw UnsupportedError('Unsupported'); |
| |
| @override |
| int get contentLength => |
| contentLengthProvider != null ? contentLengthProvider() : -1; |
| |
| @override |
| set contentLength(int value) => throw UnsupportedError('Unsupported'); |
| |
| @override |
| ContentType get contentType => throw UnimplementedError(); |
| |
| @override |
| set contentType(ContentType value) { |
| _checkSealed(); |
| removeAll(HttpHeaders.contentTypeHeader); |
| add(HttpHeaders.contentTypeHeader, '$value'); |
| } |
| |
| @override |
| DateTime date; |
| |
| @override |
| DateTime expires; |
| |
| @override |
| String host; |
| |
| @override |
| DateTime ifModifiedSince; |
| |
| @override |
| bool persistentConnection; |
| |
| @override |
| int port; |
| |
| @override |
| List<String> operator [](String name) => _values[name]; |
| |
| @override |
| void add(String name, Object value, {bool preserveHeaderCase = false}) { |
| _checkSealed(); |
| name = name.toLowerCase(); |
| _values[name] ??= <String>[]; |
| _values[name].add('$value'); |
| } |
| |
| @override |
| void clear() { |
| _checkSealed(); |
| _values.clear(); |
| } |
| |
| @override |
| void forEach(void Function(String name, List<String> values) f) { |
| _values.forEach(f); |
| } |
| |
| @override |
| void noFolding(String name) {} |
| |
| @override |
| void remove(String name, Object value) { |
| _checkSealed(); |
| name = name.toLowerCase(); |
| if (_values.containsKey('$value')) { |
| _values[name].remove('$value'); |
| } |
| } |
| |
| @override |
| void removeAll(String name) { |
| _checkSealed(); |
| name = name.toLowerCase(); |
| _values.remove(name); |
| } |
| |
| @override |
| void set(String name, Object value, {bool preserveHeaderCase = false}) { |
| _checkSealed(); |
| name = name.toLowerCase(); |
| _values[name] = <String>['$value']; |
| } |
| |
| @override |
| String value(String name) { |
| final List<String> value = _values[name.toLowerCase()]; |
| return value == null ? null : value.single; |
| } |
| } |
| |
| class FakeHttpRequest extends FakeInbound implements HttpRequest { |
| /// Creates a new [FakeHttpRequest]. |
| /// |
| /// If the optional [body] argument is specified, the request stream will |
| /// yield the specified body value when UTF-8 decoded. By default, the |
| /// request stream will be empty. The [body] property can be modified until |
| /// the stream has been exposed to callers, at which time it becomes |
| /// immutable. |
| FakeHttpRequest({ |
| this.method = 'GET', |
| String body, |
| String path = '/', |
| Map<String, dynamic> queryParametersValue, |
| FakeHttpResponse response, |
| }) : assert(method != null), |
| assert(path != null), |
| uri = Uri(path: path, queryParameters: queryParametersValue), |
| response = response ?? FakeHttpResponse(), |
| super(body); |
| |
| @override |
| String method; |
| |
| @override |
| Uri uri; |
| |
| String get path => uri.path; |
| set path(String value) { |
| uri = uri.replace(path: value); |
| } |
| |
| @override |
| FakeHttpResponse response; |
| |
| @override |
| String get protocolVersion => '1.1'; |
| |
| @override |
| Uri get requestedUri => uri; |
| |
| @override |
| HttpSession get session => throw UnsupportedError('Unsupported'); |
| } |
| |
| class FakeHttpResponse extends FakeOutbound implements HttpResponse { |
| @override |
| Duration get deadline => null; |
| |
| @override |
| set deadline(Duration value) => throw UnsupportedError('Unsupported'); |
| |
| @override |
| String get reasonPhrase => null; |
| |
| @override |
| set reasonPhrase(String value) => throw UnsupportedError('Unsupported'); |
| |
| @override |
| int statusCode = HttpStatus.ok; |
| |
| @override |
| Future<dynamic> redirect(Uri location, |
| {int status = HttpStatus.movedTemporarily}) { |
| assert(location != null); |
| assert(status != null); |
| statusCode = status; |
| headers.add(HttpHeaders.locationHeader, '$location'); |
| return close(); |
| } |
| |
| @override |
| Future<Socket> detachSocket({bool writeHeaders = true}) => |
| throw UnsupportedError('Unsupported'); |
| } |
| |
| class FakeHttpClient implements HttpClient { |
| FakeHttpClient({ |
| FakeHttpClientRequest request, |
| this.onIssueRequest, |
| }) : request = request ?? FakeHttpClientRequest(); |
| |
| /// The request to return from the HTTP methods. |
| FakeHttpClientRequest request; |
| |
| /// Optional callback that will be notified when this client issues requests. |
| IssueRequestCallback onIssueRequest; |
| |
| /// The number of requests that have been issued. |
| int get requestCount => _requestCount; |
| int _requestCount = 0; |
| |
| static const String methodDelete = 'DELETE'; |
| static const String methodGet = 'GET'; |
| static const String methodHead = 'HEAD'; |
| static const String methodPatch = 'PATCH'; |
| static const String methodPost = 'POST'; |
| static const String methodPut = 'PUT'; |
| |
| @override |
| bool autoUncompress; |
| |
| @override |
| Duration connectionTimeout; |
| |
| @override |
| Duration idleTimeout; |
| |
| @override |
| int maxConnectionsPerHost; |
| |
| @override |
| String userAgent; |
| |
| @override |
| void addCredentials( |
| Uri url, String realm, HttpClientCredentials credentials) {} |
| |
| @override |
| void addProxyCredentials( |
| String host, int port, String realm, HttpClientCredentials credentials) {} |
| |
| @override |
| set authenticate( |
| Future<bool> Function(Uri url, String scheme, String realm) f) {} |
| |
| @override |
| set authenticateProxy( |
| Future<bool> Function(String host, int port, String scheme, String realm) |
| f) {} |
| |
| @override |
| set badCertificateCallback( |
| bool Function(X509Certificate cert, String host, int port) callback) {} |
| |
| @override |
| set findProxy(String Function(Uri url) f) {} |
| |
| @override |
| void close({bool force = false}) {} |
| |
| @override |
| Future<HttpClientRequest> delete(String host, int port, String path) async { |
| return open(methodDelete, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> deleteUrl(Uri url) async { |
| return openUrl(methodDelete, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> get(String host, int port, String path) async { |
| return open(methodGet, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> getUrl(Uri url) async { |
| return openUrl(methodGet, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> head(String host, int port, String path) async { |
| return open(methodHead, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> headUrl(Uri url) async { |
| return openUrl(methodHead, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> patch(String host, int port, String path) { |
| return open(methodPatch, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> patchUrl(Uri url) { |
| return openUrl(methodPatch, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> post(String host, int port, String path) async { |
| return open(methodPost, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> postUrl(Uri url) async { |
| return openUrl(methodPost, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> put(String host, int port, String path) async { |
| return open(methodPut, host, port, path); |
| } |
| |
| @override |
| Future<HttpClientRequest> putUrl(Uri url) async { |
| return openUrl(methodPut, url); |
| } |
| |
| @override |
| Future<HttpClientRequest> open( |
| String method, String host, int port, String path) { |
| return openUrl(method, Uri(host: host, port: port, path: path)); |
| } |
| |
| @override |
| Future<HttpClientRequest> openUrl(String method, Uri url) async { |
| _requestCount++; |
| request.reset(); |
| request.method = method; |
| request.uri = url; |
| if (onIssueRequest != null) { |
| onIssueRequest(request); |
| } |
| return request; |
| } |
| } |
| |
| class FakeHttpClientRequest extends FakeOutbound implements HttpClientRequest { |
| FakeHttpClientRequest({ |
| FakeHttpClientResponse response, |
| }) : response = response ?? FakeHttpClientResponse(); |
| |
| /// The response to produce when this request is closed. |
| FakeHttpClientResponse response; |
| |
| Completer<HttpClientResponse> _doneCompleter = |
| Completer<HttpClientResponse>(); |
| |
| /// Resets this fake request so that it may be reused. |
| @override |
| void reset() { |
| super.reset(); |
| response.reset(); |
| if (!_doneCompleter.isCompleted) { |
| _doneCompleter.complete(response); |
| } |
| _doneCompleter = Completer<HttpClientResponse>(); |
| } |
| |
| @override |
| String method; |
| |
| @override |
| Uri uri; |
| |
| @override |
| bool followRedirects; |
| |
| @override |
| int maxRedirects; |
| |
| @override |
| Future<HttpClientResponse> close() async { |
| await super.close(); |
| _doneCompleter.complete(response); |
| return response; |
| } |
| |
| @override |
| Future<HttpClientResponse> get done => _doneCompleter.future; |
| } |
| |
| class FakeHttpClientResponse extends FakeInbound implements HttpClientResponse { |
| FakeHttpClientResponse({String body}) : super(body); |
| |
| @override |
| HttpClientResponseCompressionState get compressionState { |
| return HttpClientResponseCompressionState.decompressed; |
| } |
| |
| @override |
| Future<Socket> detachSocket() async => |
| throw UnsupportedError('Mocked response'); |
| |
| @override |
| bool get isRedirect => false; |
| |
| @override |
| String get reasonPhrase => null; |
| |
| @override |
| Future<HttpClientResponse> redirect( |
| [String method, Uri url, bool followLoops]) { |
| return Future<HttpClientResponse>.error( |
| UnsupportedError('Mocked response')); |
| } |
| |
| @override |
| List<RedirectInfo> get redirects => <RedirectInfo>[]; |
| |
| @override |
| int statusCode = HttpStatus.ok; |
| } |