// 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') import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:mockito/mockito.dart'; import '../flutter_test_alternative.dart'; void main() { group(consolidateHttpClientResponseBytes, () { final Uint8List chunkOne = Uint8List.fromList(<int>[0, 1, 2, 3, 4, 5]); final Uint8List chunkTwo = Uint8List.fromList(<int>[6, 7, 8, 9, 10]); MockHttpClientResponse response; setUp(() { response = MockHttpClientResponse(); when(response.compressionState).thenReturn(HttpClientResponseCompressionState.notCompressed); when(response.listen( any, onDone: anyNamed('onDone'), onError: anyNamed('onError'), cancelOnError: anyNamed('cancelOnError'), )).thenAnswer((Invocation invocation) { final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>); final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object); final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback; final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool; return Stream<Uint8List>.fromIterable( <Uint8List>[chunkOne, chunkTwo]).listen( onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError, ); }); }); test('Converts an HttpClientResponse with contentLength to bytes', () async { when(response.contentLength) .thenReturn(chunkOne.length + chunkTwo.length); final List<int> bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('Converts a compressed HttpClientResponse with contentLength to bytes', () async { when(response.contentLength).thenReturn(chunkOne.length); final List<int> bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); }); test('Converts an HttpClientResponse without contentLength to bytes', () async { when(response.contentLength).thenReturn(-1); final List<int> bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[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; when(response.contentLength).thenReturn(syntheticTotal); final List<int> records = <int>[]; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int total) { records.addAll(<int>[cumulative, total]); }, ); expect(records, <int>[ chunkOne.length, syntheticTotal, chunkOne.length + chunkTwo.length, syntheticTotal, ]); }); test('forwards errors from HttpClientResponse', () async { when(response.listen( any, onDone: anyNamed('onDone'), onError: anyNamed('onError'), cancelOnError: anyNamed('cancelOnError'), )).thenAnswer((Invocation invocation) { final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>); final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object); final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback; final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool; return Stream<Uint8List>.fromFuture( Future<Uint8List>.error(Exception('Test Error'))) .listen( onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError, ); }); when(response.contentLength).thenReturn(-1); expect(consolidateHttpClientResponseBytes(response), throwsException); }); test('Propagates error to Future return value if onBytesReceived throws', () async { when(response.contentLength).thenReturn(-1); final Future<List<int>> result = consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int total) { throw 'misbehaving callback'; }, ); expect(result, throwsA(equals('misbehaving callback'))); }); group('when gzipped', () { final List<int> gzipped = gzip.encode(chunkOne.followedBy(chunkTwo).toList()); final List<int> gzippedChunkOne = gzipped.sublist(0, gzipped.length ~/ 2); final List<int> gzippedChunkTwo = gzipped.sublist(gzipped.length ~/ 2); setUp(() { when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed); when(response.listen( any, onDone: anyNamed('onDone'), onError: anyNamed('onError'), cancelOnError: anyNamed('cancelOnError'), )).thenAnswer((Invocation invocation) { final void Function(List<int>) onData = invocation.positionalArguments[0] as void Function(List<int>); final void Function(Object) onError = invocation.namedArguments[#onError] as void Function(Object); final VoidCallback onDone = invocation.namedArguments[#onDone] as VoidCallback; final bool cancelOnError = invocation.namedArguments[#cancelOnError] as bool; return Stream<List<int>>.fromIterable( <List<int>>[gzippedChunkOne, gzippedChunkTwo]).listen( onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError, ); }); }); test('Uncompresses GZIP bytes if autoUncompress is true and response.compressionState is compressed', () async { when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed); when(response.contentLength).thenReturn(gzipped.length); final List<int> bytes = await consolidateHttpClientResponseBytes(response); expect(bytes, <int>[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 { when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed); when(response.contentLength).thenReturn(gzipped.length); final List<int> bytes = await consolidateHttpClientResponseBytes(response, autoUncompress: false); expect(bytes, gzipped); }); test('Notifies onBytesReceived with gzipped numbers', () async { when(response.compressionState).thenReturn(HttpClientResponseCompressionState.compressed); when(response.contentLength).thenReturn(gzipped.length); final List<int> records = <int>[]; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int total) { records.addAll(<int>[cumulative, total]); }, ); expect(records, <int>[ 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; when(response.compressionState).thenReturn(HttpClientResponseCompressionState.decompressed); when(response.contentLength).thenReturn(syntheticTotal); final List<int> records = <int>[]; await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int total) { records.addAll(<int>[cumulative, total]); }, ); expect(records, <int>[ gzippedChunkOne.length, null, gzipped.length, null, ]); }); }); }); } class MockHttpClientResponse extends Mock implements HttpClientResponse {}