OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 @TestOn('vm') |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:convert'; |
| 9 import 'dart:io'; |
| 10 |
| 11 import 'package:http/http.dart' as http; |
| 12 import 'package:http_parser/http_parser.dart' as parser; |
| 13 import 'package:scheduled_test/scheduled_stream.dart'; |
| 14 import 'package:scheduled_test/scheduled_test.dart'; |
| 15 import 'package:shelf/shelf.dart'; |
| 16 import 'package:shelf/shelf_io.dart' as shelf_io; |
| 17 |
| 18 import 'test_util.dart'; |
| 19 |
| 20 void main() { |
| 21 test('sync handler returns a value to the client', () { |
| 22 _scheduleServer(syncHandler); |
| 23 |
| 24 return _scheduleGet().then((response) { |
| 25 expect(response.statusCode, HttpStatus.OK); |
| 26 expect(response.body, 'Hello from /'); |
| 27 }); |
| 28 }); |
| 29 |
| 30 test('async handler returns a value to the client', () { |
| 31 _scheduleServer(asyncHandler); |
| 32 |
| 33 return _scheduleGet().then((response) { |
| 34 expect(response.statusCode, HttpStatus.OK); |
| 35 expect(response.body, 'Hello from /'); |
| 36 }); |
| 37 }); |
| 38 |
| 39 test('sync null response leads to a 500', () { |
| 40 _scheduleServer((request) => null); |
| 41 |
| 42 return _scheduleGet().then((response) { |
| 43 expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| 44 expect(response.body, 'Internal Server Error'); |
| 45 }); |
| 46 }); |
| 47 |
| 48 test('async null response leads to a 500', () { |
| 49 _scheduleServer((request) => new Future.value(null)); |
| 50 |
| 51 return _scheduleGet().then((response) { |
| 52 expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| 53 expect(response.body, 'Internal Server Error'); |
| 54 }); |
| 55 }); |
| 56 |
| 57 test('thrown error leads to a 500', () { |
| 58 _scheduleServer((request) { |
| 59 throw new UnsupportedError('test'); |
| 60 }); |
| 61 |
| 62 return _scheduleGet().then((response) { |
| 63 expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| 64 expect(response.body, 'Internal Server Error'); |
| 65 }); |
| 66 }); |
| 67 |
| 68 test('async error leads to a 500', () { |
| 69 _scheduleServer((request) { |
| 70 return new Future.error('test'); |
| 71 }); |
| 72 |
| 73 return _scheduleGet().then((response) { |
| 74 expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| 75 expect(response.body, 'Internal Server Error'); |
| 76 }); |
| 77 }); |
| 78 |
| 79 test('Request is populated correctly', () { |
| 80 var path = '/foo/bar?qs=value'; |
| 81 |
| 82 _scheduleServer((request) { |
| 83 expect(request.contentLength, 0); |
| 84 expect(request.method, 'GET'); |
| 85 |
| 86 var expectedUrl = 'http://localhost:$_serverPort$path'; |
| 87 expect(request.requestedUri, Uri.parse(expectedUrl)); |
| 88 |
| 89 expect(request.url.path, 'foo/bar'); |
| 90 expect(request.url.pathSegments, ['foo', 'bar']); |
| 91 expect(request.protocolVersion, '1.1'); |
| 92 expect(request.url.query, 'qs=value'); |
| 93 expect(request.handlerPath, '/'); |
| 94 |
| 95 return syncHandler(request); |
| 96 }); |
| 97 |
| 98 return schedule(() => http.get('http://localhost:$_serverPort$path')).then( |
| 99 (response) { |
| 100 expect(response.statusCode, HttpStatus.OK); |
| 101 expect(response.body, 'Hello from /foo/bar'); |
| 102 }); |
| 103 }); |
| 104 |
| 105 test('custom response headers are received by the client', () { |
| 106 _scheduleServer((request) { |
| 107 return new Response.ok('Hello from /', |
| 108 headers: {'test-header': 'test-value', 'test-list': 'a, b, c'}); |
| 109 }); |
| 110 |
| 111 return _scheduleGet().then((response) { |
| 112 expect(response.statusCode, HttpStatus.OK); |
| 113 expect(response.headers['test-header'], 'test-value'); |
| 114 expect(response.body, 'Hello from /'); |
| 115 }); |
| 116 }); |
| 117 |
| 118 test('custom status code is received by the client', () { |
| 119 _scheduleServer((request) { |
| 120 return new Response(299, body: 'Hello from /'); |
| 121 }); |
| 122 |
| 123 return _scheduleGet().then((response) { |
| 124 expect(response.statusCode, 299); |
| 125 expect(response.body, 'Hello from /'); |
| 126 }); |
| 127 }); |
| 128 |
| 129 test('custom request headers are received by the handler', () { |
| 130 _scheduleServer((request) { |
| 131 expect(request.headers, containsPair('custom-header', 'client value')); |
| 132 |
| 133 // dart:io HttpServer splits multi-value headers into an array |
| 134 // validate that they are combined correctly |
| 135 expect(request.headers, containsPair('multi-header', 'foo,bar,baz')); |
| 136 return syncHandler(request); |
| 137 }); |
| 138 |
| 139 var headers = { |
| 140 'custom-header': 'client value', |
| 141 'multi-header': 'foo,bar,baz' |
| 142 }; |
| 143 |
| 144 return _scheduleGet(headers: headers).then((response) { |
| 145 expect(response.statusCode, HttpStatus.OK); |
| 146 expect(response.body, 'Hello from /'); |
| 147 }); |
| 148 }); |
| 149 |
| 150 test('post with empty content', () { |
| 151 _scheduleServer((request) { |
| 152 expect(request.mimeType, isNull); |
| 153 expect(request.encoding, isNull); |
| 154 expect(request.method, 'POST'); |
| 155 expect(request.contentLength, 0); |
| 156 |
| 157 return request.readAsString().then((body) { |
| 158 expect(body, ''); |
| 159 return syncHandler(request); |
| 160 }); |
| 161 }); |
| 162 |
| 163 return _schedulePost().then((response) { |
| 164 expect(response.statusCode, HttpStatus.OK); |
| 165 expect(response.stream.bytesToString(), completion('Hello from /')); |
| 166 }); |
| 167 }); |
| 168 |
| 169 test('post with request content', () { |
| 170 _scheduleServer((request) { |
| 171 expect(request.mimeType, 'text/plain'); |
| 172 expect(request.encoding, UTF8); |
| 173 expect(request.method, 'POST'); |
| 174 expect(request.contentLength, 9); |
| 175 |
| 176 return request.readAsString().then((body) { |
| 177 expect(body, 'test body'); |
| 178 return syncHandler(request); |
| 179 }); |
| 180 }); |
| 181 |
| 182 return _schedulePost(body: 'test body').then((response) { |
| 183 expect(response.statusCode, HttpStatus.OK); |
| 184 expect(response.stream.bytesToString(), completion('Hello from /')); |
| 185 }); |
| 186 }); |
| 187 |
| 188 test('supports request hijacking', () { |
| 189 _scheduleServer((request) { |
| 190 expect(request.method, 'POST'); |
| 191 |
| 192 request.hijack(expectAsync((stream, sink) { |
| 193 expect(stream.first, completion(equals("Hello".codeUnits))); |
| 194 |
| 195 sink.add(("HTTP/1.1 404 Not Found\r\n" |
| 196 "Date: Mon, 23 May 2005 22:38:34 GMT\r\n" |
| 197 "Content-Length: 13\r\n" |
| 198 "\r\n" |
| 199 "Hello, world!").codeUnits); |
| 200 sink.close(); |
| 201 })); |
| 202 }); |
| 203 |
| 204 return _schedulePost(body: "Hello").then((response) { |
| 205 expect(response.statusCode, HttpStatus.NOT_FOUND); |
| 206 expect(response.headers["date"], "Mon, 23 May 2005 22:38:34 GMT"); |
| 207 expect( |
| 208 response.stream.bytesToString(), completion(equals("Hello, world!"))); |
| 209 }); |
| 210 }); |
| 211 |
| 212 test('reports an error if a HijackException is thrown without hijacking', () { |
| 213 _scheduleServer((request) => throw const HijackException()); |
| 214 |
| 215 return _scheduleGet().then((response) { |
| 216 expect(response.statusCode, HttpStatus.INTERNAL_SERVER_ERROR); |
| 217 }); |
| 218 }); |
| 219 |
| 220 test('passes asynchronous exceptions to the parent error zone', () { |
| 221 return runZoned(() { |
| 222 return shelf_io.serve((request) { |
| 223 new Future(() => throw 'oh no'); |
| 224 return syncHandler(request); |
| 225 }, 'localhost', 0).then((server) { |
| 226 return http.get('http://localhost:${server.port}').then((response) { |
| 227 expect(response.statusCode, HttpStatus.OK); |
| 228 expect(response.body, 'Hello from /'); |
| 229 server.close(); |
| 230 }); |
| 231 }); |
| 232 }, onError: expectAsync((error) { |
| 233 expect(error, equals('oh no')); |
| 234 })); |
| 235 }); |
| 236 |
| 237 test("doesn't pass asynchronous exceptions to the root error zone", () async { |
| 238 var response = await Zone.ROOT.run(() async { |
| 239 var server = await shelf_io.serve((request) { |
| 240 new Future(() => throw 'oh no'); |
| 241 return syncHandler(request); |
| 242 }, 'localhost', 0); |
| 243 |
| 244 try { |
| 245 return await http.get('http://localhost:${server.port}'); |
| 246 } finally { |
| 247 server.close(); |
| 248 } |
| 249 }); |
| 250 |
| 251 expect(response.statusCode, HttpStatus.OK); |
| 252 expect(response.body, 'Hello from /'); |
| 253 }); |
| 254 |
| 255 test('a bad HTTP request results in a 500 response', () { |
| 256 var socket; |
| 257 |
| 258 _scheduleServer(syncHandler); |
| 259 |
| 260 schedule(() { |
| 261 return Socket.connect('localhost', _serverPort).then((value) { |
| 262 socket = value; |
| 263 |
| 264 currentSchedule.onComplete.schedule(() { |
| 265 return socket.close(); |
| 266 }, 'close the socket'); |
| 267 }); |
| 268 }); |
| 269 |
| 270 schedule(() { |
| 271 socket.write('GET / HTTP/1.1\r\n'); |
| 272 socket.write('Host: ^^super bad !@#host\r\n'); |
| 273 socket.write('\r\n'); |
| 274 return socket.close(); |
| 275 }); |
| 276 |
| 277 schedule(() { |
| 278 return UTF8.decodeStream(socket).then((value) { |
| 279 expect(value, contains('500 Internal Server Error')); |
| 280 }); |
| 281 }); |
| 282 }); |
| 283 |
| 284 group('date header', () { |
| 285 test('is sent by default', () { |
| 286 _scheduleServer(syncHandler); |
| 287 |
| 288 // Update beforeRequest to be one second earlier. HTTP dates only have |
| 289 // second-level granularity and the request will likely take less than a |
| 290 // second. |
| 291 var beforeRequest = new DateTime.now().subtract(new Duration(seconds: 1)); |
| 292 |
| 293 return _scheduleGet().then((response) { |
| 294 expect(response.headers, contains('date')); |
| 295 var responseDate = parser.parseHttpDate(response.headers['date']); |
| 296 |
| 297 expect(responseDate.isAfter(beforeRequest), isTrue); |
| 298 expect(responseDate.isBefore(new DateTime.now()), isTrue); |
| 299 }); |
| 300 }); |
| 301 |
| 302 test('defers to header in response', () { |
| 303 var date = new DateTime.utc(1981, 6, 5); |
| 304 _scheduleServer((request) { |
| 305 return new Response.ok('test', |
| 306 headers: {HttpHeaders.DATE: parser.formatHttpDate(date)}); |
| 307 }); |
| 308 |
| 309 return _scheduleGet().then((response) { |
| 310 expect(response.headers, contains('date')); |
| 311 var responseDate = parser.parseHttpDate(response.headers['date']); |
| 312 expect(responseDate, date); |
| 313 }); |
| 314 }); |
| 315 }); |
| 316 |
| 317 group('server header', () { |
| 318 test('defaults to "dart:io with Shelf"', () { |
| 319 _scheduleServer(syncHandler); |
| 320 |
| 321 return _scheduleGet().then((response) { |
| 322 expect(response.headers, |
| 323 containsPair(HttpHeaders.SERVER, 'dart:io with Shelf')); |
| 324 }); |
| 325 }); |
| 326 |
| 327 test('defers to header in response', () { |
| 328 _scheduleServer((request) { |
| 329 return new Response.ok('test', |
| 330 headers: {HttpHeaders.SERVER: 'myServer'}); |
| 331 }); |
| 332 |
| 333 return _scheduleGet().then((response) { |
| 334 expect(response.headers, containsPair(HttpHeaders.SERVER, 'myServer')); |
| 335 }); |
| 336 }); |
| 337 }); |
| 338 |
| 339 test('respects the "shelf.io.buffer_output" context parameter', () { |
| 340 var controller = new StreamController(); |
| 341 _scheduleServer((request) { |
| 342 controller.add("Hello, "); |
| 343 |
| 344 return new Response.ok(UTF8.encoder.bind(controller.stream), |
| 345 context: {"shelf.io.buffer_output": false}); |
| 346 }); |
| 347 |
| 348 schedule(() { |
| 349 var request = new http.Request( |
| 350 "GET", Uri.parse('http://localhost:$_serverPort/')); |
| 351 |
| 352 return request.send().then((response) { |
| 353 var stream = new ScheduledStream(UTF8.decoder.bind(response.stream)); |
| 354 |
| 355 return stream.next().then((data) { |
| 356 expect(data, equals("Hello, ")); |
| 357 controller.add("world!"); |
| 358 return stream.next(); |
| 359 }).then((data) { |
| 360 expect(data, equals("world!")); |
| 361 controller.close(); |
| 362 expect(stream.hasNext, completion(isFalse)); |
| 363 }); |
| 364 }); |
| 365 }); |
| 366 }); |
| 367 } |
| 368 |
| 369 int _serverPort; |
| 370 |
| 371 Future _scheduleServer(Handler handler) { |
| 372 return schedule(() => shelf_io.serve(handler, 'localhost', 0).then((server) { |
| 373 currentSchedule.onComplete.schedule(() { |
| 374 _serverPort = null; |
| 375 return server.close(force: true); |
| 376 }); |
| 377 |
| 378 _serverPort = server.port; |
| 379 })); |
| 380 } |
| 381 |
| 382 Future<http.Response> _scheduleGet({Map<String, String> headers}) { |
| 383 if (headers == null) headers = {}; |
| 384 |
| 385 return schedule( |
| 386 () => http.get('http://localhost:$_serverPort/', headers: headers)); |
| 387 } |
| 388 |
| 389 Future<http.StreamedResponse> _schedulePost( |
| 390 {Map<String, String> headers, String body}) { |
| 391 return schedule(() { |
| 392 var request = |
| 393 new http.Request('POST', Uri.parse('http://localhost:$_serverPort/')); |
| 394 |
| 395 if (headers != null) request.headers.addAll(headers); |
| 396 if (body != null) request.body = body; |
| 397 |
| 398 return request.send(); |
| 399 }); |
| 400 } |
OLD | NEW |