net_test.dart 12.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:convert';
6

7
import 'package:fake_async/fake_async.dart';
8 9 10
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
11
import 'package:flutter_tools/src/base/io.dart' as io;
12
import 'package:flutter_tools/src/base/io.dart';
13
import 'package:flutter_tools/src/base/logger.dart';
14
import 'package:flutter_tools/src/base/net.dart';
15
import 'package:flutter_tools/src/base/platform.dart';
16

17
import '../../src/common.dart';
18
import '../../src/fake_http_client.dart';
19 20

void main() {
21
  late BufferLogger testLogger;
22 23

  setUp(() {
24
    testLogger = BufferLogger.test();
25 26 27 28 29 30
  });

  Net createNet(io.HttpClient client) {
    return Net(
      httpClientFactory: () => client,
      logger: testLogger,
31
      platform: FakePlatform(),
32 33 34
    );
  }

35 36
  group('successful fetch', () {
    const String responseString = 'response string';
37
    late List<int> responseData;
38 39 40 41 42

    setUp(() {
      responseData = utf8.encode(responseString);
    });

43
    testWithoutContext('fetchUrl() gets the data', () async {
44 45 46 47 48 49 50 51
      final Net net = createNet(
        FakeHttpClient.list(<FakeRequest>[
          FakeRequest(Uri.parse('http://example.invalid/'), response: FakeResponse(
            body: utf8.encode(responseString),
          )),
        ])
      );

52
      final List<int>? data = await net.fetchUrl(Uri.parse('http://example.invalid/'));
53 54 55
      expect(data, equals(responseData));
    });

56
    testWithoutContext('fetchUrl(destFile) writes the data to a file', () async {
57 58 59 60 61 62 63 64 65
      final Net net = createNet(
        FakeHttpClient.list(<FakeRequest>[
          FakeRequest(Uri.parse('http://example.invalid/'), response: FakeResponse(
            body: utf8.encode(responseString),
          )),
        ])
      );
      final MemoryFileSystem fileSystem = MemoryFileSystem.test();
      final File destFile = fileSystem.file('dest_file')..createSync();
66
      final List<int>? data = await net.fetchUrl(
67 68 69 70
        Uri.parse('http://example.invalid/'),
        destFile: destFile,
      );
      expect(data, equals(<int>[]));
71
      expect(destFile.readAsStringSync(), responseString);
72 73 74
    });
  });

75
  testWithoutContext('retry from 500', () async {
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)),
        FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)),
        FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)),
        FakeRequest(Uri.parse('http://example.invalid/'), response: const FakeResponse(statusCode: io.HttpStatus.internalServerError)),
      ])
    );

    await net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 4, durationOverride: Duration.zero);

    expect(testLogger.statusText,
      'Download failed -- attempting retry 1 in 1 second...\n'
      'Download failed -- attempting retry 2 in 2 seconds...\n'
      'Download failed -- attempting retry 3 in 4 seconds...\n'
      'Download failed -- retry 4\n',
    );
93 94 95
    expect(testLogger.errorText, isEmpty);
  });

96
  testWithoutContext('retry from network error', () async {
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, responseError: const io.SocketException('test')),
        FakeRequest(invalid, responseError: const io.SocketException('test')),
        FakeRequest(invalid, responseError: const io.SocketException('test')),
        FakeRequest(invalid, responseError: const io.SocketException('test')),
      ])
    );

    await net.fetchUrl(Uri.parse('http://example.invalid/'), maxAttempts: 4, durationOverride: Duration.zero);

    expect(testLogger.statusText,
      'Download failed -- attempting retry 1 in 1 second...\n'
      'Download failed -- attempting retry 2 in 2 seconds...\n'
      'Download failed -- attempting retry 3 in 4 seconds...\n'
      'Download failed -- retry 4\n',
    );
115 116
    expect(testLogger.errorText, isEmpty);
  });
117

118
  testWithoutContext('retry from SocketException', () async {
119 120 121 122 123 124 125 126 127
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, responseError: const io.SocketException('')),
        FakeRequest(invalid, responseError: const io.SocketException('')),
        FakeRequest(invalid, responseError: const io.SocketException('')),
        FakeRequest(invalid, responseError: const io.SocketException('')),
      ])
    );
128
    String? error;
129
    FakeAsync().run((FakeAsync time) {
130
      net.fetchUrl(invalid).then((List<int>? value) async {
131 132 133 134 135 136 137 138 139 140
        error = 'test completed unexpectedly';
      }, onError: (dynamic exception) {
        error = 'test failed unexpectedly: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText,
        'Download failed -- attempting retry 1 in 1 second...\n'
        'Download failed -- attempting retry 2 in 2 seconds...\n'
        'Download failed -- attempting retry 3 in 4 seconds...\n'
141
        'Download failed -- attempting retry 4 in 8 seconds...\n',
142 143 144 145 146 147 148
      );
    });
    expect(testLogger.errorText, isEmpty);
    expect(error, isNull);
    expect(testLogger.traceText, contains('Download error: SocketException'));
  });

149
  testWithoutContext('no retry from HandshakeException', () async {
150 151 152
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
153 154 155 156
        FakeRequest(invalid, responseError: const io.HandshakeException()),
        FakeRequest(invalid, responseError: const io.HandshakeException()),
        FakeRequest(invalid, responseError: const io.HandshakeException()),
        FakeRequest(invalid, responseError: const io.HandshakeException()),
157 158
      ])
    );
159
    String? error;
