Finally switched to new router

This commit is contained in:
thosakwe 2016-11-23 04:10:47 -05:00
parent eb24b0c43e
commit 3355b0ab39
13 changed files with 175 additions and 132 deletions

1
.travis.yml Normal file
View file

@ -0,0 +1 @@
language: dart

View file

@ -1,5 +1,6 @@
# angel_framework # angel_framework
![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) ![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev.23-red.svg)
![build status](https://travis-ci.org/angel-dart/framework.svg)
Core libraries for the Angel Framework. Core libraries for the Angel Framework.

View file

@ -18,19 +18,6 @@ class Controller {
Future call(AngelBase app) async { Future call(AngelBase app) async {
this.app = app; this.app = app;
app.use(exposeDecl.path, generateRoutable());
TypeMirror typeMirror = reflectType(this.runtimeType);
String name = exposeDecl.as;
if (name == null || name.isEmpty)
name = MirrorSystem.getName(typeMirror.simpleName);
app.controllers[name] = this;
}
Routable generateRoutable() {
final routable = new Routable();
// Load global expose decl // Load global expose decl
ClassMirror classMirror = reflectClass(this.runtimeType); ClassMirror classMirror = reflectClass(this.runtimeType);
@ -47,11 +34,18 @@ class Controller {
"All controllers must carry an @Expose() declaration."); "All controllers must carry an @Expose() declaration.");
} }
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware); app.use(exposeDecl.path, generateRoutable(classMirror));
TypeMirror typeMirror = reflectType(this.runtimeType);
String name = exposeDecl.as;
InstanceMirror instanceMirror = reflect(this); if (name == null || name.isEmpty)
classMirror.instanceMembers name = MirrorSystem.getName(typeMirror.simpleName);
.forEach((Symbol key, MethodMirror methodMirror) {
app.controllers[name] = this;
}
_callback(InstanceMirror instanceMirror, Routable routable, List handlers) {
return (Symbol key, MethodMirror methodMirror) {
if (methodMirror.isRegularMethod && if (methodMirror.isRegularMethod &&
key != #toString && key != #toString &&
key != #noSuchMethod && key != #noSuchMethod &&
@ -102,11 +96,13 @@ class Controller {
return await instanceMirror.invoke(key, args).reflectee; return await instanceMirror.invoke(key, args).reflectee;
}; };
final middleware = []
..addAll(handlers)
..addAll(exposeMirror.reflectee.middleware);
final route = routable.addRoute(exposeMirror.reflectee.method, final route = routable.addRoute(exposeMirror.reflectee.method,
exposeMirror.reflectee.path, handler, exposeMirror.reflectee.path, handler,
middleware: [] middleware: middleware);
..addAll(handlers)
..addAll(exposeMirror.reflectee.middleware));
String name = exposeMirror.reflectee.as; String name = exposeMirror.reflectee.as;
@ -115,7 +111,16 @@ class Controller {
routeMappings[name] = route; routeMappings[name] = route;
} }
} }
}); };
}
Routable generateRoutable(ClassMirror classMirror) {
final routable = new Routable(debug: true);
final handlers = []..addAll(exposeDecl.middleware)..addAll(middleware);
InstanceMirror instanceMirror = reflect(this);
final callback = _callback(instanceMirror, routable, handlers);
classMirror.instanceMembers.forEach(callback);
return routable; return routable;
} }

View file

