diff --git a/test/curl_client_test.dart b/test/curl_client_test.dart index 5c94a72b0d1dcf66d32d622bec0758722e929bbf..c040c2db66e04f9b26d2388ba44fdffe201cf292 100644 --- a/test/curl_client_test.dart +++ b/test/curl_client_test.dart @@ -6,14 +6,210 @@ library curl_client_test; import 'dart:io'; import 'dart:isolate'; +import 'dart:json'; import 'dart:uri'; import '../../../pkg/unittest/lib/unittest.dart'; import '../../../pkg/http/lib/http.dart' as http; -import '../../../pkg/http/test/utils.dart'; import '../../pub/curl_client.dart'; import '../../pub/io.dart'; +// TODO(rnystrom): All of the code from here to the "---..." line was copied +// from pkg/http/test/utils.dart and pkg/http/lib/src/utils.dart. It's copied +// here because http/test/utils.dart is now using "package:" imports and this +// is not. You cannot mix those because you end up with duplicate copies of the +// same library in memory. Since curl_client is going away soon anyway, I'm +// just copying the code here. Delete all of this when curl client is removed. + +/// Returns the [Encoding] that corresponds to [charset]. Throws a +/// [FormatException] if no [Encoding] was found that corresponds to [charset]. +/// [charset] may not be null. +Encoding requiredEncodingForCharset(String charset) { + var encoding = _encodingForCharset(charset); + if (encoding != null) return encoding; + throw new FormatException('Unsupported encoding "$charset".'); +} + +/// Returns the [Encoding] that corresponds to [charset]. Returns null if no +/// [Encoding] was found that corresponds to [charset]. [charset] may not be +/// null. +Encoding _encodingForCharset(String charset) { + charset = charset.toLowerCase(); + if (charset == 'ascii' || charset == 'us-ascii') return Encoding.ASCII; + if (charset == 'utf-8') return Encoding.UTF_8; + if (charset == 'iso-8859-1') return Encoding.ISO_8859_1; + return null; +} + +/// Converts [bytes] into a [String] according to [encoding]. +String decodeString(List<int> bytes, Encoding encoding) { + // TODO(nweiz): implement this once issue 6284 is fixed. + return new String.fromCharCodes(bytes); +} + +/// The current server instance. +HttpServer _server; + +/// The URL for the current server instance. +Uri get serverUrl => new Uri.fromString('http://localhost:${_server.port}'); + +/// A dummy URL for constructing requests that won't be sent. +Uri get dummyUrl => new Uri.fromString('http://dartlang.org/'); + +/// Starts a new HTTP server. +void startServer() { + _server = new HttpServer(); + + _server.addRequestHandler((request) => request.path == '/error', + (request, response) { + response.statusCode = 400; + response.contentLength = 0; + response.outputStream.close(); + }); + + _server.addRequestHandler((request) => request.path == '/loop', + (request, response) { + var n = int.parse(new Uri.fromString(request.uri).query); + response.statusCode = 302; + response.headers.set('location', + serverUrl.resolve('/loop?${n + 1}').toString()); + response.contentLength = 0; + response.outputStream.close(); + }); + + _server.addRequestHandler((request) => request.path == '/redirect', + (request, response) { + response.statusCode = 302; + response.headers.set('location', serverUrl.resolve('/').toString()); + response.contentLength = 0; + response.outputStream.close(); + }); + + _server.defaultRequestHandler = (request, response) { + consumeInputStream(request.inputStream).then((requestBodyBytes) { + response.statusCode = 200; + response.headers.contentType = new ContentType("application", "json"); + + var requestBody; + if (requestBodyBytes.isEmpty) { + requestBody = null; + } else if (request.headers.contentType.charset != null) { + var encoding = requiredEncodingForCharset( + request.headers.contentType.charset); + requestBody = decodeString(requestBodyBytes, encoding); + } else { + requestBody = requestBodyBytes; + } + + var content = { + 'method': request.method, + 'path': request.path, + 'headers': <String>{} + }; + 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 outputEncoding; + var encodingName = request.queryParameters['response-encoding']; + if (encodingName != null) { + outputEncoding = requiredEncodingForCharset(encodingName); + } else { + outputEncoding = Encoding.ASCII; + } + + var body = JSON.stringify(content); + response.contentLength = body.length; + response.outputStream.writeString(body, outputEncoding); + response.outputStream.close(); + }); + }; + + _server.listen("127.0.0.1", 0); +} + +/// Stops the current HTTP server. +void stopServer() { + _server.close(); + _server = null; +} + +/// A matcher that matches JSON that parses to a value that matches the inner +/// matcher. +Matcher parse(matcher) => new _Parse(matcher); + +class _Parse extends BaseMatcher { + final Matcher _matcher; + + _Parse(this._matcher); + + bool matches(item, MatchState matchState) { + if (item is! String) return false; + + var parsed; + try { + parsed = JSON.parse(item); + } catch (e) { + return false; + } + + return _matcher.matches(parsed, matchState); + } + + Description describe(Description description) { + return description.add('parses to a value that ') + .addDescriptionOf(_matcher); + } +} + +// TODO(nweiz): remove this once it's built in to unittest +/// A matcher for StateErrors. +const isStateError = const _StateError(); + +/// A matcher for functions that throw StateError. +const Matcher throwsStateError = + const Throws(isStateError); + +class _StateError extends TypeMatcher { + const _StateError() : super("StateError"); + bool matches(item, MatchState matchState) => item is StateError; +} + +/// 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, MatchState 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, MatchState matchState) => + item is RedirectLimitExceededException; +} + +// ---------------------------------------------------------------------------- + void main() { setUp(startServer); tearDown(stopServer);