160
    FakeAsync().run((FakeAsync time) {
161
      net.fetchUrl(invalid).then((List<int>? value) async {
162 163 164 165 166 167 168 169 170 171 172
        error = 'test completed unexpectedly';
      }, onError: (dynamic exception) {
        error = 'test failed: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText, '');
    });
    expect(error, startsWith('test failed'));
    expect(testLogger.traceText, contains('HandshakeException'));
  });
173

174
  testWithoutContext('check for bad override on ArgumentError', () async {
175
    final Uri invalid = Uri.parse('example.invalid/');
176
    final Net net = Net(
177 178 179 180 181
      httpClientFactory: () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(invalid, responseError: ArgumentError()),
        ]);
      },
182
      logger: testLogger,
183 184
      platform: FakePlatform(
        environment: <String, String>{
185 186
          'FLUTTER_STORAGE_BASE_URL': 'example.invalid',
        },
187
      ),
188
    );
189
    String? error;
190
    FakeAsync().run((FakeAsync time) {
191
      net.fetchUrl(Uri.parse('example.invalid/')).then((List<int>? value) async {
192 193 194 195 196 197 198 199 200 201 202 203 204
        error = 'test completed unexpectedly';
      }, onError: (dynamic exception) {
        error = 'test failed: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText, '');
    });
    expect(error, startsWith('test failed'));
    expect(testLogger.errorText, contains('Invalid argument'));
    expect(error, contains('FLUTTER_STORAGE_BASE_URL'));
  });

205
  testWithoutContext('retry from HttpException', () async {
206 207 208 209 210 211 212 213 214
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
      ])
    );
215
    String? error;
216
    FakeAsync().run((FakeAsync time) {
217
      net.fetchUrl(invalid).then((List<int>? value) async {
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
        error = 'test completed unexpectedly';
      }, onError: (dynamic exception) {
        error = 'test failed unexpectedly: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText,
        'Download failed -- attempting retry 1 in 1 second...\n'
        'Download failed -- attempting retry 2 in 2 seconds...\n'
        'Download failed -- attempting retry 3 in 4 seconds...\n'
        'Download failed -- attempting retry 4 in 8 seconds...\n',
      );
    });
    expect(testLogger.errorText, isEmpty);
    expect(error, isNull);
    expect(testLogger.traceText, contains('Download error: HttpException'));
234 235
  });

236
  testWithoutContext('retry from HttpException when request throws', () async {
237 238 239 240 241 242 243 244 245
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
        FakeRequest(invalid, responseError: const io.HttpException('')),
      ])
    );
246
    String? error;
247
    FakeAsync().run((FakeAsync time) {
248
      net.fetchUrl(invalid).then((List<int>? value) async {
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
        error = 'test completed unexpectedly';
      }, onError: (dynamic exception) {
        error = 'test failed unexpectedly: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText,
        'Download failed -- attempting retry 1 in 1 second...\n'
        'Download failed -- attempting retry 2 in 2 seconds...\n'
        'Download failed -- attempting retry 3 in 4 seconds...\n'
        'Download failed -- attempting retry 4 in 8 seconds...\n',
      );
    });
    expect(testLogger.errorText, isEmpty);
    expect(error, isNull);
    expect(testLogger.traceText, contains('Download error: HttpException'));
265 266
  });

267
  testWithoutContext('max attempts', () async {
268 269 270 271 272 273 274 275 276 277 278 279 280 281
    final Uri invalid = Uri.parse('http://example.invalid/');
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, response: const FakeResponse(
          statusCode: HttpStatus.internalServerError,
        )),
        FakeRequest(invalid, response: const FakeResponse(
          statusCode: HttpStatus.internalServerError,
        )),
        FakeRequest(invalid, response: const FakeResponse(
          statusCode: HttpStatus.internalServerError,
        )),
      ])
    );
282 283
    String? error;
    List<int>? actualResult;
284
    FakeAsync().run((FakeAsync time) {
285
      net.fetchUrl(invalid, maxAttempts: 3).then((List<int>? value) async {
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
        actualResult = value;
      }, onError: (dynamic exception) {
        error = 'test failed unexpectedly: $exception';
      });
      expect(testLogger.statusText, '');
      time.elapse(const Duration(milliseconds: 10000));
      expect(testLogger.statusText,
        'Download failed -- attempting retry 1 in 1 second...\n'
        'Download failed -- attempting retry 2 in 2 seconds...\n'
        'Download failed -- retry 3\n',
      );
    });
    expect(testLogger.errorText, isEmpty);
    expect(error, isNull);
    expect(actualResult, isNull);
  });

303
  testWithoutContext('remote file non-existent', () async {
304
    final Uri invalid = Uri.parse('http://example.invalid/');
305 306 307 308 309 310 311
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(invalid, method: HttpMethod.head, response: const FakeResponse(
          statusCode: HttpStatus.notFound,
        )),
      ])
    );
312
    final bool result = await net.doesRemoteFileExist(invalid);
313 314 315
    expect(result, false);
  });

316
  testWithoutContext('remote file server error', () async {
317
    final Uri valid = Uri.parse('http://example.valid/');
318 319 320 321 322 323 324
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(valid, method: HttpMethod.head, response: const FakeResponse(
          statusCode: HttpStatus.internalServerError,
        )),
      ])
    );
325
    final bool result = await net.doesRemoteFileExist(valid);
326 327 328
    expect(result, false);
  });

329
  testWithoutContext('remote file exists', () async {
330
    final Uri valid = Uri.parse('http://example.valid/');
331 332 333 334 335
    final Net net = createNet(
      FakeHttpClient.list(<FakeRequest>[
        FakeRequest(valid, method: HttpMethod.head),
      ])
    );
336
    final bool result = await net.doesRemoteFileExist(valid);
337 338
    expect(result, true);
  });
339
}