From e05a953d1e4e2f0d00b0e8d2eb9b7d76594f012b Mon Sep 17 00:00:00 2001 From: Don Date: Thu, 22 Jun 2017 13:43:43 -0700 Subject: [PATCH 1/4] Add Message tests --- test/message_change_test.dart | 100 ++++++++++ test/message_test.dart | 342 ++++++++++++++++++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 test/message_change_test.dart create mode 100644 test/message_test.dart diff --git a/test/message_change_test.dart b/test/message_change_test.dart new file mode 100644 index 0000000000..8c5942d191 --- /dev/null +++ b/test/message_change_test.dart @@ -0,0 +1,100 @@ +// Copyright (c) 2017, 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. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:test/test.dart'; + +import 'package:http/http.dart'; +import 'package:http/src/message.dart'; + +final _uri = Uri.parse('http://localhost/'); + +void main() { + group('Request', () { + _testChange(({body, headers, context}) { + return new Request('GET', _uri, + body: body, headers: headers, context: context); + }); + }); + + group('Response', () { + _testChange(({body, headers, context}) { + return new Response(_uri, 200, + body: body, headers: headers, context: context); + }); + }); +} + +/// Shared test method used by [Request] and [Response] tests to validate +/// the behavior of `change` with different `headers` and `context` values. +void _testChange( + Message factory( + {body, Map headers, Map context})) { + group('body', () { + test('with String', () async { + var request = factory(body: 'Hello, world'); + var copy = request.change(body: 'Goodbye, world'); + + var newBody = await copy.readAsString(); + + expect(newBody, equals('Goodbye, world')); + }); + + test('with Stream', () async { + var request = factory(body: 'Hello, world'); + var copy = request.change( + body: new Stream.fromIterable(['Goodbye, world']) + .transform(UTF8.encoder)); + + var newBody = await copy.readAsString(); + + expect(newBody, equals('Goodbye, world')); + }); + }); + + test('with empty headers returns identical instance', () { + var request = factory(headers: {'header1': 'header value 1'}); + var copy = request.change(headers: {}); + + expect(copy.headers, same(request.headers)); + }); + + test('with empty context returns identical instance', () { + var request = factory(context: {'context1': 'context value 1'}); + var copy = request.change(context: {}); + + expect(copy.context, same(request.context)); + }); + + test('new header values are added', () { + var request = factory(headers: {'test': 'test value'}); + var copy = request.change(headers: {'test2': 'test2 value'}); + + expect(copy.headers, + {'test': 'test value', 'test2': 'test2 value', 'content-length': '0'}); + }); + + test('existing header values are overwritten', () { + var request = factory(headers: {'test': 'test value'}); + var copy = request.change(headers: {'test': 'new test value'}); + + expect(copy.headers, {'test': 'new test value', 'content-length': '0'}); + }); + + test('new context values are added', () { + var request = factory(context: {'test': 'test value'}); + var copy = request.change(context: {'test2': 'test2 value'}); + + expect(copy.context, {'test': 'test value', 'test2': 'test2 value'}); + }); + + test('existing context values are overwritten', () { + var request = factory(context: {'test': 'test value'}); + var copy = request.change(context: {'test': 'new test value'}); + + expect(copy.context, {'test': 'new test value'}); + }); +} diff --git a/test/message_test.dart b/test/message_test.dart new file mode 100644 index 0000000000..7e90c0b6a8 --- /dev/null +++ b/test/message_test.dart @@ -0,0 +1,342 @@ +// Copyright (c) 2017, 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. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:http/src/message.dart'; +import 'package:test/test.dart'; + +// "hello," +const HELLO_BYTES = const [104, 101, 108, 108, 111, 44]; + +// " world" +const WORLD_BYTES = const [32, 119, 111, 114, 108, 100]; + +class _TestMessage extends Message { + _TestMessage(Map headers, Map context, body, + Encoding encoding) + : super(body, headers: headers, context: context, encoding: encoding); + + Message change( + {Map headers, Map context, body}) { + throw new UnimplementedError(); + } +} + +Message _createMessage( + {Map headers, + Map context, + body, + Encoding encoding}) { + return new _TestMessage(headers, context, body, encoding); +} + +void main() { + group('headers', () { + test('message headers are case insensitive', () { + var message = _createMessage(headers: {'foo': 'bar'}); + + expect(message.headers, containsPair('foo', 'bar')); + expect(message.headers, containsPair('Foo', 'bar')); + expect(message.headers, containsPair('FOO', 'bar')); + }); + + test('null header value becomes default', () { + var message = _createMessage(); + expect(message.headers.containsKey('content-length'), isFalse); + expect(message.headers, same(_createMessage().headers)); + expect(() => message.headers['h1'] = 'value1', throwsUnsupportedError); + }); + + test('headers are immutable', () { + var message = _createMessage(headers: {'h1': 'value1'}); + expect(() => message.headers['h1'] = 'value1', throwsUnsupportedError); + expect(() => message.headers['h1'] = 'value2', throwsUnsupportedError); + expect(() => message.headers['h2'] = 'value2', throwsUnsupportedError); + }); + }); + + group('context', () { + test('is accessible', () { + var message = _createMessage(context: {'foo': 'bar'}); + expect(message.context, containsPair('foo', 'bar')); + }); + + test('null context value becomes empty and immutable', () { + var message = _createMessage(); + expect(message.context, isEmpty); + expect(() => message.context['key'] = 'value', throwsUnsupportedError); + }); + + test('is immutable', () { + var message = _createMessage(context: {'key': 'value'}); + expect(() => message.context['key'] = 'value', throwsUnsupportedError); + expect(() => message.context['key2'] = 'value', throwsUnsupportedError); + }); + }); + + group("readAsString", () { + test("supports a null body", () { + var request = _createMessage(); + expect(request.readAsString(), completion(equals(""))); + }); + + test("supports a Stream> body", () { + var controller = new StreamController(); + var request = _createMessage(body: controller.stream); + expect(request.readAsString(), completion(equals("hello, world"))); + + controller.add(HELLO_BYTES); + return new Future(() { + controller + ..add(WORLD_BYTES) + ..close(); + }); + }); + + test("defaults to UTF-8", () { + var request = _createMessage( + body: new Stream.fromIterable([ + [195, 168] + ])); + expect(request.readAsString(), completion(equals("è"))); + }); + + test("the content-type header overrides the default", () { + var request = _createMessage( + headers: {'content-type': 'text/plain; charset=iso-8859-1'}, + body: new Stream.fromIterable([ + [195, 168] + ])); + expect(request.readAsString(), completion(equals("è"))); + }); + + test("an explicit encoding overrides the content-type header", () { + var request = _createMessage( + headers: {'content-type': 'text/plain; charset=iso-8859-1'}, + body: new Stream.fromIterable([ + [195, 168] + ])); + expect(request.readAsString(LATIN1), completion(equals("è"))); + }); + }); + + group("read", () { + test("supports a null body", () { + var request = _createMessage(); + expect(request.read().toList(), completion(isEmpty)); + }); + + test("supports a Stream> body", () { + var controller = new StreamController(); + var request = _createMessage(body: controller.stream); + expect(request.read().toList(), + completion(equals([HELLO_BYTES, WORLD_BYTES]))); + + controller.add(HELLO_BYTES); + return new Future(() { + controller + ..add(WORLD_BYTES) + ..close(); + }); + }); + + test("supports a List body", () { + var request = _createMessage(body: HELLO_BYTES); + expect(request.read().toList(), completion(equals([HELLO_BYTES]))); + }); + + test("throws when calling read()/readAsString() multiple times", () { + var request; + + request = _createMessage(); + expect(request.read().toList(), completion(isEmpty)); + expect(() => request.read(), throwsStateError); + + request = _createMessage(); + expect(request.readAsString(), completion(isEmpty)); + expect(() => request.readAsString(), throwsStateError); + + request = _createMessage(); + expect(request.readAsString(), completion(isEmpty)); + expect(() => request.read(), throwsStateError); + + request = _createMessage(); + expect(request.read().toList(), completion(isEmpty)); + expect(() => request.readAsString(), throwsStateError); + }); + }); + + group("content-length", () { + test("is null with a default body and without a content-length header", () { + var request = _createMessage(); + expect(request.contentLength, isNull); + }); + + test("comes from a byte body", () { + var request = _createMessage(body: [1, 2, 3]); + expect(request.contentLength, 3); + expect(request.isEmpty, isFalse); + }); + + test("comes from a string body", () { + var request = _createMessage(body: 'foobar'); + expect(request.contentLength, 6); + expect(request.isEmpty, isFalse); + }); + + test("is set based on byte length for a string body", () { + var request = _createMessage(body: 'fööbär'); + expect(request.contentLength, 9); + expect(request.isEmpty, isFalse); + + request = _createMessage(body: 'fööbär', encoding: LATIN1); + expect(request.contentLength, 6); + expect(request.isEmpty, isFalse); + }); + + test("is null for a stream body", () { + var request = _createMessage(body: new Stream.empty()); + expect(request.contentLength, isNull); + }); + + test("uses the content-length header for a stream body", () { + var request = _createMessage( + body: new Stream.empty(), headers: {'content-length': '42'}); + expect(request.contentLength, 42); + expect(request.isEmpty, isFalse); + }); + + test("real body length takes precedence over content-length header", () { + var request = + _createMessage(body: [1, 2, 3], headers: {'content-length': '42'}); + expect(request.contentLength, 3); + expect(request.isEmpty, isFalse); + }); + + test("is null for a chunked transfer encoding", () { + var request = _createMessage( + body: "1\r\na0\r\n\r\n", headers: {'transfer-encoding': 'chunked'}); + expect(request.contentLength, isNull); + }); + + test("is null for a non-identity transfer encoding", () { + var request = _createMessage( + body: "1\r\na0\r\n\r\n", headers: {'transfer-encoding': 'custom'}); + expect(request.contentLength, isNull); + }); + + test("is set for identity transfer encoding", () { + var request = _createMessage( + body: "1\r\na0\r\n\r\n", headers: {'transfer-encoding': 'identity'}); + expect(request.contentLength, equals(9)); + expect(request.isEmpty, isFalse); + }); + }); + + group("mimeType", () { + test("is null without a content-type header", () { + expect(_createMessage().mimeType, isNull); + }); + + test("comes from the content-type header", () { + expect(_createMessage(headers: {'content-type': 'text/plain'}).mimeType, + equals('text/plain')); + }); + + test("doesn't include parameters", () { + expect( + _createMessage( + headers: {'content-type': 'text/plain; foo=bar; bar=baz'}) + .mimeType, + equals('text/plain')); + }); + }); + + group("encoding", () { + test("is null without a content-type header", () { + expect(_createMessage().encoding, isNull); + }); + + test("is null without a charset parameter", () { + expect(_createMessage(headers: {'content-type': 'text/plain'}).encoding, + isNull); + }); + + test("is null with an unrecognized charset parameter", () { + expect( + _createMessage( + headers: {'content-type': 'text/plain; charset=fblthp'}).encoding, + isNull); + }); + + test("comes from the content-type charset parameter", () { + expect( + _createMessage( + headers: {'content-type': 'text/plain; charset=iso-8859-1'}) + .encoding, + equals(LATIN1)); + }); + + test("comes from the content-type charset parameter with a different case", + () { + expect( + _createMessage( + headers: {'Content-Type': 'text/plain; charset=iso-8859-1'}) + .encoding, + equals(LATIN1)); + }); + + test("defaults to encoding a String as UTF-8", () { + expect( + _createMessage(body: "è").read().toList(), + completion(equals([ + [195, 168] + ]))); + }); + + test("uses the explicit encoding if available", () { + expect( + _createMessage(body: "è", encoding: LATIN1).read().toList(), + completion(equals([ + [232] + ]))); + }); + + test("adds an explicit encoding to the content-type", () { + var request = _createMessage( + body: "è", encoding: LATIN1, headers: {'content-type': 'text/plain'}); + expect(request.headers, + containsPair('content-type', 'text/plain; charset=iso-8859-1')); + }); + + test("adds an explicit encoding to the content-type with a different case", + () { + var request = _createMessage( + body: "è", encoding: LATIN1, headers: {'Content-Type': 'text/plain'}); + expect(request.headers, + containsPair('Content-Type', 'text/plain; charset=iso-8859-1')); + }); + + test( + "sets an absent content-type to application/octet-stream in order to " + "set the charset", () { + var request = _createMessage(body: "è", encoding: LATIN1); + expect( + request.headers, + containsPair( + 'content-type', 'application/octet-stream; charset=iso-8859-1')); + }); + + test("overwrites an existing charset if given an explicit encoding", () { + var request = _createMessage( + body: "è", + encoding: LATIN1, + headers: {'content-type': 'text/plain; charset=whatever'}); + expect(request.headers, + containsPair('content-type', 'text/plain; charset=iso-8859-1')); + }); + }); +} From a7d41fd09953a37ef2f2d30a278f9c311b90e9c1 Mon Sep 17 00:00:00 2001 From: Don Date: Thu, 22 Jun 2017 15:07:47 -0700 Subject: [PATCH 2/4] Fix exports --- lib/http.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/http.dart b/lib/http.dart index d098a47e27..6b80d7f1eb 100644 --- a/lib/http.dart +++ b/lib/http.dart @@ -14,8 +14,7 @@ export 'src/base_client.dart'; export 'src/client.dart'; export 'src/exception.dart'; export 'src/io_client.dart'; -export 'src/multipart_file.dart'; -export 'src/multipart_request.dart'; +export 'src/middleware.dart'; export 'src/request.dart'; export 'src/response.dart'; From 9076baed267f3620ea7fa29f76fd3437555990ad Mon Sep 17 00:00:00 2001 From: Don Date: Thu, 22 Jun 2017 20:24:21 -0700 Subject: [PATCH 3/4] Migrate to test --- pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 10e970bec8..1f678acf01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,9 @@ dependencies: path: ">=0.9.0 <2.0.0" stack_trace: ">=0.9.1 <2.0.0" dev_dependencies: - unittest: ">=0.9.0 <0.12.0" + test: "^0.12.18" +# Override dependency on package_resolver to enable test +dependency_overrides: + package_resolver: '^1.0.0' environment: sdk: ">=1.24.0-dev.0.0 <2.0.0" From 53edcf8b4b85049fe6f94fa80726da6b0f0cecc8 Mon Sep 17 00:00:00 2001 From: Don Date: Thu, 22 Jun 2017 20:28:51 -0700 Subject: [PATCH 4/4] Fix import ordering --- test/message_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/message_test.dart b/test/message_test.dart index 7e90c0b6a8..c8d6474176 100644 --- a/test/message_test.dart +++ b/test/message_test.dart @@ -5,9 +5,10 @@ import 'dart:async'; import 'dart:convert'; -import 'package:http/src/message.dart'; import 'package:test/test.dart'; +import 'package:http/src/message.dart'; + // "hello," const HELLO_BYTES = const [104, 101, 108, 108, 111, 44];