@ -37,7 +37,10 @@ class HookedService extends Service {
HookedService(Service this.inner) { HookedService(Service this.inner) {
// Clone app instance // Clone app instance
if (inner.app != null) this.app = inner.app; if (inner.app != null) this.app = inner.app;
}
@override
void addRoutes() {
// Set up our routes. We still need to copy middleware from inner service // Set up our routes. We still need to copy middleware from inner service
Map restProvider = {'provider': Providers.REST}; Map restProvider = {'provider': Providers.REST};
@ -45,7 +48,7 @@ class HookedService extends Service {
Middleware before = getAnnotation(inner, Middleware); Middleware before = getAnnotation(inner, Middleware);
final handlers = []; final handlers = [];
if (before != null) handlers.add(before.handlers); if (before != null) handlers.addAll(before.handlers);
Middleware indexMiddleware = getAnnotation(inner.index, Middleware); Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
get('/', (req, res) async { get('/', (req, res) async {
@ -67,7 +70,7 @@ class HookedService extends Service {
get( get(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])), .read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -76,7 +79,7 @@ class HookedService extends Service {
patch( patch(
'/:id', '/:id',
(req, res) async => (req, res) async =>
await this.modify(req.params['id'], req.body, restProvider), await this.modify(req.params['id'], req.body, restProvider),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -86,7 +89,7 @@ class HookedService extends Service {
post( post(
'/:id', '/:id',
(req, res) async => (req, res) async =>
await this.update(req.params['id'], req.body, restProvider), await this.update(req.params['id'], req.body, restProvider),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -96,7 +99,7 @@ class HookedService extends Service {
delete( delete(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])), .remove(req.params['id'], mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(

View file

@ -109,8 +109,19 @@ class ResponseContext extends Extensible {
/// Redirects to the given named [Route]. /// Redirects to the given named [Route].
void redirectTo(String name, [Map params, int code]) { void redirectTo(String name, [Map params, int code]) {
// Todo: Need to recurse route hierarchy, but also efficiently :) _findRoute(Route route) {
Route matched = app.routes.firstWhere((Route route) => route.name == name); for (Route child in route.children) {
final resolved = _findRoute(child);
if (resolved != null) return resolved;
}
return route.children
.firstWhere((r) => r.name == name, orElse: () => null);
}
Route matched = _findRoute(app.root);
if (matched != null) { if (matched != null) {
redirect(matched.makeUri(params), code: code); redirect(matched.makeUri(params), code: code);
return; return;
@ -160,7 +171,8 @@ class ResponseContext extends Extensible {
if (isOpen) { if (isOpen) {
if (value is List<int>) if (value is List<int>)
buffer.add(value); buffer.add(value);
else buffer.add(encoding.encode(value.toString())); else
buffer.add(encoding.encode(value.toString()));
} }
} }
} }

View file

@ -33,12 +33,10 @@ class Routable extends Router {
final Map<String, RequestMiddleware> requestMiddleware = {}; final Map<String, RequestMiddleware> requestMiddleware = {};
/// A set of [Service] objects that have been mapped into routes. /// A set of [Service] objects that have been mapped into routes.
Map<Pattern, Service> get services => Map<Pattern, Service> get services => _services;
new Map<Pattern, Service>.unmodifiable(_services);
/// A set of [Controller] objects that have been loaded into the application. /// A set of [Controller] objects that have been loaded into the application.
Map<String, Controller> get controllers => Map<String, Controller> get controllers => _controllers;
new Map<String, Controller>.unmodifiable(_controllers);
StreamController<Service> _onService = StreamController<Service> _onService =
new StreamController<Service>.broadcast(); new StreamController<Service>.broadcast();
@ -61,7 +59,7 @@ class Routable extends Router {
@override @override
Route addRoute(String method, Pattern path, Object handler, Route addRoute(String method, Pattern path, Object handler,
{List middleware}) { {List middleware: const []}) {
final List handlers = []; final List handlers = [];
// Merge @Middleware declaration, if any // Merge @Middleware declaration, if any
Middleware middlewareDeclaration = getAnnotation(handler, Middleware); Middleware middlewareDeclaration = getAnnotation(handler, Middleware);
@ -69,8 +67,11 @@ class Routable extends Router {
handlers.addAll(middlewareDeclaration.handlers); handlers.addAll(middlewareDeclaration.handlers);
} }
return super.addRoute(method, path, handler, final List handlerSequence = [];
middleware: []..addAll(middleware ?? [])..addAll(handlers)); handlerSequence.addAll(middleware ?? []);
handlerSequence.addAll(handlers);
return super.addRoute(method, path, handler, middleware: handlerSequence);
} }
void use(Pattern path, Router router, void use(Pattern path, Router router,
@ -89,9 +90,11 @@ class Routable extends Router {
.toString() .toString()
.trim() .trim()
.replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = service; .replaceAll(new RegExp(r'(^/+)|(/+$)'), '')] = service;
service.addRoutes();
} }
final handlers = []; final handlers = [];
if (_router is AngelBase) { if (_router is AngelBase) {
handlers.add((RequestContext req, ResponseContext res) async { handlers.add((RequestContext req, ResponseContext res) async {
req.app = _router; req.app = _router;
@ -109,9 +112,9 @@ class Routable extends Router {
copiedMiddleware[middlewareName]; copiedMiddleware[middlewareName];
} }
root.child(path, debug: debug, handlers: handlers).addChild(router.root); // _router.dumpTree(header: 'Mounting on "$path":');
// root.child(path, debug: debug, handlers: handlers).addChild(router.root);
_router.dumpTree(header: 'Mounting on "$path":'); mount(path, _router);
if (router is Routable) { if (router is Routable) {
// Copy services, too. :) // Copy services, too. :)

View file

@ -98,8 +98,8 @@ class Angel extends AngelBase {
/// Returns false on failure; otherwise, returns the HttpServer. /// Returns false on failure; otherwise, returns the HttpServer.
Future<HttpServer> startServer([InternetAddress address, int port]) async { Future<HttpServer> startServer([InternetAddress address, int port]) async {
final host = address ?? InternetAddress.LOOPBACK_IP_V4; final host = address ?? InternetAddress.LOOPBACK_IP_V4;
final server = await _serverGenerator(host, port ?? 0); this.httpServer = await _serverGenerator(host, port ?? 0);
return this.httpServer = server..listen(handleRequest); return httpServer..listen(handleRequest);
} }
/// Loads some base dependencies into the service container. /// Loads some base dependencies into the service container.
@ -178,14 +178,13 @@ class Angel extends AngelBase {
final req = await RequestContext.from(request, this); final req = await RequestContext.from(request, this);
final res = new ResponseContext(request.response, this); final res = new ResponseContext(request.response, this);
String requestedUrl = request.uri String requestedUrl = request.uri
.toString() .path
.replaceAll("?" + request.uri.query, "")
.replaceAll(_straySlashes, ''); .replaceAll(_straySlashes, '');
if (requestedUrl.isEmpty) requestedUrl = '/'; if (requestedUrl.isEmpty) requestedUrl = '/';
final route = resolve(requestedUrl, method: request.method); final route = resolve(requestedUrl, method: request.method);
print('Resolve ${requestedUrl} -> $route'); _printDebug('Resolved ${requestedUrl} -> $route');
req.params.addAll(route?.parseParameters(requestedUrl) ?? {}); req.params.addAll(route?.parseParameters(requestedUrl) ?? {});
final handlerSequence = []..addAll(before); final handlerSequence = []..addAll(before);
@ -331,8 +330,8 @@ class Angel extends AngelBase {
bootstrapContainer(); bootstrapContainer();
_serverGenerator = (InternetAddress address, int port) async { _serverGenerator = (InternetAddress address, int port) async {
var certificateChain = var certificateChain =
Platform.script.resolve('server_chain.pem').toFilePath(); Platform.script.resolve(certificateChainPath).toFilePath();
var serverKey = Platform.script.resolve('server_key.pem').toFilePath(); var serverKey = Platform.script.resolve(serverKeyPath).toFilePath();
var serverContext = new SecurityContext(); var serverContext = new SecurityContext();
serverContext.useCertificateChain(certificateChain); serverContext.useCertificateChain(certificateChain);
serverContext.usePrivateKey(serverKey, serverContext.usePrivateKey(serverKey,

View file

@ -65,14 +65,14 @@ class Service extends Routable {
throw new AngelHttpException.MethodNotAllowed(); throw new AngelHttpException.MethodNotAllowed();
} }
Service() : super() { void addRoutes() {
Map restProvider = {'provider': Providers.REST}; Map restProvider = {'provider': Providers.REST};
// Add global middleware if declared on the instance itself // Add global middleware if declared on the instance itself
Middleware before = getAnnotation(this, Middleware); Middleware before = getAnnotation(this, Middleware);
final handlers = []; final handlers = [];
if (before != null) handlers.add(before.handlers); if (before != null) handlers.addAll(before.handlers);
Middleware indexMiddleware = getAnnotation(this.index, Middleware); Middleware indexMiddleware = getAnnotation(this.index, Middleware);
get('/', (req, res) async { get('/', (req, res) async {
@ -94,7 +94,7 @@ class Service extends Routable {
get( get(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.read(req.params['id'], mergeMap([req.query, restProvider])), .read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll((readMiddleware == null) ? [] : readMiddleware.handlers)); ..addAll((readMiddleware == null) ? [] : readMiddleware.handlers));
@ -103,7 +103,7 @@ class Service extends Routable {
patch( patch(
'/:id', '/:id',
(req, res) async => (req, res) async =>
await this.modify(req.params['id'], req.body, restProvider), await this.modify(req.params['id'], req.body, restProvider),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -113,7 +113,7 @@ class Service extends Routable {
post( post(
'/:id', '/:id',
(req, res) async => (req, res) async =>
await this.update(req.params['id'], req.body, restProvider), await this.update(req.params['id'], req.body, restProvider),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
@ -123,10 +123,12 @@ class Service extends Routable {
delete( delete(
'/:id', '/:id',
(req, res) async => await this (req, res) async => await this
.remove(req.params['id'], mergeMap([req.query, restProvider])), .remove(req.params['id'], mergeMap([req.query, restProvider])),
middleware: [] middleware: []
..addAll(handlers) ..addAll(handlers)
..addAll( ..addAll(
(removeMiddleware == null) ? [] : removeMiddleware.handlers)); (removeMiddleware == null) ? [] : removeMiddleware.handlers));
normalize();
} }
} }

View file

@ -1,13 +1,12 @@
name: angel_framework name: angel_framework
version: 1.0.0-dev.22 version: 1.0.0-dev.23
description: Core libraries for the Angel framework. description: Core libraries for the Angel framework.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework homepage: https://github.com/angel-dart/angel_framework
environment: environment:
sdk: ">=1.18.0" sdk: ">=1.18.0"
dependencies: dependencies:
angel_route: angel_route: ^1.0.0-dev
path: ../angel_route
body_parser: ^1.0.0-dev body_parser: ^1.0.0-dev
container: ^0.1.2 container: ^0.1.2
json_god: ^2.0.0-beta json_god: ^2.0.0-beta

View file

@ -10,23 +10,23 @@ import 'common.dart';
class TodoController extends Controller { class TodoController extends Controller {
List<Todo> todos = [new Todo(text: "Hello", over: "world")]; List<Todo> todos = [new Todo(text: "Hello", over: "world")];
@Expose("/:id", middleware: const["bar"]) @Expose("/:id", middleware: const ["bar"])
Future<Todo> fetchTodo(int id, RequestContext req, Future<Todo> fetchTodo(
ResponseContext res) async { int id, RequestContext req, ResponseContext res) async {
expect(req, isNotNull); expect(req, isNotNull);
expect(res, isNotNull); expect(res, isNotNull);
return todos[id]; return todos[id];
} }
@Expose("/namedRoute/:foo", as: "foo") @Expose("/namedRoute/:foo", as: "foo")
Future<String> someRandomRoute(RequestContext req, Future<String> someRandomRoute(
ResponseContext res) async { RequestContext req, ResponseContext res) async {
return "${req.params['foo']}!"; return "${req.params['foo']}!";
} }
} }
main() { main() {
Angel app = new Angel(); Angel app;
HttpServer server; HttpServer server;
InternetAddress host = InternetAddress.LOOPBACK_IP_V4; InternetAddress host = InternetAddress.LOOPBACK_IP_V4;
int port = 3000; int port = 3000;
@ -34,25 +34,25 @@ main() {
String url = "http://${host.address}:$port"; String url = "http://${host.address}:$port";
setUp(() async { setUp(() async {
app = new Angel();
app.registerMiddleware("foo", (req, res) async => res.write("Hello, ")); app.registerMiddleware("foo", (req, res) async => res.write("Hello, "));
app.registerMiddleware("bar", (req, res) async => res.write("world!")); app.registerMiddleware("bar", (req, res) async => res.write("world!"));
app.get("/redirect", (req, ResponseContext res) async => app.get(
res.redirectToAction("TodoController@foo", {"foo": "world"})); "/redirect",
(req, ResponseContext res) async =>
res.redirectToAction("TodoController@foo", {"foo": "world"}));
await app.configure(new TodoController()); await app.configure(new TodoController());
print(app.controllers); print(app.controllers);
print("\nDUMPING ROUTES:"); app.dumpTree();
app.routes.forEach((Route route) {
print("\t${route.method} ${route.path} -> ${route.handlers}");
});
print("\n");
server = await app.startServer(host, port); server = await app.startServer(host, port);
client = new http.Client(); client = new http.Client();
}); });
tearDown(() async { tearDown(() async {
await (server ?? app.httpServer).close(force: true); await server.close(force: true);
app = null;
client.close(); client.close();
client = null; client = null;
}); });
@ -62,7 +62,7 @@ main() {
var response = await client.get("$url/todos/0"); var response = await client.get("$url/todos/0");
print(response.body); print(response.body);
expect(response.body.indexOf("Hello, "), equals(0)); expect(rgx.firstMatch(response.body).start, equals(0));
Map todo = JSON.decode(response.body.replaceAll(rgx, "")); Map todo = JSON.decode(response.body.replaceAll(rgx, ""));
print("Todo: $todo"); print("Todo: $todo");

View file

@ -15,7 +15,7 @@ main() {
String url; String url;
setUp(() async { setUp(() async {
app = new Angel(); app = new Angel(debug: true);
client = new http.Client(); client = new http.Client();
// Inject some todos // Inject some todos
@ -27,6 +27,7 @@ main() {
await app.configure(new ErrandController()); await app.configure(new ErrandController());
server = await app.startServer(); server = await app.startServer();
print('server: $server, httpServer: ${app.httpServer}');
url = "http://${server.address.host}:${server.port}"; url = "http://${server.address.host}:${server.port}";
}); });

View file

@ -1,3 +1,4 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:json_god/json_god.dart' as god; import 'package:json_god/json_god.dart' as god;
@ -6,74 +7,86 @@ import 'common.dart';
main() { main() {
Map headers = { Map headers = {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}; };
Angel app;
String url;
http.Client client;
HookedService Todos;
setUp(() async { Angel app;
app = new Angel(); HttpServer server;
client = new http.Client(); String url;
app.use('/todos', new MemoryService<Todo>()); http.Client client;
Todos = app.service("todos"); HookedService Todos;
await app.startServer(null, 0); setUp(() async {
url = "http://${app.httpServer.address.host}:${app.httpServer.port}"; app = new Angel(debug: true);
}); client = new http.Client();
app.use('/todos', new MemoryService<Todo>());
Todos = app.service("todos");
tearDown(() async { app
app = null; ..normalize()
url = null; ..dumpTree(showMatchers: true);
client.close();
client = null;
Todos = null;
});
test("listen before and after", () async { server = await app.startServer();
int count = 0; url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
});
Todos tearDown(() async {
..beforeIndexed.listen((_) { await server.close(force: true);
count++; app = null;
}) url = null;
..afterIndexed.listen((_) { client.close();
count++; client = null;
}); Todos = null;
});
var response = await client.get("$url/todos"); test("listen before and after", () async {
print(response.body); int count = 0;
expect(count, equals(2));
});
test("cancel before", () async { Todos
Todos.beforeCreated..listen((HookedServiceEvent event) { ..beforeIndexed.listen((_) {
count++;
})
..afterIndexed.listen((_) {
count++;
});
var response = await client.get("$url/todos");
print(response.body);
expect(count, equals(2));
});
test("cancel before", () async {
Todos.beforeCreated
..listen((HookedServiceEvent event) {
event.cancel({"hello": "hooked world"}); event.cancel({"hello": "hooked world"});
})..listen((HookedServiceEvent event) { })
..listen((HookedServiceEvent event) {
event.cancel({"this_hook": "should never run"}); event.cancel({"this_hook": "should never run"});
}); });
var response = await client.post( var response = await client.post("$url/todos",
"$url/todos", body: god.serialize({"arbitrary": "data"}), body: god.serialize({"arbitrary": "data"}), headers: headers);
headers: headers); print(response.body);
print(response.body); Map result = god.deserialize(response.body);
Map result = god.deserialize(response.body); expect(result["hello"], equals("hooked world"));
expect(result["hello"], equals("hooked world")); });
});
test("cancel after", () async { test("cancel after", () async {
Todos.afterIndexed..listen((HookedServiceEvent event) async { Todos.afterIndexed
..listen((HookedServiceEvent event) async {
// Hooks can be Futures ;) // Hooks can be Futures ;)
event.cancel([{"angel": "framework"}]); event.cancel([
})..listen((HookedServiceEvent event) { {"angel": "framework"}
]);
})
..listen((HookedServiceEvent event) {
event.cancel({"this_hook": "should never run either"}); event.cancel({"this_hook": "should never run either"});
}); });
var response = await client.get("$url/todos"); var response = await client.get("$url/todos");
print(response.body); print(response.body);
List result = god.deserialize(response.body); List result = god.deserialize(response.body);
expect(result[0]["angel"], equals("framework")); expect(result[0]["angel"], equals("framework"));
}); });
} }

View file

@ -26,7 +26,7 @@ main() {
http.Client client; http.Client client;
setUp(() async { setUp(() async {
final debug = false; final debug = true;
angel = new Angel(debug: debug); angel = new Angel(debug: debug);
nested = new Angel(debug: debug); nested = new Angel(debug: debug);
todos = new Angel(debug: debug); todos = new Angel(debug: debug);
@ -38,7 +38,7 @@ main() {
}) })
..registerMiddleware('intercept_service', ..registerMiddleware('intercept_service',
(RequestContext req, res) async { (RequestContext req, res) async {
print("Intercepting a service!"); res.write("Service with ");
return true; return true;
}); });
@ -48,7 +48,8 @@ main() {
ted = nested.post('/ted/:route', (RequestContext req, res) { ted = nested.post('/ted/:route', (RequestContext req, res) {
print('Params: ${req.params}'); print('Params: ${req.params}');
print('Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}'); print(
'Path: ${ted.path}, matcher: ${ted.matcher.pattern}, uri: ${req.path}');
return req.params; return req.params;
}); });
@ -75,7 +76,9 @@ main() {
angel.use('/query', new QueryService()); angel.use('/query', new QueryService());
angel.get('*', 'MJ'); angel.get('*', 'MJ');
angel.dumpTree(header: "DUMPING ROUTES:"); angel
..normalize()
..dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
client = new http.Client(); client = new http.Client();
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0); await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
@ -101,6 +104,7 @@ main() {
var response = await client.get('$url/name/HELLO/last/WORLD'); var response = await client.get('$url/name/HELLO/last/WORLD');
print(response.body); print(response.body);
var json = god.deserialize(response.body); var json = god.deserialize(response.body);
expect(json, new isInstanceOf<Map<String, String>>());
expect(json['first'], equals('HELLO')); expect(json['first'], equals('HELLO'));
expect(json['last'], equals('WORLD')); expect(json['last'], equals('WORLD'));
}); });
@ -165,6 +169,6 @@ main() {
response = await client.get("$url/query/foo?bar=baz"); response = await client.get("$url/query/foo?bar=baz");
print(response.body); print(response.body);
expect(response.body, equals("Middleware")); expect(response.body, equals("Service with Middleware"));
}); });
} }