Dart 2 fixes

This commit is contained in:
Tobe O 2018-07-09 12:10:25 -04:00
parent 06b85a8bb9
commit 046c1f7807
10 changed files with 296 additions and 110 deletions

View file

@ -2,6 +2,7 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" />

View file

@ -1 +1,4 @@
language: dart
language: dart
dart:
- dev
- stable

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
# 1.1.0+1
* Dart 2/strong mode fixes.
* Pass a `useZone` flag to `AngelHttp` through `TestServer`.

3
analysis_options.yaml Normal file
View file

@ -0,0 +1,3 @@
analyzer:
strong-mode:
implicit-casts: false

142
example/main.dart Normal file
View file

@ -0,0 +1,142 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_test/angel_test.dart';
import 'package:angel_validate/angel_validate.dart';
import 'package:angel_websocket/server.dart';
import 'package:test/test.dart';
main() {
Angel app;
TestClient client;
setUp(() async {
app = new Angel()
..get('/hello', 'Hello')
..get(
'/error',
() => throw new AngelHttpException.forbidden(message: 'Test')
..errors.addAll(['foo', 'bar']))
..get('/body', (ResponseContext res) {
res
..write('OK')
..end();
})
..get(
'/valid',
() => {
'michael': 'jackson',
'billie': {'jean': 'hee-hee', 'is_my_lover': false}
})
..post('/hello', (req, res) async {
return {'bar': req.body['foo']};
})
..get('/gzip', (req, res) async {
res
..headers['content-encoding'] = 'gzip'
..write(gzip.encode('Poop'.codeUnits))
..end();
})
..use(
'/foo',
new AnonymousService(
index: ([params]) async => [
{'michael': 'jackson'}
],
create: (data, [params]) async => {'foo': 'bar'}));
var ws = new AngelWebSocket(app);
await app.configure(ws.configureServer);
app.all('/ws', ws.handleRequest);
app.errorHandler = (e, req, res) => e.toJson();
client = await connectTo(app);
});
tearDown(() async {
await client.close();
app = null;
});
group('matchers', () {
group('isJson+hasStatus', () {
test('get', () async {
final response = await client.get('/hello');
expect(response, isJson('Hello'));
});
test('post', () async {
final response = await client.post('/hello', body: {'foo': 'baz'});
expect(response, allOf(hasStatus(200), isJson({'bar': 'baz'})));
});
});
test('isAngelHttpException', () async {
var res = await client.get('/error');
print(res.body);
expect(res, isAngelHttpException());
expect(
res,
isAngelHttpException(
statusCode: 403, message: 'Test', errors: ['foo', 'bar']));
});
test('hasBody', () async {
var res = await client.get('/body');
expect(res, hasBody());
expect(res, hasBody('OK'));
});
test('hasHeader', () async {
var res = await client.get('/hello');
expect(res, hasHeader('server'));
expect(res, hasHeader('server', 'angel'));
expect(res, hasHeader('server', ['angel']));
});
test('hasValidBody+hasContentType', () async {
var res = await client.get('/valid');
expect(res, hasContentType('application/json'));
expect(res, hasContentType(new ContentType('application', 'json')));
expect(
res,
hasValidBody(new Validator({
'michael*': [isString, isNotEmpty, equals('jackson')],
'billie': new Validator({
'jean': [isString, isNotEmpty],
'is_my_lover': [isBool, isFalse]
})
})));
});
test('gzip decode', () async {
var res = await client.get('/gzip');
expect(res, hasHeader('content-encoding', 'gzip'));
expect(res, hasBody('Poop'));
});
group('service', () {
test('index', () async {
var foo = client.service('foo');
var result = await foo.index();
expect(result, [
{'michael': 'jackson'}
]);
});
test('index', () async {
var foo = client.service('foo');
var result = await foo.create({});
expect(result, {'foo': 'bar'});
});
});
test('websocket', () async {
var ws = await client.websocket();
var foo = ws.service('foo');
foo.create({});
var result = await foo.onCreated.first;
expect(result.data, equals({'foo': 'bar'}));
});
});
}

