blob: 7b892f910aaddc5c41862afee423b07a8c5db943 [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:io';
import 'package:cupertino_http/cupertino_http.dart';
import 'package:integration_test/integration_test.dart';
import 'package:objective_c/objective_c.dart';
import 'package:test/test.dart';
void testWebSocketTask() {
group('websocket', () {
late HttpServer server;
int? lastCloseCode;
String? lastCloseReason;
setUp(() async {
lastCloseCode = null;
lastCloseReason = null;
server = await HttpServer.bind('localhost', 0)
..listen((request) {
if (request.uri.path.endsWith('error')) {
request.response.statusCode = 500;
request.response.close();
} else {
WebSocketTransformer.upgrade(request).then(
(websocket) => websocket.listen(
(event) {
final code = request.uri.queryParameters['code'];
final reason = request.uri.queryParameters['reason'];
websocket.add(event);
if (!request.uri.queryParameters.containsKey('noclose')) {
websocket.close(
code == null ? null : int.parse(code),
reason,
);
}
},
onDone: () {
lastCloseCode = websocket.closeCode;
lastCloseReason = websocket.closeReason;
},
),
);
}
});
});
tearDown(() async {
await server.close();
});
test('background session', () {
final session = URLSession.sessionWithConfiguration(
URLSessionConfiguration.backgroundSession('background'),
);
expect(
() => session.webSocketTaskWithRequest(
URLRequest.fromUrl(
Uri.parse('ws://localhost:${server.port}/?noclose'),
),
),
throwsUnsupportedError,
);
session.finishTasksAndInvalidate();
});
test(
'client code and reason',
() async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(
Uri.parse('ws://localhost:${server.port}/?noclose'),
),
)..resume();
await task.sendMessage(
URLSessionWebSocketMessage.fromString('Hello World!'),
);
await task.receiveMessage();
task.cancelWithCloseCode(4998, 'Bye'.codeUnits.toNSData());
// Allow the server to run and save the close code.
while (lastCloseCode == null) {
await Future<void>.delayed(const Duration(milliseconds: 10));
}
expect(lastCloseCode, 4998);
expect(lastCloseReason, 'Bye');
},
skip: Platform.isMacOS
? 'https://github.com/dart-lang/http/issues/1814'
: false,
);
test('server code and reason', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(
Uri.parse('ws://localhost:${server.port}/?code=4999&reason=fun'),
),
)..resume();
await task.sendMessage(
URLSessionWebSocketMessage.fromString('Hello World!'),
);
await task.receiveMessage();
await expectLater(
task.receiveMessage(),
throwsA(
isA<NSError>().having(
(e) => e.code,
'code',
57, // NOT_CONNECTED
),
),
);
expect(task.closeCode, 4999);
expect(task.closeReason!.toList(), 'fun'.codeUnits);
task.cancel();
session.finishTasksAndInvalidate();
});
test('data message', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')),
)..resume();
await task.sendMessage(
URLSessionWebSocketMessage.fromData([1, 2, 3].toNSData()),
);
final receivedMessage = await task.receiveMessage();
expect(
receivedMessage.type,
NSURLSessionWebSocketMessageType.NSURLSessionWebSocketMessageTypeData,
);
expect(receivedMessage.data!.toList(), [1, 2, 3]);
expect(receivedMessage.string, null);
task.cancel();
});
test('text message', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')),
)..resume();
await task.sendMessage(
URLSessionWebSocketMessage.fromString('Hello World!'),
);
final receivedMessage = await task.receiveMessage();
expect(
receivedMessage.type,
NSURLSessionWebSocketMessageType.NSURLSessionWebSocketMessageTypeString,
);
expect(receivedMessage.data, null);
expect(receivedMessage.string, 'Hello World!');
task.cancel();
});
test('send failure', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}/error')),
)..resume();
await expectLater(
task.sendMessage(URLSessionWebSocketMessage.fromString('Hello World!')),
throwsA(
isA<NSError>().having(
(e) => e.code,
'code',
-1011, // NSURLErrorBadServerResponse
),
),
);
task.cancel();
});
test('receive failure', () async {
final session = URLSession.sharedSession();
final task = session.webSocketTaskWithRequest(
URLRequest.fromUrl(Uri.parse('ws://localhost:${server.port}')),
)..resume();
await task.sendMessage(
URLSessionWebSocketMessage.fromString('Hello World!'),
);
await task.receiveMessage();
await expectLater(
task.receiveMessage(),
throwsA(
isA<NSError>().having(
(e) => e.code,
'code',
57, // NOT_CONNECTED
),
),
);
task.cancel();
});
});
}
void testURLSessionTaskCommon(
URLSessionTask Function(URLSession session, Uri url) f, {
bool suspendedAfterCancel = false,
}) {
group('task states', () {
late HttpServer server;
late URLSessionTask task;
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();
});
final session = URLSession.sharedSession();
task = f(session, Uri.parse('http://localhost:${server.port}'));
});
tearDown(() {
task.cancel();
server.close();
});
test('starts suspended', () {
expect(task.state, NSURLSessionTaskState.NSURLSessionTaskStateSuspended);
expect(task.response, null);
task.toString(); // Just verify that there is no crash.
});
test('resume to running', () {
task.resume();
expect(task.state, NSURLSessionTaskState.NSURLSessionTaskStateRunning);
expect(task.response, null);
task.toString(); // Just verify that there is no crash.
});
test(
'cancel',
() {
task.cancel();
if (suspendedAfterCancel) {
expect(
task.state,
NSURLSessionTaskState.NSURLSessionTaskStateSuspended,
);
} else {
expect(
task.state,
NSURLSessionTaskState.NSURLSessionTaskStateCanceling,
);
}
expect(task.response, null);
task.toString(); // Just verify that there is no crash.
},
// After the request cancellation is complete, the task state will be
// `NSURLSessionTaskStateCompleted`. So run the test a few times to allow
// the timing to work out (which still isn't a great approach but I can't
// think of a better way).
retry: 5,
);
test('completed', () async {
task.resume();
while (task.state !=
NSURLSessionTaskState.NSURLSessionTaskStateCompleted) {
// Let the event loop run.
await Future<void>(() {});
}
});
});
group('task completed', () {
late HttpServer server;
late URLSessionTask task;
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();
});
final session = URLSession.sharedSession();
task =
session.dataTaskWithRequest(
MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}/mypath'),
)
..httpMethod = 'POST'
..httpBody = [1, 2, 3].toNSData(),
)
..prefersIncrementalDelivery = false
..priority = 0.2
..taskDescription = 'my task description'
..resume();
while (task.state !=
NSURLSessionTaskState.NSURLSessionTaskStateCompleted) {
// Let the event loop run.
await Future<void>(() {});
}
});
tearDown(() {
task.cancel();
server.close();
});
test('priority', () async {
expect(task.priority, inInclusiveRange(0.19, 0.21));
});
test('current request', () async {
expect(task.currentRequest!.url!.path, '/mypath');
});
test('original request', () async {
expect(task.originalRequest!.url!.path, '/mypath');
});
test('has response', () async {
expect(task.response, isA<HTTPURLResponse>());
});
test('no error', () async {
expect(task.error, null);
});
test('countOfBytesExpectedToReceive - no content length set', () async {
expect(task.countOfBytesExpectedToReceive, -1);
});
test('countOfBytesReceived', () async {
expect(task.countOfBytesReceived, 11);
});
test('countOfBytesExpectedToSend', () async {
expect(task.countOfBytesExpectedToSend, 3);
});
test('countOfBytesSent', () async {
expect(task.countOfBytesSent, 3);
});
test('taskDescription', () {
expect(task.taskDescription, 'my task description');
});
test('taskIdentifier', () {
task.taskIdentifier; // Just verify that there is no crash.
});
test('prefersIncrementalDelivery', () {
expect(task.prefersIncrementalDelivery, false);
});
test('toString', () {
task.toString(); // Just verify that there is no crash.
});
});
group('task failed', () {
late URLSessionTask task;
setUp(() async {
final session = URLSession.sharedSession();
task = session.dataTaskWithRequest(
MutableURLRequest.fromUrl(Uri.parse('http://notarealserver')),
)..resume();
while (task.state !=
NSURLSessionTaskState.NSURLSessionTaskStateCompleted) {
// Let the event loop run.
await Future<void>(() {});
}
});
tearDown(() {
task.cancel();
});
test('no response', () async {
expect(task.response, null);
});
test('no error', () async {
expect(task.error!.code, -1003); // CannotFindHost
});
test('toString', () {
task.toString(); // Just verify that there is no crash.
});
});
group('task redirect', () {
late HttpServer server;
late URLSessionTask task;
setUp(() async {
// The task will request http://localhost:XXX/launch, which will be
// redirected to http://localhost:XXX/landed.
server = (await HttpServer.bind('localhost', 0))
..listen((request) async {
await request.drain<void>();
if (request.requestedUri.path != '/landed') {
await request.response.redirect(Uri(path: '/landed'));
return;
}
request.response.headers.set('Content-Type', 'text/plain');
request.response.write('Hello World');
await request.response.close();
});
final session = URLSession.sharedSession();
task = session.dataTaskWithRequest(
MutableURLRequest.fromUrl(
Uri.parse('http://localhost:${server.port}/launch'),
),
)..resume();
while (task.state !=
NSURLSessionTaskState.NSURLSessionTaskStateCompleted) {
// Let the event loop run.
await Future<void>(() {});
}
});
tearDown(() {
task.cancel();
server.close();
});
test('current request', () async {
expect(task.currentRequest!.url!.path, '/landed');
});
test('original request', () async {
expect(task.originalRequest!.url!.path, '/launch');
});
test('toString', () {
task.toString(); // Just verify that there is no crash.
});
});
}
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('data task', () {
testURLSessionTaskCommon(
(session, uri) => session.dataTaskWithRequest(URLRequest.fromUrl(uri)),
);
});
group('download task', () {
testURLSessionTaskCommon(
(session, uri) =>
session.downloadTaskWithRequest(URLRequest.fromUrl(uri)),
);
});
group('websocket task', () {
testURLSessionTaskCommon(
(session, uri) =>
session.webSocketTaskWithRequest(URLRequest.fromUrl(uri)),
suspendedAfterCancel: true,
);
});
testWebSocketTask();
}