// Copyright 2014 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. @TestOn('!chrome') library; import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; final Uint8List chunkOne = Uint8List.fromList([0, 1, 2, 3, 4, 5]); final Uint8List chunkTwo = Uint8List.fromList([6, 7, 8, 9, 10]); void main() { group(consolidateHttpClientResponseBytes, () { late MockHttpClientResponse response; setUp(() { response = MockHttpClientResponse(chunkOne: chunkOne, chunkTwo: chunkTwo); }); test('Converts an HttpClientResponse with contentLength to bytes', () async { response.contentLength = chunkOne.length + chunkTwo.length; final List bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('Converts a compressed HttpClientResponse with contentLength to bytes', () async { response.contentLength = chunkOne.length; final List bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('Converts an HttpClientResponse without contentLength to bytes', () async { response.contentLength = -1; final List bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('Notifies onBytesReceived for every chunk of bytes', () async { final int syntheticTotal = (chunkOne.length + chunkTwo.length) * 2; response.contentLength = syntheticTotal; final records = []; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { records.addAll([cumulative, total]); }, ); expect(records, [ chunkOne.length, syntheticTotal, chunkOne.length + chunkTwo.length, syntheticTotal, ]); }); test('forwards errors from HttpClientResponse', () async { response = MockHttpClientResponse(error: Exception('Test Error')); response.contentLength = -1; expect(consolidateHttpClientResponseBytes(response), throwsException); }); test('Propagates error to Future return value if onBytesReceived throws', () async { response.contentLength = -1; final Future> result = consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { throw 'misbehaving callback'; }, ); expect(result, throwsA(equals('misbehaving callback'))); }); group('when gzipped', () { final List gzipped = gzip.encode(chunkOne.followedBy(chunkTwo).toList()); final List gzippedChunkOne = gzipped.sublist(0, gzipped.length ~/ 2); final List gzippedChunkTwo = gzipped.sublist(gzipped.length ~/ 2); setUp(() { response = MockHttpClientResponse(chunkOne: gzippedChunkOne, chunkTwo: gzippedChunkTwo); response.compressionState = HttpClientResponseCompressionState.compressed; }); test( 'Uncompresses GZIP bytes if autoUncompress is true and response.compressionState is compressed', () async { response.contentLength = gzipped.length; final List bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }, ); test( 'returns gzipped bytes if autoUncompress is false and response.compressionState is compressed', () async { response.contentLength = gzipped.length; final List bytes = await consolidateHttpClientResponseBytes( response, autoUncompress: false, ); expect(bytes, gzipped); }, ); test('Notifies onBytesReceived with gzipped numbers', () async { response.contentLength = gzipped.length; final records = []; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { records.addAll([cumulative, total]); }, ); expect(records, [ gzippedChunkOne.length, gzipped.length, gzipped.length, gzipped.length, ]); }); test( 'Notifies onBytesReceived with expectedContentLength of -1 if response.compressionState is decompressed', () async { final int syntheticTotal = (chunkOne.length + chunkTwo.length) * 2; response.compressionState = HttpClientResponseCompressionState.decompressed; response.contentLength = syntheticTotal; final records = []; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { records.addAll([cumulative, total]); }, ); expect(records, [gzippedChunkOne.length, null, gzipped.length, null]); }, ); }); }); } class MockHttpClientResponse extends Fake implements HttpClientResponse { MockHttpClientResponse({ this.error, this.chunkOne = const [], this.chunkTwo = const [], }); final dynamic error; final List chunkOne; final List chunkTwo; @override int contentLength = 0; @override HttpClientResponseCompressionState compressionState = HttpClientResponseCompressionState.notCompressed; @override StreamSubscription> listen( void Function(List event)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError, }) { if (error != null) { return Stream>.fromFuture( Future>.error(error as Object), ).listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError); } return Stream>.fromIterable(>[ chunkOne, chunkTwo, ]).listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError); } }