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); |
+ })); |
+ }); |
+ }); |
+ }); |
+} |