blob: 1d1b8628ccdc5d7d386852d7b90d80a636427370 [file] [log] [blame]
// Copyright 2013 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.
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
await ui.webOnlyInitializePlatform();
// Test successful HTTP roundtrips where the server returns a happy status
// code and a payload.
await _testSuccessfulPayloads();
// Test successful HTTP roundtrips where the server returned something other
// than a happy result (404 in particular is a common one).
await _testHttpErrorCodes();
// Test network errors that prevent the HTTP roundtrip to complete. These
// errors include invalid URLs, CORS issues, lost internet access, etc.
await _testNetworkErrors();
test('window.fetch is banned', () async {
expect(
() => domWindow.fetch('/'),
throwsA(isA<UnsupportedError>()),
);
});
}
Future<void> _testSuccessfulPayloads() async {
test('httpFetch fetches a text file', () async {
final HttpFetchResponse response = await httpFetch('/lib/src/engine/alarm_clock.dart');
expect(response.status, 200);
expect(response.contentLength, greaterThan(0));
expect(response.hasPayload, isTrue);
expect(response.payload, isNotNull);
expect(response.url, '/lib/src/engine/alarm_clock.dart');
expect(
await response.text(),
startsWith('''
// Copyright 2013 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.'''),
);
});
test('httpFetch fetches a binary file as ByteBuffer', () async {
final HttpFetchResponse response = await httpFetch('/test_images/1x1.png');
expect(response.status, 200);
expect(response.contentLength, greaterThan(0));
expect(response.hasPayload, isTrue);
expect(response.payload, isNotNull);
expect(response.url, '/test_images/1x1.png');
expect(
(await response.asByteBuffer()).asUint8List().sublist(0, 8),
<int>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
);
});
test('httpFetch fetches a binary file as Uint8List', () async {
final HttpFetchResponse response = await httpFetch('/test_images/1x1.png');
expect(response.status, 200);
expect(response.contentLength, greaterThan(0));
expect(response.hasPayload, isTrue);
expect(response.payload, isNotNull);
expect(response.url, '/test_images/1x1.png');
expect(
(await response.asUint8List()).sublist(0, 8),
<int>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
);
});
test('httpFetch fetches json', () async {
final HttpFetchResponse response = await httpFetch('/test_images/');
expect(response.status, 200);
expect(response.contentLength, greaterThan(0));
expect(response.hasPayload, isTrue);
expect(response.payload, isNotNull);
expect(response.url, '/test_images/');
expect(
await response.json(),
isA<List<Object?>>(),
);
});
test('httpFetch reads data in chunks', () async {
// There is no guarantee that the server will actually serve the data in any
// particular chunk sizes, but breaking up the data in _some_ way does cause
// it to chunk it.
const List<List<int>> lengthAndChunks = <List<int>>[
<int>[0, 0],
<int>[10, 10],
<int>[1000, 100],
<int>[10000, 1000],
<int>[100000, 10000],
];
for (final List<int> lengthAndChunk in lengthAndChunks) {
final int length = lengthAndChunk.first;
final int chunkSize = lengthAndChunk.last;
final String url = '/long_test_payload?length=$length&chunk=$chunkSize';
final HttpFetchResponse response = await httpFetch(url);
expect(response.status, 200);
expect(response.contentLength, length);
expect(response.hasPayload, isTrue);
expect(response.payload, isNotNull);
expect(response.url, url);
final List<int> result = <int>[];
await response.payload.read<Uint8List>(result.addAll);
expect(result, hasLength(length));
expect(
result,
List<int>.generate(length, (int i) => i & 0xFF),
);
}
});
test('httpFetchText fetches a text file', () async {
final String text = await httpFetchText('/lib/src/engine/alarm_clock.dart');
expect(
text,
startsWith('''
// Copyright 2013 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.'''),
);
});
test('httpFetchByteBuffer fetches a binary file as ByteBuffer', () async {
final ByteBuffer response = await httpFetchByteBuffer('/test_images/1x1.png');
expect(
response.asUint8List().sublist(0, 8),
<int>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
);
});
test('httpFetchJson fetches json', () async {
final Object? json = await httpFetchJson('/test_images/');
expect(json, isA<List<Object?>>());
});
}
Future<void> _testHttpErrorCodes() async {
test('httpFetch throws HttpFetchNoPayloadError on 404', () async {
final HttpFetchResponse response = await httpFetch('/file_not_found');
expect(response.status, 404);
expect(response.hasPayload, isFalse);
expect(response.url, '/file_not_found');
try {
// Attempting to read the payload when there isn't one should result in
// HttpFetchNoPayloadError thrown.
response.payload;
fail('Expected HttpFetchNoPayloadError');
} on HttpFetchNoPayloadError catch(error) {
expect(error.status, 404);
expect(error.url, '/file_not_found');
expect(
error.toString(),
'Flutter Web engine failed to fetch "/file_not_found". '
'HTTP request succeeded, but the server responded with HTTP status 404.',
);
}
});
test('httpFetch* functions throw HttpFetchNoPayloadError on 404', () async {
final List<AsyncCallback> testFunctions = <AsyncCallback>[
() async => httpFetchText('/file_not_found'),
() async => httpFetchByteBuffer('/file_not_found'),
() async => httpFetchJson('/file_not_found'),
];
for (final AsyncCallback testFunction in testFunctions) {
try {
await testFunction();
fail('Expected HttpFetchNoPayloadError');
} on HttpFetchNoPayloadError catch(error) {
expect(error.status, 404);
expect(error.url, '/file_not_found');
expect(
error.toString(),
'Flutter Web engine failed to fetch "/file_not_found". '
'HTTP request succeeded, but the server responded with HTTP status 404.',
);
}
}
});
}
Future<void> _testNetworkErrors() async {
test('httpFetch* functions throw HttpFetchError on network errors', () async {
// Fetch throws the error this test wants on URLs with user credentials.
const String badUrl = 'https://user:password@example.com/';
final List<AsyncCallback> testFunctions = <AsyncCallback>[
() async => httpFetch(badUrl),
() async => httpFetchText(badUrl),
() async => httpFetchByteBuffer(badUrl),
() async => httpFetchJson(badUrl),
];
for (final AsyncCallback testFunction in testFunctions) {
try {
await testFunction();
fail('Expected HttpFetchError');
} on HttpFetchError catch(error) {
expect(error.url, badUrl);
expect(
error.toString(),
// Browsers agree on throwing a TypeError, but they disagree on the
// error message. So this only checks for the common error prefix, but
// not the entire error message.
startsWith(
'Flutter Web engine failed to complete HTTP request to fetch '
'"https://user:password@example.com/": TypeError: '
),
);
}
}
});
}
typedef AsyncCallback = Future<void> Function();