| Index: generated/googleapis_beta/test/common/common_internal_test.dart
|
| diff --git a/generated/googleapis_beta/test/common/common_internal_test.dart b/generated/googleapis_beta/test/common/common_internal_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a8fc299c3b8e62b0c7c9ab6bacd293d4218e739b
|
| --- /dev/null
|
| +++ b/generated/googleapis_beta/test/common/common_internal_test.dart
|
| @@ -0,0 +1,911 @@
|
| +library googleapis_beta.common_internal_test;
|
| +import 'dart:async';
|
| +import 'dart:convert';
|
| +
|
| +import 'package:crypto/crypto.dart' as crypto;
|
| +import 'package:googleapis_beta/common/common.dart';
|
| +import 'package:googleapis_beta/src/common_internal.dart';
|
| +import 'package:http/http.dart' as http;
|
| +import 'package:unittest/unittest.dart';
|
| +class HttpServerMock extends http.BaseClient {
|
| + Function _callback;
|
| + bool _expectJson;
|
| +
|
| + void register(Function callback, bool expectJson) {
|
| + _callback = callback;
|
| + _expectJson = expectJson;
|
| + }
|
| +
|
| + Future<http.StreamedResponse> send(http.BaseRequest request) {
|
| + if (_expectJson) {
|
| + return request.finalize()
|
| + .transform(UTF8.decoder)
|
| + .join('')
|
| + .then((String jsonString) {
|
| + if (jsonString.isEmpty) {
|
| + return _callback(request, null);
|
| + } else {
|
| + return _callback(request, JSON.decode(jsonString));
|
| + }
|
| + });
|
| + } else {
|
| + var stream = request.finalize();
|
| + if (stream == null) {
|
| + return _callback(request, []);
|
| + } else {
|
| + return stream.toBytes().then((data) {
|
| + return _callback(request, data);
|
| + });
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +http.StreamedResponse stringResponse(int status, Map headers, String body) {
|
| + var stream = new Stream.fromIterable([UTF8.encode(body)]);
|
| + return new http.StreamedResponse(stream, status, headers: headers);
|
| +}
|
| +
|
| +http.StreamedResponse binaryResponse(int status,
|
| + Map<String,String> headers,
|
| + List<int> bytes) {
|
| + var stream = new Stream.fromIterable([bytes]);
|
| + return new http.StreamedResponse(stream, status, headers: headers);
|
| +}
|
| +
|
| +Stream<List<int>> byteStream(String s) {
|
| + var bodyController = new StreamController();
|
| + bodyController.add(UTF8.encode(s));
|
| + bodyController.close();
|
| + return bodyController.stream;
|
| +}
|
| +
|
| +class _ApiRequestError extends TypeMatcher {
|
| + const _ApiRequestError() : super("ApiRequestError");
|
| + bool matches(item, Map matchState) => item is ApiRequestError;
|
| +}
|
| +
|
| +class _DetailedApiRequestError extends TypeMatcher {
|
| + const _DetailedApiRequestError() : super("DetailedApiRequestError");
|
| + bool matches(item, Map matchState) => item is DetailedApiRequestError;
|
| +}
|
| +
|
| +class TestError {}
|
| +
|
| +class _TestError extends TypeMatcher {
|
| + const _TestError() : super("TestError");
|
| + bool matches(item, Map matchState) => item is TestError;
|
| +}
|
| +
|
| +const isApiRequestError = const _ApiRequestError();
|
| +const isDetailedApiRequestError = const _DetailedApiRequestError();
|
| +const isTestError = const _TestError();
|
| +
|
| +
|
| +main() {
|
| + group('common-external', () {
|
| + test('escaper', () {
|
| + expect(Escaper.ecapePathComponent('a/b%c '), equals('a%2Fb%25c%20'));
|
| + expect(Escaper.ecapeVariable('a/b%c '), equals('a%2Fb%25c%20'));
|
| + expect(Escaper.ecapeVariableReserved('a/b%c+ '), equals('a/b%25c+%20'));
|
| + expect(Escaper.escapeQueryComponent('a/b%c '), equals('a%2Fb%25c%20'));
|
| + });
|
| +
|
| + test('mapMap', () {
|
| + newTestMap() => {
|
| + 's' : 'string',
|
| + 'i' : 42,
|
| + };
|
| +
|
| + var copy = mapMap(newTestMap());
|
| + expect(copy, hasLength(2));
|
| + expect(copy['s'], equals('string'));
|
| + expect(copy['i'], equals(42));
|
| +
|
| +
|
| + var mod = mapMap(newTestMap(), (x) => '$x foobar');
|
| + expect(mod, hasLength(2));
|
| + expect(mod['s'], equals('string foobar'));
|
| + expect(mod['i'], equals('42 foobar'));
|
| + });
|
| +
|
| + test('base64-encoder', () {
|
| + var base64encoder = new Base64Encoder();
|
| +
|
| + testString(String msg, String expectedBase64) {
|
| + var msgBytes = UTF8.encode(msg);
|
| +
|
| + Stream singleByteStream(List<int> msgBytes) {
|
| + var controller = new StreamController();
|
| + for (var byte in msgBytes) {
|
| + controller.add([byte]);
|
| + }
|
| + controller.close();
|
| + return controller.stream;
|
| + }
|
| +
|
| + Stream allByteStream(List<int> msgBytes) {
|
| + var controller = new StreamController();
|
| + controller.add(msgBytes);
|
| + controller.close();
|
| + return controller.stream;
|
| + }
|
| +
|
| + singleByteStream(msgBytes)
|
| + .transform(base64encoder)
|
| + .join('')
|
| + .then(expectAsync((String result) {
|
| + expect(result, equals(expectedBase64));
|
| + }));
|
| +
|
| + allByteStream(msgBytes)
|
| + .transform(base64encoder)
|
| + .join('')
|
| + .then(expectAsync((String result) {
|
| + expect(result, equals(expectedBase64));
|
| + }));
|
| +
|
| + expect(Base64Encoder.lengthOfBase64Stream(msg.length),
|
| + equals(expectedBase64.length));
|
| + }
|
| +
|
| + testString('pleasure.', 'cGxlYXN1cmUu');
|
| + testString('leasure.', 'bGVhc3VyZS4=');
|
| + testString('easure.', 'ZWFzdXJlLg==');
|
| + testString('asure.', 'YXN1cmUu');
|
| + testString('sure.', 'c3VyZS4=');
|
| + testString('', '');
|
| + });
|
| +
|
| + group('chunk-stack', () {
|
| + var chunkSize = 9;
|
| +
|
| + folded(List<List<int>> byteArrays) {
|
| + return byteArrays.fold([], (buf, e) => buf..addAll(e));
|
| + }
|
| +
|
| + test('finalize', () {
|
| + var chunkStack = new ChunkStack(9);
|
| + chunkStack.finalize();
|
| + expect(() => chunkStack.addBytes([1]), throwsA(isStateError));
|
| + expect(() => chunkStack.finalize(), throwsA(isStateError));
|
| + });
|
| +
|
| + test('empty', () {
|
| + var chunkStack = new ChunkStack(9);
|
| + expect(chunkStack.length, equals(0));
|
| + chunkStack.finalize();
|
| + expect(chunkStack.length, equals(0));
|
| + });
|
| +
|
| + test('sub-chunk-size', () {
|
| + var bytes = [1, 2, 3];
|
| +
|
| + var chunkStack = new ChunkStack(9);
|
| + chunkStack.addBytes(bytes);
|
| + expect(chunkStack.length, equals(0));
|
| + chunkStack.finalize();
|
| + expect(chunkStack.length, equals(1));
|
| + expect(chunkStack.totalByteLength, equals(bytes.length));
|
| +
|
| + var chunks = chunkStack.removeSublist(0, chunkStack.length);
|
| + expect(chunkStack.length, equals(0));
|
| + expect(chunks, hasLength(1));
|
| +
|
| + expect(folded(chunks.first.byteArrays), equals(bytes));
|
| + expect(chunks.first.offset, equals(0));
|
| + expect(chunks.first.length, equals(3));
|
| + expect(chunks.first.endOfChunk, equals(bytes.length));
|
| + });
|
| +
|
| + test('exact-chunk-size', () {
|
| + var bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
| +
|
| + var chunkStack = new ChunkStack(9);
|
| + chunkStack.addBytes(bytes);
|
| + expect(chunkStack.length, equals(1));
|
| + chunkStack.finalize();
|
| + expect(chunkStack.length, equals(1));
|
| + expect(chunkStack.totalByteLength, equals(bytes.length));
|
| +
|
| + var chunks = chunkStack.removeSublist(0, chunkStack.length);
|
| + expect(chunkStack.length, equals(0));
|
| + expect(chunks, hasLength(1));
|
| +
|
| + expect(folded(chunks.first.byteArrays), equals(bytes));
|
| + expect(chunks.first.offset, equals(0));
|
| + expect(chunks.first.length, equals(bytes.length));
|
| + expect(chunks.first.endOfChunk, equals(bytes.length));
|
| + });
|
| +
|
| + test('super-chunk-size', () {
|
| + var bytes0 = [1, 2, 3, 4];
|
| + var bytes1 = [1, 2, 3, 4];
|
| + var bytes2 = [5, 6, 7, 8, 9, 10, 11];
|
| + var bytes = folded([bytes0, bytes1, bytes2]);
|
| +
|
| + var chunkStack = new ChunkStack(9);
|
| + chunkStack.addBytes(bytes0);
|
| + chunkStack.addBytes(bytes1);
|
| + chunkStack.addBytes(bytes2);
|
| + expect(chunkStack.length, equals(1));
|
| + chunkStack.finalize();
|
| + expect(chunkStack.length, equals(2));
|
| + expect(chunkStack.totalByteLength, equals(bytes.length));
|
| +
|
| + var chunks = chunkStack.removeSublist(0, chunkStack.length);
|
| + expect(chunkStack.length, equals(0));
|
| + expect(chunks, hasLength(2));
|
| +
|
| + expect(folded(chunks.first.byteArrays),
|
| + equals(bytes.sublist(0, chunkSize)));
|
| + expect(chunks.first.offset, equals(0));
|
| + expect(chunks.first.length, equals(chunkSize));
|
| + expect(chunks.first.endOfChunk, equals(chunkSize));
|
| +
|
| + expect(folded(chunks.last.byteArrays),
|
| + equals(bytes.sublist(chunkSize)));
|
| + expect(chunks.last.offset, equals(chunkSize));
|
| + expect(chunks.last.length, equals(bytes.length - chunkSize));
|
| + expect(chunks.last.endOfChunk, equals(bytes.length));
|
| + });
|
| + });
|
| +
|
| + test('media', () {
|
| + // Tests for [MediaRange]
|
| + var partialRange = new ByteRange(1, 100);
|
| + expect(partialRange.start, equals(1));
|
| + expect(partialRange.end, equals(100));
|
| +
|
| + var fullRange = new ByteRange(0, -1);
|
| + expect(fullRange.start, equals(0));
|
| + expect(fullRange.end, equals(-1));
|
| +
|
| + expect(() => new ByteRange(0, 0), throws);
|
| + expect(() => new ByteRange(-1, 0), throws);
|
| + expect(() => new ByteRange(-1, 1), throws);
|
| +
|
| + // Tests for [DownloadOptions]
|
| + expect(DownloadOptions.Metadata.isMetadataDownload, isTrue);
|
| +
|
| + expect(DownloadOptions.FullMedia.isFullDownload, isTrue);
|
| + expect(DownloadOptions.FullMedia.isMetadataDownload, isFalse);
|
| +
|
| + // Tests for [Media]
|
| + var stream = new StreamController().stream;
|
| + expect(() => new Media(null, 0, contentType: 'foobar'),
|
| + throwsA(isArgumentError));
|
| + expect(() => new Media(stream, 0, contentType: null),
|
| + throwsA(isArgumentError));
|
| + expect(() => new Media(stream, -1, contentType: 'foobar'),
|
| + throwsA(isArgumentError));
|
| +
|
| + var lengthUnknownMedia = new Media(stream, null);
|
| + expect(lengthUnknownMedia.stream, equals(stream));
|
| + expect(lengthUnknownMedia.length, equals(null));
|
| +
|
| + var media = new Media(stream, 10, contentType: 'foobar');
|
| + expect(media.stream, equals(stream));
|
| + expect(media.length, equals(10));
|
| + expect(media.contentType, equals('foobar'));
|
| +
|
| + // Tests for [ResumableUploadOptions]
|
| + expect(() => new ResumableUploadOptions(numberOfAttempts: 0),
|
| + throwsA(isArgumentError));
|
| + expect(() => new ResumableUploadOptions(chunkSize: 1),
|
| + throwsA(isArgumentError));
|
| + });
|
| +
|
| + group('api-requester', () {
|
| + var httpMock, rootUrl, basePath;
|
| + ApiRequester requester;
|
| +
|
| + var responseHeaders = {
|
| + 'content-type' : 'application/json; charset=utf-8',
|
| + };
|
| +
|
| + setUp(() {
|
| + httpMock = new HttpServerMock();
|
| + rootUrl = 'http://example.com/';
|
| + basePath = '/base/';
|
| + requester = new ApiRequester(httpMock, rootUrl, basePath);
|
| + });
|
| +
|
| +
|
| + // Tests for Request, Response
|
| +
|
| + group('metadata-request-response', () {
|
| + test('empty-request-empty-response', () {
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=json'));
|
| + return stringResponse(200, responseHeaders, '');
|
| + }), true);
|
| + requester.request('abc', 'GET').then(expectAsync((response) {
|
| + expect(response, isNull);
|
| + }));
|
| + });
|
| +
|
| + test('json-map-request-json-map-response', () {
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=json'));
|
| + expect(json is Map, isTrue);
|
| + expect(json, hasLength(1));
|
| + expect(json['foo'], equals('bar'));
|
| + return stringResponse(200, responseHeaders, '{"foo2" : "bar2"}');
|
| + }), true);
|
| + requester.request('abc',
|
| + 'GET',
|
| + body: JSON.encode({'foo' : 'bar'})).then(
|
| + expectAsync((response) {
|
| + expect(response is Map, isTrue);
|
| + expect(response, hasLength(1));
|
| + expect(response['foo2'], equals('bar2'));
|
| + }));
|
| + });
|
| +
|
| + test('json-list-request-json-list-response', () {
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=json'));
|
| + expect(json is List, isTrue);
|
| + expect(json, hasLength(2));
|
| + expect(json[0], equals('a'));
|
| + expect(json[1], equals(1));
|
| + return stringResponse(200, responseHeaders, '["b", 2]');
|
| + }), true);
|
| + requester.request('abc',
|
| + 'GET',
|
| + body: JSON.encode(['a', 1])).then(
|
| + expectAsync((response) {
|
| + expect(response is List, isTrue);
|
| + expect(response[0], equals('b'));
|
| + expect(response[1], equals(2));
|
| + }));
|
| + });
|
| + });
|
| +
|
| + group('media-download', () {
|
| + test('media-download', () {
|
| + var data256 = new List.generate(256, (i) => i);
|
| + httpMock.register(expectAsync((http.BaseRequest request, data) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=media'));
|
| + expect(data, isEmpty);
|
| + var headers = {
|
| + 'content-length' : '${data256.length}',
|
| + 'content-type' : 'foobar',
|
| + };
|
| + return binaryResponse(200, headers, data256);
|
| + }), false);
|
| + requester.request('abc',
|
| + 'GET',
|
| + body: '',
|
| + downloadOptions: DownloadOptions.FullMedia).then(
|
| + expectAsync((Media media) {
|
| + expect(media.contentType, equals('foobar'));
|
| + expect(media.length, equals(data256.length));
|
| + media.stream.fold([], (b, d) => b..addAll(d)).then(expectAsync((d) {
|
| + expect(d, equals(data256));
|
| + }));
|
| + }));
|
| + });
|
| +
|
| + test('media-download-partial', () {
|
| + var data256 = new List.generate(256, (i) => i);
|
| + var data64 = data256.sublist(128, 128 + 64);
|
| +
|
| + httpMock.register(expectAsync((http.BaseRequest request, data) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=media'));
|
| + expect(data, isEmpty);
|
| + expect(request.headers['range'],
|
| + equals('bytes=128-191'));
|
| + var headers = {
|
| + 'content-length' : '${data64.length}',
|
| + 'content-type' : 'foobar',
|
| + 'content-range' : 'bytes 128-191/256',
|
| + };
|
| + return binaryResponse(200, headers, data64);
|
| + }), false);
|
| + var range = new ByteRange(128, 128 + 64 - 1);
|
| + var options = new PartialDownloadOptions(range);
|
| + requester.request('abc',
|
| + 'GET',
|
| + body: '',
|
| + downloadOptions: options).then(
|
| + expectAsync((Media media) {
|
| + expect(media.contentType, equals('foobar'));
|
| + expect(media.length, equals(data64.length));
|
| + media.stream.fold([], (b, d) => b..addAll(d)).then(expectAsync((d) {
|
| + expect(d, equals(data64));
|
| + }));
|
| + }));
|
| + });
|
| +
|
| + test('json-upload-media-download', () {
|
| + var data256 = new List.generate(256, (i) => i);
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?alt=media'));
|
| + expect(json is List, isTrue);
|
| + expect(json, hasLength(2));
|
| + expect(json[0], equals('a'));
|
| + expect(json[1], equals(1));
|
| +
|
| + var headers = {
|
| + 'content-length' : '${data256.length}',
|
| + 'content-type' : 'foobar',
|
| + };
|
| + return binaryResponse(200, headers, data256);
|
| + }), true);
|
| + requester.request('abc',
|
| + 'GET',
|
| + body: JSON.encode(['a', 1]),
|
| + downloadOptions: DownloadOptions.FullMedia).then(
|
| + expectAsync((Media media) {
|
| + expect(media.contentType, equals('foobar'));
|
| + expect(media.length, equals(data256.length));
|
| + media.stream.fold([], (b, d) => b..addAll(d)).then(expectAsync((d) {
|
| + expect(d, equals(data256));
|
| + }));
|
| + }));
|
| + });
|
| + });
|
| +
|
| + // Tests for media uploads
|
| +
|
| + group('media-upload', () {
|
| + Stream streamFromByteArrays(byteArrays) {
|
| + var controller = new StreamController();
|
| + for (var array in byteArrays) {
|
| + controller.add(array);
|
| + }
|
| + controller.close();
|
| + return controller.stream;
|
| + }
|
| + Media mediaFromByteArrays(byteArrays, {bool withLen: true}) {
|
| + int len = 0;
|
| + byteArrays.forEach((array) { len += array.length; });
|
| + if (!withLen) len = null;
|
| + return new Media(streamFromByteArrays(byteArrays),
|
| + len,
|
| + contentType: 'foobar');
|
| + }
|
| + validateServerRequest(e, http.BaseRequest request, List<int> data) {
|
| + return new Future.sync(() {
|
| + var h = e['headers'];
|
| + var r = e['response'];
|
| +
|
| + expect(request.url.toString(), equals(e['url']));
|
| + expect(request.method, equals(e['method']));
|
| + h.forEach((k, v) {
|
| + expect(request.headers[k], equals(v));
|
| + });
|
| +
|
| + expect(data, equals(e['data']));
|
| + return r;
|
| + });
|
| + }
|
| + serverRequestValidator(List expectations) {
|
| + int i = 0;
|
| + return (http.BaseRequest request, List<int> data) {
|
| + return validateServerRequest(expectations[i++], request, data);
|
| + };
|
| + }
|
| +
|
| + test('simple', () {
|
| + var bytes = new List.generate(10 * 256 * 1024 + 1, (i) => i % 256);
|
| + var expectations = [
|
| + {
|
| + 'url' : 'http://example.com/xyz?uploadType=media&alt=json',
|
| + 'method' : 'POST',
|
| + 'data' : bytes,
|
| + 'headers' : {
|
| + 'content-length' : '${bytes.length}',
|
| + 'content-type' : 'foobar',
|
| + },
|
| + 'response' : stringResponse(200, responseHeaders, '')
|
| + },
|
| + ];
|
| +
|
| + httpMock.register(
|
| + expectAsync(serverRequestValidator(expectations)), false);
|
| + var media = mediaFromByteArrays([bytes]);
|
| + requester.request('/xyz',
|
| + 'POST',
|
| + uploadMedia: media).then(
|
| + expectAsync((response) {}));
|
| + });
|
| +
|
| + test('multipart-upload', () {
|
| + var bytes = new List.generate(10 * 256 * 1024 + 1, (i) => i % 256);
|
| + var contentBytes =
|
| + '--314159265358979323846\r\n'
|
| + 'Content-Type: $CONTENT_TYPE_JSON_UTF8\r\n\r\n'
|
| + 'BODY'
|
| + '\r\n--314159265358979323846\r\n'
|
| + 'Content-Type: foobar\r\n'
|
| + 'Content-Transfer-Encoding: base64\r\n\r\n'
|
| + '${crypto.CryptoUtils.bytesToBase64(bytes)}'
|
| + '\r\n--314159265358979323846--';
|
| +
|
| + var expectations = [
|
| + {
|
| + 'url' : 'http://example.com/xyz?uploadType=multipart&alt=json',
|
| + 'method' : 'POST',
|
| + 'data' : UTF8.encode('$contentBytes'),
|
| + 'headers' : {
|
| + 'content-length' : '${contentBytes.length}',
|
| + 'content-type' :
|
| + 'multipart/related; boundary="314159265358979323846"',
|
| + },
|
| + 'response' : stringResponse(200, responseHeaders, '')
|
| + },
|
| + ];
|
| +
|
| + httpMock.register(
|
| + expectAsync(serverRequestValidator(expectations)), false);
|
| + var media = mediaFromByteArrays([bytes]);
|
| + requester.request('/xyz',
|
| + 'POST',
|
| + body: 'BODY',
|
| + uploadMedia: media).then(
|
| + expectAsync((response) {}));
|
| + });
|
| +
|
| + group('resumable-upload', () {
|
| + // TODO: respect [stream]
|
| + buildExpectations(List<int> bytes, int chunkSize, bool stream,
|
| + {int numberOfServerErrors: 0}) {
|
| + int totalLength = bytes.length;
|
| + int numberOfChunks = totalLength ~/ chunkSize;
|
| + int numberOfBytesInLastChunk = totalLength % chunkSize;
|
| +
|
| + if (numberOfBytesInLastChunk > 0) {
|
| + numberOfChunks++;
|
| + } else {
|
| + numberOfBytesInLastChunk = chunkSize;
|
| + }
|
| +
|
| + var expectations = [];
|
| +
|
| + // First request is making a POST and gets the upload URL.
|
| + expectations.add({
|
| + 'url' : 'http://example.com/xyz?uploadType=resumable&alt=json',
|
| + 'method' : 'POST',
|
| + 'data' : [],
|
| + 'headers' : {
|
| + 'content-length' : '0',
|
| + 'content-type' : 'application/json; charset=utf-8',
|
| + 'x-upload-content-type' : 'foobar',
|
| + }..addAll(stream ? {} : {
|
| + 'x-upload-content-length' : '$totalLength',
|
| + }),
|
| + 'response' : stringResponse(
|
| + 200, {'location' : 'http://upload.com/'}, '')
|
| + });
|
| +
|
| + var lastEnd = 0;
|
| + for (int i = 0; i < numberOfChunks; i++) {
|
| + bool isLast = i == (numberOfChunks - 1);
|
| + var lengthMarker = stream && !isLast ? '*' : '$totalLength';
|
| +
|
| + int bytesToExpect = chunkSize;
|
| + if (isLast) {
|
| + bytesToExpect = numberOfBytesInLastChunk;
|
| + }
|
| +
|
| + var start = i * chunkSize;
|
| + var end = start + bytesToExpect;
|
| + var sublist = bytes.sublist(start, end);
|
| +
|
| + var firstContentRange =
|
| + 'bytes $start-${end-1}/$lengthMarker';
|
| + var firstRange =
|
| + 'bytes=0-${end-1}';
|
| +
|
| + // We issue [numberOfServerErrors] 503 errors first, and then a
|
| + // successfull response.
|
| + for (var j = 0; j < (numberOfServerErrors + 1); j++) {
|
| + bool successfullResponse = j == numberOfServerErrors;
|
| +
|
| + var response;
|
| + if (successfullResponse) {
|
| + var headers = isLast
|
| + ? { 'content-type' : 'application/json; charset=utf-8' }
|
| + : {'range' : firstRange };
|
| + response = stringResponse(isLast ? 200 : 308, headers, '');
|
| + } else {
|
| + var headers = {};
|
| + response = stringResponse(503, headers, '');
|
| + }
|
| +
|
| + expectations.add({
|
| + 'url' : 'http://upload.com/',
|
| + 'method' : 'PUT',
|
| + 'data' : sublist,
|
| + 'headers' : {
|
| + 'content-length' : '${sublist.length}',
|
| + 'content-range' : firstContentRange,
|
| + 'content-type' : 'foobar',
|
| + },
|
| + 'response' : response,
|
| + });
|
| + }
|
| + }
|
| + return expectations;
|
| + }
|
| +
|
| + List<List<int>> makeParts(List<int> bytes, List<int> splits) {
|
| + var parts = [];
|
| + int lastEnd = 0;
|
| + for (int i = 0; i < splits.length; i++) {
|
| + parts.add(bytes.sublist(lastEnd, splits[i]));
|
| + lastEnd = splits[i];
|
| + }
|
| + return parts;
|
| + }
|
| +
|
| + runTest(int chunkSizeInBlocks, int length, List splits, bool stream,
|
| + {int numberOfServerErrors: 0, resumableOptions,
|
| + int expectedErrorStatus, int messagesNrOfFailure}) {
|
| + int chunkSize = chunkSizeInBlocks * 256 * 1024;
|
| +
|
| + int i = 0;
|
| + var bytes = new List.generate(length, (i) => i % 256);
|
| + var parts = makeParts(bytes, splits);
|
| +
|
| + // Simulation of our server
|
| + var expectations = buildExpectations(
|
| + bytes, chunkSize, false,
|
| + numberOfServerErrors: numberOfServerErrors);
|
| + // If the server simulates 50X errors and the client resumes only
|
| + // a limited amount of time, we'll trunkate the number of requests
|
| + // the server expects.
|
| + // [The client will give up and if the server expects more, the test
|
| + // would timeout.]
|
| + if (expectedErrorStatus != null) {
|
| + expectations = expectations.sublist(0, messagesNrOfFailure);
|
| + }
|
| + httpMock.register(
|
| + expectAsync(serverRequestValidator(expectations),
|
| + count: expectations.length),
|
| + false);
|
| +
|
| + // Our client
|
| + var media = mediaFromByteArrays(parts);
|
| + if (resumableOptions == null) {
|
| + resumableOptions =
|
| + new ResumableUploadOptions(chunkSize: chunkSize);
|
| + }
|
| + var result = requester.request('/xyz',
|
| + 'POST',
|
| + uploadMedia: media,
|
| + uploadOptions: resumableOptions);
|
| + if (expectedErrorStatus != null) {
|
| + result.catchError(expectAsync((error) {
|
| + expect(error is DetailedApiRequestError, isTrue);
|
| + expect(error.status, equals(expectedErrorStatus));
|
| + }));
|
| + } else {
|
| + result.then(expectAsync((_) {}));
|
| + }
|
| + }
|
| +
|
| + Function backoffWrapper(int callCount) {
|
| + return expectAsync((int failedAttempts) {
|
| + var exp = ResumableUploadOptions.ExponentialBackoff;
|
| + Duration duration = exp(failedAttempts);
|
| + expect(duration.inSeconds, equals(1 << (failedAttempts - 1)));
|
| + return const Duration(milliseconds: 1);
|
| + }, count: callCount);
|
| + }
|
| +
|
| + test('length-small-block', () {
|
| + runTest(1, 10, [10], false);
|
| + });
|
| +
|
| + test('length-small-block-parts', () {
|
| + runTest(1, 20, [1, 2, 3, 4, 5, 6, 7, 19, 20], false);
|
| + });
|
| +
|
| + test('length-big-block', () {
|
| + runTest(1, 1024 * 1024, [1024*1024], false);
|
| + });
|
| +
|
| + test('length-big-block-parts', () {
|
| + runTest(1, 1024 * 1024,
|
| + [1,
|
| + 256*1024-1,
|
| + 256*1024,
|
| + 256*1024+1,
|
| + 1024*1024-1,
|
| + 1024*1024], false);
|
| + });
|
| +
|
| + test('stream-small-block', () {
|
| + runTest(1, 10, [10], true);
|
| + });
|
| +
|
| + test('stream-small-block-parts', () {
|
| + runTest(1, 20, [1, 2, 3, 4, 5, 6, 7, 19, 20], true);
|
| + });
|
| +
|
| + test('stream-big-block', () {
|
| + runTest(1, 1024 * 1024, [1024*1024], true);
|
| + });
|
| +
|
| + test('stream-big-block-parts', () {
|
| + runTest(1, 1024 * 1024,
|
| + [1,
|
| + 256*1024-1,
|
| + 256*1024,
|
| + 256*1024+1,
|
| + 1024*1024-1,
|
| + 1024*1024], true);
|
| + });
|
| +
|
| + test('stream-big-block-parts--with-server-error-recovery', () {
|
| + var numFailedAttempts = 4 * 3;
|
| + var options = new ResumableUploadOptions(
|
| + chunkSize: 256 * 1024, numberOfAttempts: 4,
|
| + backoffFunction: backoffWrapper(numFailedAttempts));
|
| + runTest(1, 1024 * 1024,
|
| + [1,
|
| + 256*1024-1,
|
| + 256*1024,
|
| + 256*1024+1,
|
| + 1024*1024-1,
|
| + 1024*1024],
|
| + true,
|
| + numberOfServerErrors: 3,
|
| + resumableOptions: options);
|
| + });
|
| +
|
| + test('stream-big-block-parts--server-error', () {
|
| + var numFailedAttempts = 2;
|
| + var options = new ResumableUploadOptions(
|
| + chunkSize: 256 * 1024, numberOfAttempts: 3,
|
| + backoffFunction: backoffWrapper(numFailedAttempts));
|
| + runTest(1, 1024 * 1024,
|
| + [1,
|
| + 256*1024-1,
|
| + 256*1024,
|
| + 256*1024+1,
|
| + 1024*1024-1,
|
| + 1024*1024],
|
| + true,
|
| + numberOfServerErrors: 3,
|
| + resumableOptions: options,
|
| + expectedErrorStatus: 503,
|
| + messagesNrOfFailure: 4);
|
| + });
|
| + });
|
| + });
|
| +
|
| + // Tests for error responses
|
| + group('request-errors', () {
|
| + makeTestError() {
|
| + // All errors from the [http.Client] propagate through.
|
| + // We use [TestError] to simulate it.
|
| + httpMock.register(expectAsync((http.BaseRequest request, string) {
|
| + return new Future.error(new TestError());
|
| + }), false);
|
| + }
|
| +
|
| + makeDetailed400Error() {
|
| + httpMock.register(expectAsync((http.BaseRequest request, string) {
|
| + return stringResponse(400,
|
| + responseHeaders,
|
| + '{"error" : {"code" : 42, "message": "foo"}}');
|
| + }), false);
|
| + }
|
| +
|
| + makeNormal199Error() {
|
| + httpMock.register(expectAsync((http.BaseRequest request, string) {
|
| + return stringResponse(199, {}, '');
|
| + }), false);
|
| + }
|
| +
|
| + makeInvalidContentTypeError() {
|
| + httpMock.register(expectAsync((http.BaseRequest request, string) {
|
| + var responseHeaders = { 'content-type' : 'image/png'};
|
| + return stringResponse(200, responseHeaders, '');
|
| + }), false);
|
| + }
|
| +
|
| +
|
| + test('normal-http-client', () {
|
| + makeTestError();
|
| + expect(requester.request('abc', 'GET'), throwsA(isTestError));
|
| + });
|
| +
|
| + test('normal-detailed-400', () {
|
| + makeDetailed400Error();
|
| + requester.request('abc', 'GET')
|
| + .catchError(expectAsync((error, stack) {
|
| + expect(error, isDetailedApiRequestError);
|
| + DetailedApiRequestError e = error;
|
| + expect(e.status, equals(42));
|
| + expect(e.message, equals('foo'));
|
| + }));
|
| + });
|
| +
|
| + test('normal-199', () {
|
| + makeNormal199Error();
|
| + expect(requester.request('abc', 'GET'), throwsA(isApiRequestError));
|
| + });
|
| +
|
| + test('normal-invalid-content-type', () {
|
| + makeInvalidContentTypeError();
|
| + expect(requester.request('abc', 'GET'), throwsA(isApiRequestError));
|
| + });
|
| +
|
| + var options = DownloadOptions.FullMedia;
|
| + test('media-http-client', () {
|
| + makeTestError();
|
| + expect(requester.request('abc', 'GET', downloadOptions: options),
|
| + throwsA(isTestError));
|
| + });
|
| +
|
| + test('media-detailed-400', () {
|
| + makeDetailed400Error();
|
| + requester.request('abc', 'GET')
|
| + .catchError(expectAsync((error, stack) {
|
| + expect(error, isDetailedApiRequestError);
|
| + DetailedApiRequestError e = error;
|
| + expect(e.status, equals(42));
|
| + expect(e.message, equals('foo'));
|
| + }));
|
| + });
|
| +
|
| + test('media-199', () {
|
| + makeNormal199Error();
|
| + expect(requester.request('abc', 'GET', downloadOptions: options),
|
| + throwsA(isApiRequestError));
|
| + });
|
| + });
|
| +
|
| +
|
| + // Tests for path/query parameters
|
| +
|
| + test('request-parameters-query', () {
|
| + var queryParams = {
|
| + 'a' : ['a1', 'a2'],
|
| + 's' : ['s1']
|
| + };
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}',
|
| + equals('http://example.com/base/abc?a=a1&a=a2&s=s1&alt=json'));
|
| + return stringResponse(200, responseHeaders, '');
|
| + }), true);
|
| + requester.request('abc', 'GET', queryParams: queryParams)
|
| + .then(expectAsync((response) {
|
| + expect(response, isNull);
|
| + }));
|
| + });
|
| +
|
| + test('request-parameters-path', () {
|
| + httpMock.register(expectAsync((http.BaseRequest request, json) {
|
| + expect(request.method, equals('GET'));
|
| + expect('${request.url}', equals(
|
| + 'http://example.com/base/s/foo/a1/a2/bar/s1/e?alt=json'));
|
| + return stringResponse(200, responseHeaders, '');
|
| + }), true);
|
| + requester.request('s/foo/a1/a2/bar/s1/e', 'GET')
|
| + .then(expectAsync((response) {
|
| + expect(response, isNull);
|
| + }));
|
| + });
|
| + });
|
| + });
|
| +}
|
|
|