blob: 0d86694c092c9e992a32b46ee7456b45f4b292e7 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. 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:async';
import 'dart:convert';
import 'dart:io';
import 'package:cupertino_http/cupertino_http.dart';
import 'package:integration_test/integration_test.dart';
import 'package:test/test.dart';
void testOnComplete(URLSessionConfiguration config) {
group('onComplete', () {
late HttpServer server;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.write('Hello World');
await request.response.close();
});
});
tearDown(() {
server.close();
});
test('success', () async {
final c = Completer<void>();
Error? actualError;
late URLSession actualSession;
late URLSessionTask actualTask;
final session =
URLSession.sessionWithConfiguration(config, onComplete: (s, t, e) {
actualSession = s;
actualTask = t;
actualError = e;
c.complete();
});
final task = session.dataTaskWithRequest(
URLRequest.fromUrl(Uri.parse('http://localhost:${server.port}')))
..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualError, null);
});
test('bad host', () async {
final c = Completer<void>();
Error? actualError;
late URLSession actualSession;
late URLSessionTask actualTask;
final session =
URLSession.sessionWithConfiguration(config, onComplete: (s, t, e) {
actualSession = s;
actualTask = t;
actualError = e;
c.complete();
});
final task = session.dataTaskWithRequest(
URLRequest.fromUrl(Uri.https('does-not-exist', '')))
..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(
actualError!.code,
anyOf(
-1001, // kCFURLErrorTimedOut
-1003, // kCFURLErrorCannotFindHost
));
});
});
}
void testOnResponse(URLSessionConfiguration config) {
group('onResponse', () {
late HttpServer server;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.write('Hello World');
await request.response.close();
});
});
tearDown(() {
server.close();
});
test('success', () async {
final c = Completer<void>();
late HTTPURLResponse actualResponse;
late URLSession actualSession;
late URLSessionTask actualTask;
final session =
URLSession.sessionWithConfiguration(config, onResponse: (s, t, r) {
actualSession = s;
actualTask = t;
actualResponse = r as HTTPURLResponse;
c.complete();
return URLSessionResponseDisposition.urlSessionResponseAllow;
});
final task = session.dataTaskWithRequest(
URLRequest.fromUrl(Uri.parse('http://localhost:${server.port}')))
..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualResponse.statusCode, 200);
});
test('bad host', () async {
// `onResponse` should not be called because there was no valid response.
final c = Completer<void>();
var called = false;
final session = URLSession.sessionWithConfiguration(config,
onComplete: (session, task, error) => c.complete(),
onResponse: (s, t, r) {
called = true;
return URLSessionResponseDisposition.urlSessionResponseAllow;
});
session
.dataTaskWithRequest(
URLRequest.fromUrl(Uri.https('does-not-exist', '')))
.resume();
await c.future;
expect(called, false);
});
});
}
void testOnData(URLSessionConfiguration config) {
group('onData', () {
late HttpServer server;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.write('Hello World');
await request.response.close();
});
});
tearDown(() {
server.close();
});
test('success', () async {
final c = Completer<void>();
final actualData = MutableData.empty();
late URLSession actualSession;
late URLSessionTask actualTask;
final session = URLSession.sessionWithConfiguration(config,
onComplete: (s, t, r) => c.complete(),
onData: (s, t, d) {
actualSession = s;
actualTask = t;
actualData.appendBytes(d.bytes);
});
final task = session.dataTaskWithRequest(
URLRequest.fromUrl(Uri.parse('http://localhost:${server.port}')))
..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualData.bytes, 'Hello World'.codeUnits);
});
});
}
void testOnFinishedDownloading(URLSessionConfiguration config) {
group('onFinishedDownloading', () {
late HttpServer server;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
request.response.headers.set('Content-Type', 'text/plain');
request.response.write('Hello World');
await request.response.close();
});
});
tearDown(() {
server.close();
});
test('success', () async {
final c = Completer<void>();
late URLSession actualSession;
late URLSessionDownloadTask actualTask;
late String actualContent;
final session = URLSession.sessionWithConfiguration(config,
onComplete: (s, t, r) => c.complete(),
onFinishedDownloading: (s, t, uri) {
actualSession = s;
actualTask = t;
actualContent = File.fromUri(uri).readAsStringSync();
});
final task = session.downloadTaskWithRequest(
URLRequest.fromUrl(Uri.parse('http://localhost:${server.port}')))
..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualContent, 'Hello World');
});
});
}
void testOnRedirect(URLSessionConfiguration config) {
group('onRedirect', () {
late HttpServer redirectServer;
setUp(() async {
// URI | Redirects TO
// ===========|==============
// ".../10" | ".../9"
// ".../9" | ".../8"
// ... | ...
// ".../1" | "/"
// "/" | <no redirect>
redirectServer = await HttpServer.bind('localhost', 0)
..listen((request) async {
if (request.requestedUri.pathSegments.isEmpty) {
unawaited(request.response.close());
} else {
final n = int.parse(request.requestedUri.pathSegments.last);
final nextPath = n - 1 == 0 ? '' : '${n - 1}';
unawaited(request.response.redirect(Uri.parse(
'http://localhost:${redirectServer.port}/$nextPath')));
}
});
});
tearDown(() {
redirectServer.close();
});
test('disallow redirect', () async {
final session = URLSession.sessionWithConfiguration(config,
onRedirect:
(redirectSession, redirectTask, redirectResponse, newRequest) =>
null);
final c = Completer<void>();
HTTPURLResponse? response;
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/100')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response!.statusCode, 302);
expect(response!.allHeaderFields['Location'],
'http://localhost:${redirectServer.port}/99');
expect(error, null);
});
test('use preposed redirect request', () async {
final session = URLSession.sessionWithConfiguration(config,
onRedirect:
(redirectSession, redirectTask, redirectResponse, newRequest) =>
newRequest);
final c = Completer<void>();
HTTPURLResponse? response;
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/1')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response!.statusCode, 200);
expect(error, null);
});
test('use custom redirect request', () async {
final session = URLSession.sessionWithConfiguration(
config,
onRedirect: (redirectSession, redirectTask, redirectResponse,
newRequest) =>
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/')),
);
final c = Completer<void>();
HTTPURLResponse? response;
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/100')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response!.statusCode, 200);
expect(error, null);
});
test('exception in http redirection', () async {
final session = URLSession.sessionWithConfiguration(
config,
onRedirect:
(redirectSession, redirectTask, redirectResponse, newRequest) {
throw UnimplementedError();
},
);
final c = Completer<void>();
HTTPURLResponse? response;
// ignore: unused_local_variable
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/100')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response!.statusCode, 302);
// TODO(https://github.com/dart-lang/ffigen/issues/386): Check that the
// error is set.
}, skip: 'Error not set for redirect exceptions.');
test('3 redirects', () async {
var redirectCounter = 0;
final session = URLSession.sessionWithConfiguration(
config,
onRedirect:
(redirectSession, redirectTask, redirectResponse, newRequest) {
expect(redirectResponse.statusCode, 302);
switch (redirectCounter) {
case 0:
expect(redirectResponse.allHeaderFields['Location'],
'http://localhost:${redirectServer.port}/2');
expect(newRequest.url,
Uri.parse('http://localhost:${redirectServer.port}/2'));
break;
case 1:
expect(redirectResponse.allHeaderFields['Location'],
'http://localhost:${redirectServer.port}/1');
expect(newRequest.url,
Uri.parse('http://localhost:${redirectServer.port}/1'));
break;
case 2:
expect(redirectResponse.allHeaderFields['Location'],
'http://localhost:${redirectServer.port}/');
expect(newRequest.url,
Uri.parse('http://localhost:${redirectServer.port}/'));
break;
}
++redirectCounter;
return newRequest;
},
);
final c = Completer<void>();
HTTPURLResponse? response;
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/3')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response!.statusCode, 200);
expect(error, null);
});
test('allow too many redirects', () async {
// The Foundation URL Loading System limits the number of redirects
// even when a redirect delegate is present and allows the redirect.
final session = URLSession.sessionWithConfiguration(
config,
onRedirect:
(redirectSession, redirectTask, redirectResponse, newRequest) =>
newRequest,
);
final c = Completer<void>();
HTTPURLResponse? response;
Error? error;
session.dataTaskWithCompletionHandler(
URLRequest.fromUrl(
Uri.parse('http://localhost:${redirectServer.port}/100')),
(d, r, e) {
response = r;
error = e;
c.complete();
}).resume();
await c.future;
expect(response, null);
expect(error!.code, -1007); // kCFURLErrorHTTPTooManyRedirects
});
});
}
void testOnWebSocketTaskOpened(URLSessionConfiguration config) {
group('onWebSocketTaskOpened', () {
late HttpServer server;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
if (request.requestedUri.queryParameters.containsKey('error')) {
request.response.statusCode = 500;
unawaited(request.response.close());
return;
}
final webSocket = await WebSocketTransformer.upgrade(
request,
protocolSelector: (l) => 'myprotocol',
);
await webSocket.close();
});
});
tearDown(() {
server.close();
});
test('with protocol', () async {
final c = Completer<void>();
late String? actualProtocol;
late URLSession actualSession;
late URLSessionWebSocketTask actualTask;
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (s, t, p) {
actualSession = s;
actualTask = t;
actualProtocol = p;
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}'))
..setValueForHttpHeaderField('Sec-WebSocket-Protocol', 'myprotocol');
final task = session.webSocketTaskWithRequest(request)..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualProtocol, 'myprotocol');
});
test('without protocol', () async {
final c = Completer<void>();
late String? actualProtocol;
late URLSession actualSession;
late URLSessionWebSocketTask actualTask;
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (s, t, p) {
actualSession = s;
actualTask = t;
actualProtocol = p;
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}'));
final task = session.webSocketTaskWithRequest(request)..resume();
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualProtocol, null);
});
test('server failure', () async {
final c = Completer<void>();
var onWebSocketTaskOpenedCalled = false;
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (s, t, p) {
onWebSocketTaskOpenedCalled = true;
}, onComplete: (s, t, e) {
expect(e, isNotNull);
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}?error=1'));
session.webSocketTaskWithRequest(request).resume();
await c.future;
expect(onWebSocketTaskOpenedCalled, false);
});
});
}
void testOnWebSocketTaskClosed(URLSessionConfiguration config) {
group('testOnWebSocketTaskClosed', () {
late HttpServer server;
late int? serverCode;
late String? serverReason;
setUp(() async {
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
if (request.requestedUri.queryParameters.containsKey('error')) {
request.response.statusCode = 500;
unawaited(request.response.close());
return;
}
final webSocket = await WebSocketTransformer.upgrade(
request,
);
await webSocket.close(serverCode, serverReason);
});
});
tearDown(() {
server.close();
});
test('close no code', () async {
final c = Completer<void>();
late int actualCloseCode;
late String? actualReason;
late URLSession actualSession;
late URLSessionWebSocketTask actualTask;
serverCode = null;
serverReason = null;
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (session, task, protocol) {},
onWebSocketTaskClosed: (session, task, closeCode, reason) {
actualSession = session;
actualTask = task;
actualCloseCode = closeCode!;
actualReason = utf8.decode(reason!.bytes);
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}'));
final task = session.webSocketTaskWithRequest(request)..resume();
expect(
task.receiveMessage(),
throwsA(isA<Error>()
.having((e) => e.code, 'code', 57 // Socket is not connected.
)));
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualCloseCode, 1005);
expect(actualReason, '');
});
test('close code', () async {
final c = Completer<void>();
late int actualCloseCode;
late String? actualReason;
late URLSession actualSession;
late URLSessionWebSocketTask actualTask;
serverCode = 4000;
serverReason = null;
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (session, task, protocol) {},
onWebSocketTaskClosed: (session, task, closeCode, reason) {
actualSession = session;
actualTask = task;
actualCloseCode = closeCode!;
actualReason = utf8.decode(reason!.bytes);
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}'));
final task = session.webSocketTaskWithRequest(request)..resume();
expect(
task.receiveMessage(),
throwsA(isA<Error>()
.having((e) => e.code, 'code', 57 // Socket is not connected.
)));
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualCloseCode, serverCode);
expect(actualReason, '');
});
test('close code and reason', () async {
final c = Completer<void>();
late int actualCloseCode;
late String? actualReason;
late URLSession actualSession;
late URLSessionWebSocketTask actualTask;
serverCode = 4000;
serverReason = 'no real reason';
final session = URLSession.sessionWithConfiguration(config,
onWebSocketTaskOpened: (session, task, protocol) {},
onWebSocketTaskClosed: (session, task, closeCode, reason) {
actualSession = session;
actualTask = task;
actualCloseCode = closeCode!;
actualReason = utf8.decode(reason!.bytes);
c.complete();
});
final request = MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}'));
final task = session.webSocketTaskWithRequest(request)..resume();
expect(
task.receiveMessage(),
throwsA(isA<Error>()
.having((e) => e.code, 'code', 57 // Socket is not connected.
)));
await c.future;
expect(actualSession, session);
expect(actualTask, task);
expect(actualCloseCode, serverCode);
expect(actualReason, serverReason);
});
});
}
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('backgroundSession', () {
final config =
URLSessionConfiguration.backgroundSession('backgroundSession');
testOnComplete(config);
testOnResponse(config);
testOnData(config);
// onRedirect is not called for background sessions.
testOnFinishedDownloading(config);
// WebSocket tasks are not supported in background sessions.
});
group('defaultSessionConfiguration', () {
final config = URLSessionConfiguration.defaultSessionConfiguration();
testOnComplete(config);
testOnResponse(config);
testOnData(config);
testOnRedirect(config);
testOnFinishedDownloading(config);
testOnWebSocketTaskOpened(config);
testOnWebSocketTaskClosed(config);
});
group('ephemeralSessionConfiguration', () {
final config = URLSessionConfiguration.ephemeralSessionConfiguration();
testOnComplete(config);
testOnResponse(config);
testOnData(config);
testOnRedirect(config);
testOnFinishedDownloading(config);
testOnWebSocketTaskOpened(config);
testOnWebSocketTaskClosed(config);
});
}