| // Copyright (c) 2013, 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 test_utils; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:http/src/byte_stream.dart'; |
| import 'package:http/src/utils.dart'; |
| import 'package:unittest/unittest.dart'; |
| |
| import 'safe_http_server.dart'; |
| |
| /// The current server instance. |
| HttpServer _server; |
| |
| /// The URL for the current server instance. |
| Uri get serverUrl => Uri.parse('http://localhost:${_server.port}'); |
| |
| /// A dummy URL for constructing requests that won't be sent. |
| Uri get dummyUrl => Uri.parse('http://dartlang.org/'); |
| |
| /// Starts a new HTTP server. |
| Future startServer() { |
| return SafeHttpServer.bind("localhost", 0).then((s) { |
| _server = s; |
| s.listen((request) { |
| var path = request.uri.path; |
| var response = request.response; |
| |
| if (path == '/error') { |
| response.statusCode = 400; |
| response.contentLength = 0; |
| response.close(); |
| return; |
| } |
| |
| if (path == '/loop') { |
| var n = int.parse(request.uri.query); |
| response.statusCode = 302; |
| response.headers.set('location', |
| serverUrl.resolve('/loop?${n + 1}').toString()); |
| response.contentLength = 0; |
| response.close(); |
| return; |
| } |
| |
| if (path == '/redirect') { |
| response.statusCode = 302; |
| response.headers.set('location', serverUrl.resolve('/').toString()); |
| response.contentLength = 0; |
| response.close(); |
| return; |
| } |
| |
| new ByteStream(request).toBytes().then((requestBodyBytes) { |
| var outputEncoding; |
| var encodingName = request.uri.queryParameters['response-encoding']; |
| if (encodingName != null) { |
| outputEncoding = requiredEncodingForCharset(encodingName); |
| } else { |
| outputEncoding = ASCII; |
| } |
| |
| response.headers.contentType = |
| new ContentType( |
| "application", "json", charset: outputEncoding.name); |
| response.headers.set('single', 'value'); |
| |
| var requestBody; |
| if (requestBodyBytes.isEmpty) { |
| requestBody = null; |
| } else if (request.headers.contentType.charset != null) { |
| var encoding = requiredEncodingForCharset( |
| request.headers.contentType.charset); |
| requestBody = encoding.decode(requestBodyBytes); |
| } else { |
| requestBody = requestBodyBytes; |
| } |
| |
| var content = { |
| 'method': request.method, |
| 'path': request.uri.path, |
| 'headers': {} |
| }; |
| if (requestBody != null) content['body'] = requestBody; |
| request.headers.forEach((name, values) { |
| // These headers are automatically generated by dart:io, so we don't |
| // want to test them here. |
| if (name == 'cookie' || name == 'host') return; |
| |
| content['headers'][name] = values; |
| }); |
| |
| var body = JSON.encode(content); |
| response.contentLength = body.length; |
| response.write(body); |
| response.close(); |
| }); |
| }); |
| }); |
| } |
| |
| /// Stops the current HTTP server. |
| void stopServer() { |
| if (_server != null) { |
| _server.close(); |
| _server = null; |
| } |
| } |
| |
| /// Removes eight spaces of leading indentation from a multiline string. |
| /// |
| /// Note that this is very sensitive to how the literals are styled. They should |
| /// be: |
| /// ''' |
| /// Text starts on own line. Lines up with subsequent lines. |
| /// Lines are indented exactly 8 characters from the left margin. |
| /// Close is on the same line.''' |
| /// |
| /// This does nothing if text is only a single line. |
| // TODO(nweiz): Make this auto-detect the indentation level from the first |
| // non-whitespace line. |
| String cleanUpLiteral(String text) { |
| var lines = text.split('\n'); |
| if (lines.length <= 1) return text; |
| |
| for (var j = 0; j < lines.length; j++) { |
| if (lines[j].length > 8) { |
| lines[j] = lines[j].substring(8, lines[j].length); |
| } else { |
| lines[j] = ''; |
| } |
| } |
| |
| return lines.join('\n'); |
| } |
| |
| /// A matcher that matches JSON that parses to a value that matches the inner |
| /// matcher. |
| Matcher parse(matcher) => new _Parse(matcher); |
| |
| class _Parse extends Matcher { |
| final Matcher _matcher; |
| |
| _Parse(this._matcher); |
| |
| bool matches(item, Map matchState) { |
| if (item is! String) return false; |
| |
| var parsed; |
| try { |
| parsed = JSON.decode(item); |
| } catch (e) { |
| return false; |
| } |
| |
| return _matcher.matches(parsed, matchState); |
| } |
| |
| Description describe(Description description) { |
| return description.add('parses to a value that ') |
| .addDescriptionOf(_matcher); |
| } |
| } |
| |
| /// A matcher for HttpExceptions. |
| const isHttpException = const _HttpException(); |
| |
| /// A matcher for functions that throw HttpException. |
| const Matcher throwsHttpException = |
| const Throws(isHttpException); |
| |
| class _HttpException extends TypeMatcher { |
| const _HttpException() : super("HttpException"); |
| bool matches(item, Map matchState) => item is HttpException; |
| } |
| |
| /// A matcher for RedirectLimitExceededExceptions. |
| const isRedirectLimitExceededException = |
| const _RedirectLimitExceededException(); |
| |
| /// A matcher for functions that throw RedirectLimitExceededException. |
| const Matcher throwsRedirectLimitExceededException = |
| const Throws(isRedirectLimitExceededException); |
| |
| class _RedirectLimitExceededException extends TypeMatcher { |
| const _RedirectLimitExceededException() : |
| super("RedirectLimitExceededException"); |
| |
| bool matches(item, Map matchState) => |
| item is RedirectException && item.message == "Redirect limit exceeded"; |
| } |
| |
| /// A matcher for SocketExceptions. |
| const isSocketException = const _SocketException(); |
| |
| /// A matcher for functions that throw SocketException. |
| const Matcher throwsSocketException = |
| const Throws(isSocketException); |
| |
| class _SocketException extends TypeMatcher { |
| const _SocketException() : super("SocketException"); |
| bool matches(item, Map matchState) => item is SocketException; |
| } |