| // Copyright (c) 2014, 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. |
| |
| library shelf_io_test; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:http/http.dart' as http; |
| import 'package:scheduled_test/scheduled_test.dart'; |
| import 'package:shelf/shelf.dart'; |
| import 'package:shelf/shelf_io.dart' as shelf_io; |
| |
| import 'test_util.dart'; |
| |
| void main() { |
| test('sync handler returns a value to the client', () { |
| _scheduleServer(syncHandler); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /'); |
| }); |
| }); |
| |
| test('async handler returns a value to the client', () { |
| _scheduleServer(asyncHandler); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /'); |
| }); |
| }); |
| |
| test('sync null response leads to a 500', () { |
| _scheduleServer((request) => null); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| expect(response.body, 'Internal Server Error'); |
| }); |
| }); |
| |
| test('async null response leads to a 500', () { |
| _scheduleServer((request) => new Future.value(null)); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| expect(response.body, 'Internal Server Error'); |
| }); |
| }); |
| |
| test('thrown error leads to a 500', () { |
| _scheduleServer((request) { |
| throw new UnsupportedError('test'); |
| }); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| expect(response.body, 'Internal Server Error'); |
| }); |
| }); |
| |
| test('async error leads to a 500', () { |
| _scheduleServer((request) { |
| return new Future.error('test'); |
| }); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| expect(response.body, 'Internal Server Error'); |
| }); |
| }); |
| |
| test('Request is populated correctly', () { |
| var path = '/foo/bar?qs=value'; |
| |
| _scheduleServer((request) { |
| expect(request.contentLength, 0); |
| expect(request.method, 'GET'); |
| |
| var expectedUrl = 'http://localhost:$_serverPort$path'; |
| expect(request.requestedUri, Uri.parse(expectedUrl)); |
| |
| expect(request.url.path, '/foo/bar'); |
| expect(request.url.pathSegments, ['foo', 'bar']); |
| expect(request.protocolVersion, '1.1'); |
| expect(request.url.query, 'qs=value'); |
| expect(request.scriptName, ''); |
| |
| return syncHandler(request); |
| }); |
| |
| return schedule(() => http.get('http://localhost:$_serverPort$path')) |
| .then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /foo/bar'); |
| }); |
| }); |
| |
| test('custom response headers are received by the client', () { |
| _scheduleServer((request) { |
| return new Response.ok('Hello from /', headers: { |
| 'test-header': 'test-value', |
| 'test-list': 'a, b, c' |
| }); |
| }); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.headers['test-header'], 'test-value'); |
| expect(response.body, 'Hello from /'); |
| }); |
| }); |
| |
| test('custom status code is received by the client', () { |
| _scheduleServer((request) { |
| return new Response(299, body: 'Hello from /'); |
| }); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, 299); |
| expect(response.body, 'Hello from /'); |
| }); |
| }); |
| |
| test('custom request headers are received by the handler', () { |
| _scheduleServer((request) { |
| expect(request.headers, containsPair('custom-header', 'client value')); |
| |
| // dart:io HttpServer splits multi-value headers into an array |
| // validate that they are combined correctly |
| expect(request.headers, containsPair('multi-header', 'foo,bar,baz')); |
| return syncHandler(request); |
| }); |
| |
| var headers = { |
| 'custom-header': 'client value', |
| 'multi-header': 'foo,bar,baz' |
| }; |
| |
| return _scheduleGet(headers: headers).then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /'); |
| }); |
| }); |
| |
| test('post with empty content', () { |
| _scheduleServer((request) { |
| expect(request.mimeType, isNull); |
| expect(request.encoding, isNull); |
| expect(request.method, 'POST'); |
| expect(request.contentLength, 0); |
| |
| return request.readAsString().then((body) { |
| expect(body, ''); |
| return syncHandler(request); |
| }); |
| }); |
| |
| return _schedulePost().then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.stream.bytesToString(), completion('Hello from /')); |
| }); |
| }); |
| |
| test('post with request content', () { |
| _scheduleServer((request) { |
| expect(request.mimeType, 'text/plain'); |
| expect(request.encoding, UTF8); |
| expect(request.method, 'POST'); |
| expect(request.contentLength, 9); |
| |
| return request.readAsString().then((body) { |
| expect(body, 'test body'); |
| return syncHandler(request); |
| }); |
| }); |
| |
| return _schedulePost(body: 'test body').then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.stream.bytesToString(), completion('Hello from /')); |
| }); |
| }); |
| |
| test('supports request hijacking', () { |
| _scheduleServer((request) { |
| expect(request.method, 'POST'); |
| |
| request.hijack(expectAsync((stream, sink) { |
| expect(stream.first, completion(equals("Hello".codeUnits))); |
| |
| sink.add(( |
| "HTTP/1.1 404 Not Found\r\n" |
| "Date: Mon, 23 May 2005 22:38:34 GMT\r\n" |
| "Content-Length: 13\r\n" |
| "\r\n" |
| "Hello, world!").codeUnits); |
| sink.close(); |
| })); |
| }); |
| |
| return _schedulePost(body: "Hello").then((response) { |
| expect(response.statusCode, HttpStatus.NOT_FOUND); |
| expect(response.headers["date"], "Mon, 23 May 2005 22:38:34 GMT"); |
| expect(response.stream.bytesToString(), |
| completion(equals("Hello, world!"))); |
| }); |
| }); |
| |
| test('reports an error if a HijackException is thrown without hijacking', () { |
| _scheduleServer((request) => throw const HijackException()); |
| |
| return _scheduleGet().then((response) { |
| expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| }); |
| }); |
| |
| test('passes asynchronous exceptions to the parent error zone', () { |
| return runZoned(() { |
| return shelf_io.serve((request) { |
| new Future(() => throw 'oh no'); |
| return syncHandler(request); |
| }, 'localhost', 0).then((server) { |
| return http.get('http://localhost:${server.port}').then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /'); |
| server.close(); |
| }); |
| }); |
| }, onError: expectAsync((error) { |
| expect(error, equals('oh no')); |
| })); |
| }); |
| |
| test("doesn't pass asynchronous exceptions to the root error zone", () { |
| return Zone.ROOT.run(() { |
| return shelf_io.serve((request) { |
| new Future(() => throw 'oh no'); |
| return syncHandler(request); |
| }, 'localhost', 0).then((server) { |
| return http.get('http://localhost:${server.port}').then((response) { |
| expect(response.statusCode, HttpStatus.OK); |
| expect(response.body, 'Hello from /'); |
| server.close(); |
| }); |
| }); |
| }); |
| }); |
| } |
| |
| int _serverPort; |
| |
| Future _scheduleServer(Handler handler) { |
| return schedule(() => shelf_io.serve(handler, 'localhost', 0).then((server) { |
| currentSchedule.onComplete.schedule(() { |
| _serverPort = null; |
| return server.close(force: true); |
| }); |
| |
| _serverPort = server.port; |
| })); |
| } |
| |
| Future<http.Response> _scheduleGet({Map<String, String> headers}) { |
| if (headers == null) headers = {}; |
| |
| return schedule(() => |
| http.get('http://localhost:$_serverPort/', headers: headers)); |
| } |
| |
| Future<http.StreamedResponse> _schedulePost({Map<String, String> headers, |
| String body}) { |
| |
| return schedule(() { |
| |
| var request = new http.Request('POST', |
| Uri.parse('http://localhost:$_serverPort/')); |
| |
| if (headers != null) request.headers.addAll(headers); |
| if (body != null) request.body = body; |
| |
| return request.send(); |
| }); |
| } |