// 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; }