View file

@ -1,2 +1,2 @@
export 'src/client.dart';
export 'src/matchers.dart';
export 'src/matchers.dart';

View file

@ -1,33 +1,37 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:convert' show Encoding;
import 'dart:io' show ContentType, Cookie, HttpSession, HttpServer, WebSocket;
import 'package:dart2_constant/convert.dart';
import 'package:dart2_constant/io.dart' hide WebSocket;
import 'package:angel_client/base_angel_client.dart' as client;
import 'package:angel_client/io.dart' as client;
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_websocket/io.dart' as client;
import 'package:http/http.dart' as http;
import 'package:http/http.dart' as http hide StreamedResponse;
import 'package:http/src/streamed_response.dart';
import 'package:mock_request/mock_request.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';
import 'package:uuid/uuid.dart';
//import 'package:uuid/uuid.dart';
final RegExp _straySlashes = new RegExp(r"(^/)|(/+$)");
const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
/*const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
const Map<String, String> _writeHeaders = const {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
final Uuid _uuid = new Uuid();
final Uuid _uuid = new Uuid();*/
/// Shorthand for bootstrapping a [TestClient].
Future<TestClient> connectTo(Angel app,
{Map initialSession, bool autoDecodeGzip: true}) async {
if (!app.isProduction)
app.configuration.putIfAbsent('testMode', () => true);
{Map initialSession,
bool autoDecodeGzip: true,
bool useZone: false}) async {
if (!app.isProduction) app.configuration.putIfAbsent('testMode', () => true);
for (var plugin in app.startupHooks)
await plugin(app);
return new TestClient(app, autoDecodeGzip: autoDecodeGzip != false)
for (var plugin in app.startupHooks) await plugin(app);
return new TestClient(app,
autoDecodeGzip: autoDecodeGzip != false, useZone: useZone)
..session.addAll(initialSession ?? {});
}
@ -50,7 +54,12 @@ class TestClient extends client.BaseAngelClient {
@override
String authToken;
TestClient(this.server, {this.autoDecodeGzip: true}) : super(new http.Client(), '/');
AngelHttp _http;
TestClient(this.server, {this.autoDecodeGzip: true, bool useZone: false})
: super(new http.IOClient(), '/') {
_http = new AngelHttp(server, useZone: useZone);
}
Future close() {
this.client.close();
@ -60,8 +69,8 @@ class TestClient extends client.BaseAngelClient {
/// Opens a WebSockets connection to the server. This will automatically bind the server
/// over HTTP, if it is not already listening. Unfortunately, WebSockets cannot be mocked (yet!).
Future<client.WebSockets> websocket({String path, Duration timeout}) async {
HttpServer http = server.httpServer;
if (http == null) http = await server.startServer();
HttpServer http = _http.httpServer;
if (http == null) http = await _http.startServer();
var url = 'ws://${http.address.address}:${http.port}';
var cleanPath = (path ?? '/ws')?.replaceAll(_straySlashes, '');
if (cleanPath?.isNotEmpty == true) url += '/$cleanPath';
@ -70,24 +79,21 @@ class TestClient extends client.BaseAngelClient {
return ws;
}
Future<http.Response> sendUnstreamed(String method, url,
Map<String, String> headers,
[body, Encoding encoding]) =>
Future<http.Response> sendUnstreamed(
String method, url, Map<String, String> headers,
[body, Encoding encoding]) =>
send(method, url, headers, body, encoding).then(http.Response.fromStream);
Future<http.StreamedResponse> send(String method, url,
Map<String, String> headers,
Future<StreamedResponse> send(String method, url, Map<String, String> headers,
[body, Encoding encoding]) async {
var rq = new MockHttpRequest(
method, url is Uri ? url : Uri.parse(url.toString()));
headers?.forEach(rq.headers.add);
if (authToken?.isNotEmpty == true)
rq.headers.set(HttpHeaders.AUTHORIZATION, 'Bearer $authToken');
rq.headers.set('authorization', 'Bearer $authToken');
rq
..cookies.addAll(cookies)
..session.addAll(session);
rq..cookies.addAll(cookies)..session.addAll(session);
if (body is Stream<List<int>>) {
await rq.addStream(body);
@ -95,16 +101,17 @@ class TestClient extends client.BaseAngelClient {
rq.add(body);
} else if (body is Map) {
if (rq.headers.contentType == null ||
rq.headers.contentType.mimeType == ContentType.JSON.mimeType) {
rq.headers.contentType.mimeType == 'application/json') {
rq
..headers.contentType = ContentType.JSON
..write(JSON.encode(
body.keys.fold({}, (out, k) => out..[k.toString()] = body[k])));
..headers.contentType = new ContentType('application', 'json')
..write(json.encode(body.keys.fold<Map<String, dynamic>>(
{}, (out, k) => out..[k.toString()] = body[k])));
} else if (rq.headers.contentType?.mimeType ==
'application/x-www-form-urlencoded') {
rq.write(body.keys.fold<List<String>>([],
(out, k) => out..add('$k=' + Uri.encodeComponent(body[k])))
.join());
rq.write(body.keys.fold<List<String>>(
[],
(out, k) => out
..add('$k=' + Uri.encodeComponent(body[k].toString()))).join());
} else {
throw new UnsupportedError(
'Map bodies can only be sent for requests with the content type application/json or application/x-www-form-urlencoded.');
@ -114,7 +121,8 @@ class TestClient extends client.BaseAngelClient {
}
await rq.close();
await server.handleRequest(rq);
await _http.handleRequest(rq);
var rs = rq.response;
session
@ -130,17 +138,17 @@ class TestClient extends client.BaseAngelClient {
Stream<List<int>> stream = rs;
if (autoDecodeGzip != false &&
rs.headers[HttpHeaders.CONTENT_ENCODING]?.contains('gzip') == true)
stream = stream.transform(GZIP.decoder);
rs.headers['content-encoding']?.contains('gzip') == true)
stream = stream.transform(gzip.decoder);
return new http.StreamedResponse(stream, rs.statusCode,
return new StreamedResponse(stream, rs.statusCode,
contentLength: rs.contentLength,
isRedirect: rs.headers[HttpHeaders.LOCATION] != null,
isRedirect: rs.headers['location'] != null,
headers: extractedHeaders,
persistentConnection:
rq.headers.value(HttpHeaders.CONNECTION)?.toLowerCase()?.trim() ==
'keep-alive' ||
rq.headers.persistentConnection == true,
rq.headers.value('connection')?.toLowerCase()?.trim() ==
'keep-alive' ||
rq.headers.persistentConnection == true,
reasonPhrase: rs.reasonPhrase);
}
@ -175,7 +183,7 @@ class TestClient extends client.BaseAngelClient {
Future configure(client.AngelConfigurer configurer) => configurer(this);
@override
client.Service service<T>(String path,
client.Service service(String path,
{Type type, client.AngelDeserializer deserializer}) {
String uri = path.toString().replaceAll(_straySlashes, "");
return _services.putIfAbsent(
@ -191,7 +199,7 @@ class _MockService extends client.BaseAngelService {
: super(null, _app, basePath, deserializer: deserializer);
@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
Future<StreamedResponse> send(http.BaseRequest request) {
if (app.authToken != null && app.authToken.isNotEmpty) {
request.headers['Authorization'] = 'Bearer ${app.authToken}';
}
@ -211,7 +219,7 @@ class _MockWebSockets extends client.WebSockets {
Map<String, String> headers = {};
if (app.authToken?.isNotEmpty == true)
headers[HttpHeaders.AUTHORIZATION] = 'Bearer ${app.authToken}';
headers['authorization'] = 'Bearer ${app.authToken}';
var socket = await WebSocket.connect(basePath, headers: headers);
return new IOWebSocketChannel(socket);

View file

@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:angel_http_exception/angel_http_exception.dart';
import 'package:angel_validate/angel_validate.dart';
import 'package:dart2_constant/convert.dart';
import 'package:http/http.dart' as http;
import 'package:matcher/matcher.dart';
@ -51,8 +51,9 @@ class _IsJson extends Matcher {
}
@override
bool matches(http.Response item, Map matchState) =>
equals(value).matches(JSON.decode(item.body), matchState);
bool matches(item, Map matchState) =>
item is http.Response &&
equals(value).matches(json.decode(item.body), matchState);
}
class _HasBody extends Matcher {
@ -65,12 +66,16 @@ class _HasBody extends Matcher {
description.add('has body $body');
@override
bool matches(http.Response item, Map matchState) {
if (body == true) return isNotEmpty.matches(item.bodyBytes, matchState);
if (body is List<int>)
return equals(body).matches(item.bodyBytes, matchState);
else
return equals(body.toString()).matches(item.body, matchState);
bool matches(item, Map matchState) {
if (item is http.Response) {
if (body == true) return isNotEmpty.matches(item.bodyBytes, matchState);
if (body is List<int>)
return equals(body).matches(item.bodyBytes, matchState);
else
return equals(body.toString()).matches(item.body, matchState);
} else {
return false;
}
}
}
@ -81,21 +86,27 @@ class _HasContentType extends Matcher {
@override
Description describe(Description description) {
var str =
contentType is ContentType ? contentType.value : contentType.toString();
var str = contentType is ContentType
? ((contentType as ContentType).value)
: contentType.toString();
return description.add('has content type ' + str);
}
@override
bool matches(http.Response item, Map matchState) {
if (!item.headers.containsKey(HttpHeaders.CONTENT_TYPE)) return false;
bool matches(item, Map matchState) {
if (item is http.Response) {
if (!item.headers.containsKey('content-type')) return false;
if (contentType is ContentType) {
var compare = ContentType.parse(item.headers[HttpHeaders.CONTENT_TYPE]);
return equals(contentType.mimeType).matches(compare.mimeType, matchState);
if (contentType is ContentType) {
var compare = ContentType.parse(item.headers['content-type']);
return equals(contentType.mimeType)
.matches(compare.mimeType, matchState);
} else {
return equals(contentType.toString())
.matches(item.headers['content-type'], matchState);
}
} else {
return equals(contentType.toString())
.matches(item.headers[HttpHeaders.CONTENT_TYPE], matchState);
return false;
}
}
}
@ -115,15 +126,20 @@ class _HasHeader extends Matcher {
}
@override
bool matches(http.Response item, Map matchState) {
if (value == true) {
return contains(key.toLowerCase()).matches(item.headers.keys, matchState);
bool matches(item, Map matchState) {
if (item is http.Response) {
if (value == true) {
return contains(key.toLowerCase())
.matches(item.headers.keys, matchState);
} else {
if (!item.headers.containsKey(key.toLowerCase())) return false;
Iterable v = value is Iterable ? value : [value];
return v
.map((x) => x.toString())
.every(item.headers[key.toLowerCase()].split(',').contains);
}
} else {
if (!item.headers.containsKey(key.toLowerCase())) return false;
Iterable v = value is Iterable ? value : [value];
return v
.map((x) => x.toString())
.every(item.headers[key.toLowerCase()].split(',').contains);
return false;
}
}
}
@ -139,7 +155,8 @@ class _HasStatus extends Matcher {
}
@override
bool matches(http.Response item, Map matchState) =>
bool matches(item, Map matchState) =>
item is http.Response &&
equals(status).matches(item.statusCode, matchState);
}
@ -153,10 +170,14 @@ class _HasValidBody extends Matcher {
description.add('matches validation schema ${validator.rules}');
@override
bool matches(http.Response item, Map matchState) {
final json = JSON.decode(item.body);
if (json is! Map) return false;
return validator.matches(json, matchState);
bool matches(item, Map matchState) {
if (item is http.Response) {
final jsons = json.decode(item.body);
if (jsons is! Map) return false;
return validator.matches(jsons, matchState);
} else {
return false;
}
}
}
@ -198,30 +219,35 @@ class _IsAngelHttpException extends Matcher {
}
@override
bool matches(http.Response item, Map matchState) {
final json = JSON.decode(item.body);
bool matches(item, Map matchState) {
if (item is http.Response) {
final jsons = json.decode(item.body);
if (json is Map && json['isError'] == true) {
var exc = new AngelHttpException.fromMap(json);
if (jsons is Map && jsons['isError'] == true) {
var exc = new AngelHttpException.fromMap(jsons);
print(exc.toJson());
if (message?.isNotEmpty != true && statusCode == null && errors.isEmpty)
return true;
else {
if (statusCode != null) if (!equals(statusCode)
.matches(exc.statusCode, matchState)) return false;
if (message?.isNotEmpty != true && statusCode == null && errors.isEmpty)
return true;
else {
if (statusCode != null) if (!equals(statusCode)
.matches(exc.statusCode, matchState)) return false;
if (message?.isNotEmpty == true) if (!equals(message)
.matches(exc.message, matchState)) return false;
if (message?.isNotEmpty == true) if (!equals(message)
.matches(exc.message, matchState)) return false;
if (errors.isNotEmpty) {
if (!errors
.every((err) => contains(err).matches(exc.errors, matchState)))
return false;
if (errors.isNotEmpty) {
if (!errors
.every((err) => contains(err).matches(exc.errors, matchState)))
return false;
}
return true;
}
return true;
}
} else
} else
return false;
} else {
return false;
}
}
}

View file

@ -1,18 +1,17 @@
author: "Tobe O <thosakwe@gmail.com>"
description: "Testing utility library for the Angel framework."
homepage: "https://github.com/angel-dart/test.git"
name: "angel_test"
version: 1.1.0
author: Tobe O <thosakwe@gmail.com>
description: Testing utility library for the Angel framework.
homepage: https://github.com/angel-dart/test.git
name: angel_test
version: 1.1.0+1
dependencies:
angel_client: ^1.1.0-alpha
angel_framework: ^1.1.0-alpha
angel_validate: ^1.0.0
angel_websocket: ^1.1.0-alpha
http: "^0.11.3+9"
matcher: "^0.12.0+2"
http: ^0.11.0
matcher: ^0.12.0+2
mock_request: ^1.0.0
uuid: "^0.5.3"
dev_dependencies:
test: "^0.12.17+2"
test: ^0.12.17+2
environment:
sdk: ">=1.19.0"
sdk: ">=1.8.9 <3.0.0"

View file

@ -32,8 +32,8 @@ main() {
})
..get('/gzip', (req, res) async {
res
..headers[HttpHeaders.CONTENT_ENCODING] = 'gzip'
..write(GZIP.encode('Poop'.codeUnits))
..headers['content-encoding'] = 'gzip'
..write(gzip.encode('Poop'.codeUnits))
..end();
})
..use(
@ -50,7 +50,7 @@ main() {
app.errorHandler = (e, req, res) => e.toJson();
client = await connectTo(app);
client = await connectTo(app, useZone: false);
});
tearDown(() async {
@ -96,8 +96,9 @@ main() {
test('hasValidBody+hasContentType', () async {
var res = await client.get('/valid');
print('Body: ${res.body}');
expect(res, hasContentType('application/json'));
expect(res, hasContentType(ContentType.JSON));
expect(res, hasContentType(new ContentType('application', 'json')));
expect(
res,
hasValidBody(new Validator({
@ -111,7 +112,7 @@ main() {
test('gzip decode', () async {
var res = await client.get('/gzip');
expect(res, hasHeader(HttpHeaders.CONTENT_ENCODING, 'gzip'));
expect(res, hasHeader('content-encoding', 'gzip'));
expect(res, hasBody('Poop'));
});
@ -136,7 +137,7 @@ main() {
var foo = ws.service('foo');
foo.create({});
var result = await foo.onCreated.first;
expect(result.data, equals({'foo': 'bar'}));
expect(result is Map ? result : result.data, equals({'foo': 'bar'}));
});
});
}