All tests now passing

This commit is contained in:
thosakwe 2016-10-20 23:13:13 -04:00
parent 6dbccd2be6
commit 1b37e0a2a3
13 changed files with 294 additions and 95 deletions

View file

@ -9,6 +9,7 @@
<excludeFolder url="file://$MODULE_DIR$/test/packages" /> <excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/route/packages" /> <excludeFolder url="file://$MODULE_DIR$/test/route/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/router/packages" /> <excludeFolder url="file://$MODULE_DIR$/test/router/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/server/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" /> <excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/web/hash/packages" /> <excludeFolder url="file://$MODULE_DIR$/web/hash/packages" />
<excludeFolder url="file://$MODULE_DIR$/web/packages" /> <excludeFolder url="file://$MODULE_DIR$/web/packages" />

View file

@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Server Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="Server Tests" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/server/all_tests.dart" />
<method />
</configuration>
</component>

View file

@ -17,7 +17,7 @@ abstract class BrowserRouter extends Router {
: new _PushStateRouter(listen: listen, root: root); : new _PushStateRouter(listen: listen, root: root);
} }
BrowserRouter._([Route root]) : super(root); BrowserRouter._([Route root]) : super(root: root);
/// Calls `goTo` on the [Route] matching `path`. /// Calls `goTo` on the [Route] matching `path`.
void go(String path, [Map params]); void go(String path, [Map params]);
@ -37,7 +37,7 @@ class _BrowserRouterImpl extends Router implements BrowserRouter {
@override @override
Stream<Route> get onRoute => _onRoute.stream; Stream<Route> get onRoute => _onRoute.stream;
_BrowserRouterImpl({bool listen, Route root}) : super(root) { _BrowserRouterImpl({bool listen, Route root}) : super(root: root) {
if (listen) this.listen(); if (listen) this.listen();
prepareAnchors(); prepareAnchors();
} }

View file

@ -115,6 +115,15 @@ class Route {
return result; return result;
} }
/// Returns the [Route] instance that will respond to requests
/// to the index of this instance's path.
///
/// May return `this`.
Route get indexRoute {
return children.firstWhere((r) => r.path.replaceAll(path, '').isEmpty,
orElse: () => this);
}
void _printDebug(msg) { void _printDebug(msg) {
if (debug) print(msg); if (debug) print(msg);
} }
@ -157,6 +166,7 @@ class Route {
/// The final child route is returned. /// The final child route is returned.
factory Route.build(Pattern path, factory Route.build(Pattern path,
{Iterable<Route> children: const [], {Iterable<Route> children: const [],
bool debug: false,
Iterable handlers: const [], Iterable handlers: const [],
method: "GET", method: "GET",
String name: null}) { String name: null}) {
@ -169,7 +179,11 @@ class Route {
if (segments.isEmpty) { if (segments.isEmpty) {
return new Route('/', return new Route('/',
children: children, handlers: handlers, method: method, name: name); children: children,
debug: debug,
handlers: handlers,
method: method,
name: name);
} }
for (int i = 0; i < segments.length; i++) { for (int i = 0; i < segments.length; i++) {
@ -177,15 +191,15 @@ class Route {
if (i == segments.length - 1) { if (i == segments.length - 1) {
if (result == null) { if (result == null) {
result = new Route(segment); result = new Route(segment, debug: debug);
} else { } else {
result = result.child(segment); result = result.child(segment, debug: debug);
} }
} else { } else {
if (result == null) { if (result == null) {
result = new Route(segment, method: "*"); result = new Route(segment, debug: debug, method: "*");
} else { } else {
result = result.child(segment, method: "*"); result = result.child(segment, debug: debug, method: "*");
} }
} }
} }
@ -195,11 +209,11 @@ class Route {
result._method = method; result._method = method;
result._name = name; result._name = name;
return result; return result..debug = debug;
} }
/// Combines the paths and matchers of two [Route] instances, and creates a new instance. /// Combines the paths and matchers of two [Route] instances, and creates a new instance.
factory Route.join(Route parent, Route child) { factory Route.join(Route parent, Route child, {bool debug: false}) {
final String path1 = parent.path final String path1 = parent.path
.replaceAll(_rgxStart, '') .replaceAll(_rgxStart, '')
.replaceAll(_rgxEnd, '') .replaceAll(_rgxEnd, '')
@ -217,7 +231,6 @@ class Route {
final route = new Route('$path1/$path2', final route = new Route('$path1/$path2',
children: child.children, children: child.children,
debug: parent.debug || child.debug,
handlers: child.handlers, handlers: child.handlers,
method: child.method, method: child.method,
name: child.name); name: child.name);
@ -228,10 +241,29 @@ class Route {
.._matcher = new RegExp('$pattern1$separator$pattern2') .._matcher = new RegExp('$pattern1$separator$pattern2')
.._parent = parent .._parent = parent
.._stub = child.matcher); .._stub = child.matcher);
parent._printDebug(
'Joined $path1 and $path2, produced stub ${route._stub.pattern}');
return route; parent._printDebug(
"Joined '/$path1' and '/$path2', created stub: ${route._stub.pattern}");
return route..debug = parent.debug || child.debug || debug;
}
Route _inherit(Route route) {
/*
final List<Route> _children = [];
final List _handlers = [];
RegExp _matcher;
String _method;
String _name;
Route _parent;
RegExp _parentResolver;
String _path;
String _pathified;
RegExp _resolver;
RegExp _stub;
*/
return route.._parent = this;
} }
/// Calls [addChild] on all given routes. /// Calls [addChild] on all given routes.
@ -241,7 +273,14 @@ class Route {
/// Adds the given route as a hierarchical child of this one. /// Adds the given route as a hierarchical child of this one.
Route addChild(Route route, {bool join: true}) { Route addChild(Route route, {bool join: true}) {
Route created = join ? new Route.join(this, route) : route.._parent = this; Route created;
if (join) {
created = new Route.join(this, route);
} else {
_children.add(created = route.._parent = this);
}
return created..debug = debug; return created..debug = debug;
} }
@ -251,11 +290,16 @@ class Route {
/// Creates a hierarchical child of this route with the given path. /// Creates a hierarchical child of this route with the given path.
Route child(Pattern path, Route child(Pattern path,
{Iterable<Route> children: const [], {Iterable<Route> children: const [],
bool debug: false,
Iterable handlers: const [], Iterable handlers: const [],
String method: "GET", String method: "GET",
String name: null}) { String name: null}) {
final route = new Route.build(path, final route = new Route.build(path,
children: children, handlers: handlers, method: method, name: name); children: children,
debug: debug,
handlers: handlers,
method: method,
name: name);
return addChild(route); return addChild(route);
} }
@ -310,25 +354,39 @@ class Route {
/// ///
/// Can be used to navigate a route hierarchy like a file system. /// Can be used to navigate a route hierarchy like a file system.
Route resolve(String path, {bool filter(Route route), String fullPath}) { Route resolve(String path, {bool filter(Route route), String fullPath}) {
final _filter = filter ?? (_) => true; bool _filter(route) {
if (filter == null) {
_printDebug('No filter provided, returning true for $route');
return true;
} else {
_printDebug('Running filter on $route');
final result = filter(route);
_printDebug('Filter result: $result');
return result;
}
}
final _fullPath = fullPath ?? path; final _fullPath = fullPath ?? path;
if ((path.isEmpty || path == '.') && _filter(this)) { if ((path.isEmpty || path == '.') && _filter(indexRoute)) {
// Try to find index // Try to find index
_printDebug('INDEX???'); _printDebug('Empty path, resolving with indexRoute: $indexRoute');
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this); return indexRoute;
} else if (path == '/') { } else if (path == '/') {
return absoluteParent.resolve(''); return absoluteParent.resolve('');
} else if (path.replaceAll(_straySlashes, '').isEmpty) { } else if (path.replaceAll(_straySlashes, '').isEmpty) {
for (Route route in children) { for (Route route in children) {
final stub = route.path.replaceAll(this.path, ''); final stub = route.path.replaceAll(this.path, '');
if ((stub == '/' || stub.isEmpty) && _filter(route)) return route; if ((stub == '/' || stub.isEmpty) && _filter(route))
return route.resolve('');
} }
if (_filter(this)) if (_filter(indexRoute)) {
return this; _printDebug(
else 'Path "/$path" is technically empty, sending to indexRoute: $indexRoute');
return indexRoute;
} else
return null; return null;
} else if (path == '..') { } else if (path == '..') {
if (parent != null) if (parent != null)
@ -343,13 +401,16 @@ class Route {
filter: _filter, fullPath: _fullPath); filter: _filter, fullPath: _fullPath);
} else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) || } else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) ||
_resolver.hasMatch(path.replaceAll(_straySlashes, ''))) { _resolver.hasMatch(path.replaceAll(_straySlashes, ''))) {
return this; _printDebug(
'Path "/$path" matched our matcher, sending to indexRoute: $indexRoute');
return indexRoute;
} else { } else {
final segments = path.split('/').where((str) => str.isNotEmpty).toList(); final segments = path.split('/').where((str) => str.isNotEmpty).toList();
_printDebug('Segments: $segments on "/${this.path}"'); _printDebug('Segments: $segments on "/${this.path}"');
if (segments.isEmpty) { if (segments.isEmpty) {
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this); _printDebug('Empty segments, sending to indexRoute: $indexRoute');
return indexRoute;
} }
if (segments[0] == '..') { if (segments[0] == '..') {
@ -366,12 +427,12 @@ class Route {
for (Route route in children) { for (Route route in children) {
final subPath = '${this.path}/${segments[0]}'; final subPath = '${this.path}/${segments[0]}';
_printDebug( _printDebug(
'seg0: ${segments[0]}, stub: ${route._stub.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}'); 'seg0: ${segments[0]}, stub: ${route._stub?.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}');
if (route.match(subPath) != null || if (route.match(subPath) != null ||
route._resolver.firstMatch(subPath) != null) { route._resolver.firstMatch(subPath) != null) {
if (segments.length == 1 && _filter(route)) if (segments.length == 1 && _filter(route))
return route; return route.resolve('');
else { else {
return route.resolve(segments.skip(1).join('/'), return route.resolve(segments.skip(1).join('/'),
filter: _filter, filter: _filter,
@ -380,8 +441,14 @@ class Route {
_fullPath.replaceAll(_straySlashes, '')); _fullPath.replaceAll(_straySlashes, ''));
} }
} else if (route._stub != null && route._stub.hasMatch(segments[0])) { } else if (route._stub != null && route._stub.hasMatch(segments[0])) {
_printDebug('MAYBE STUB?'); if (segments.length == 1) {
return route; _printDebug('Stub perhaps matches');
return route.resolve('');
} else {
_printDebug(
'Maybe stub matches. Sending remaining segments to $route');
return route.resolve(segments.skip(1).join('/'));
}
} }
} }
@ -405,38 +472,11 @@ class Route {
(child.match(_fullPath) != null || (child.match(_fullPath) != null ||
child._resolver.firstMatch(_fullPath) != null) && child._resolver.firstMatch(_fullPath) != null) &&
_filter(child)) { _filter(child)) {
return child; return child.resolve('');
} }
} }
_printDebug('No subpath match: $subPath');
} else
_printDebug('Nope: $_parentResolver');
}
/*
// Try to fill params
for (Route route in children) {
final params = parseParameters(_fullPath);
final _filledPath = makeUri(params);
_printDebug(
'Trying to match filled $_filledPath for ${route.path} on ${this.path}');
if ((route.match(_filledPath) != null ||
route._resolver.firstMatch(_filledPath) != null) &&
_filter(route))
return route;
else if ((route.match(_filledPath) != null ||
route._resolver.firstMatch(_filledPath) != null) &&
_filter(route))
return route;
else if ((route.match('/$_filledPath') != null ||
route._resolver.firstMatch('/$_filledPath') != null) &&
_filter(route))
return route;
else {
_printDebug('Failed for ${route.matcher} when given $_filledPath');
} }
}*/ }
// Try to match the whole route, if nothing else works // Try to match the whole route, if nothing else works
for (Route route in children) { for (Route route in children) {
@ -445,18 +485,20 @@ class Route {
if ((route.match(_fullPath) != null || if ((route.match(_fullPath) != null ||
route._resolver.firstMatch(_fullPath) != null) && route._resolver.firstMatch(_fullPath) != null) &&
_filter(route)) _filter(route))
return route; return route.resolve('');
else if ((route.match(_fullPath) != null || else if ((route.match(_fullPath) != null ||
route._resolver.firstMatch(_fullPath) != null) && route._resolver.firstMatch(_fullPath) != null) &&
_filter(route)) _filter(route))
return route; return route.resolve('');
else if ((route.match('/$_fullPath') != null || else if ((route.match('/$_fullPath') != null ||
route._resolver.firstMatch('/$_fullPath') != null) && route._resolver.firstMatch('/$_fullPath') != null) &&
_filter(route)) _filter(route)) return route.resolve('');
return route; }
else {
_printDebug('Failed for ${route.matcher} when given $_fullPath'); // Lastly, check to see if we have an index route to resolve with
} if (indexRoute != this) {
_printDebug('Forwarding "/$path" to indexRoute');
return indexRoute.resolve(path);
} }
return null; return null;

View file

@ -6,6 +6,8 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :) /// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
class Router extends Extensible { class Router extends Extensible {
Route _root;
/// Set to `true` to print verbose debug output when interacting with this route. /// Set to `true` to print verbose debug output when interacting with this route.
bool debug = false; bool debug = false;
@ -13,15 +15,16 @@ class Router extends Extensible {
Map<String, dynamic> requestMiddleware = {}; Map<String, dynamic> requestMiddleware = {};
/// The single [Route] that serves as the root of the hierarchy. /// The single [Route] that serves as the root of the hierarchy.
final Route root; Route get root => _root;
/// Provide a `root` to make this Router revolve around a pre-defined route. /// Provide a `root` to make this Router revolve around a pre-defined route.
/// Not recommended. /// Not recommended.
Router([Route root]) : this.root = root ?? new _RootRoute(); Router({this.debug: false, Route root}) {
_root = (_root = root ?? new _RootRoute())..debug = debug;
}
void _printDebug(msg) { void _printDebug(msg) {
if (debug) if (debug) print(msg);
_printDebug(msg);
} }
/// Adds a route that responds to the given path /// Adds a route that responds to the given path
@ -36,9 +39,10 @@ class Router extends Extensible {
..add(handler); ..add(handler);
if (path is RegExp) { if (path is RegExp) {
return root.child(path, handlers: handlers, method: method); return root.child(path, debug: debug, handlers: handlers, method: method);
} else if (path.toString().replaceAll(_straySlashes, '').isEmpty) { } else if (path.toString().replaceAll(_straySlashes, '').isEmpty) {
return root.child(path.toString(), handlers: handlers, method: method); return root.child(path.toString(),
debug: debug, handlers: handlers, method: method);
} else { } else {
var segments = path var segments = path
.toString() .toString()
@ -48,9 +52,9 @@ class Router extends Extensible {
Route result; Route result;
if (segments.isEmpty) { if (segments.isEmpty) {
return new Route('/', handlers: handlers, method: method); return new Route('/', debug: debug, handlers: handlers, method: method)
..debug = debug;
} else { } else {
_printDebug('Want ${segments[0]}');
result = resolve(segments[0]); result = resolve(segments[0]);
if (result != null) { if (result != null) {
@ -67,7 +71,9 @@ class Router extends Extensible {
result = existing; result = existing;
} }
} while (existing != null); } while (existing != null);
} else throw new RoutingException("Cannot overwrite existing route '${segments[0]}'."); } else
throw new RoutingException(
"Cannot overwrite existing route '${segments[0]}'.");
} }
} }
@ -76,15 +82,17 @@ class Router extends Extensible {
if (i == segments.length - 1) { if (i == segments.length - 1) {
if (result == null) { if (result == null) {
result = root.child(segment, handlers: handlers, method: method); result = root.child(segment,
debug: debug, handlers: handlers, method: method);
} else { } else {
result = result.child(segment, handlers: handlers, method: method); result = result.child(segment,
debug: debug, handlers: handlers, method: method);
} }
} else { } else {
if (result == null) { if (result == null) {
result = root.child(segment, method: "*"); result = root.child(segment, debug: debug, method: "*");
} else { } else {
result = result.child(segment, method: "*"); result = result.child(segment, debug: debug, method: "*");
} }
} }
} }
@ -105,7 +113,8 @@ class Router extends Extensible {
if (route == root) if (route == root)
buf.write('(root) ${route.method} '); buf.write('(root) ${route.method} ');
else buf.write('- ${route.method} '); else
buf.write('- ${route.method} ');
final p = final p =
replace != null ? route.path.replaceAll(replace, '') : route.path; replace != null ? route.path.replaceAll(replace, '') : route.path;
@ -143,7 +152,7 @@ class Router extends Extensible {
String namespace: null}) { String namespace: null}) {
final route = final route =
root.child(path, handlers: middleware, method: method, name: name); root.child(path, handlers: middleware, method: method, name: name);
final router = new Router(route); final router = new Router(root: route);
callback(router); callback(router);
// Let's copy middleware, heeding the optional middleware namespace. // Let's copy middleware, heeding the optional middleware namespace.
@ -191,7 +200,7 @@ class Router extends Extensible {
copiedMiddleware[middlewareName]; copiedMiddleware[middlewareName];
} }
root.child(path).addChild(router.root); root.child(path, debug: debug).addChild(router.root);
} }
/// Adds a route that responds to any request matching the given path. /// Adds a route that responds to any request matching the given path.
@ -236,8 +245,7 @@ class Router extends Extensible {
} }
class _RootRoute extends Route { class _RootRoute extends Route {
_RootRoute():super("/", name: "<root>"); _RootRoute() : super("/", name: "<root>");
@override @override
String toString() => "ROOT"; String toString() => "ROOT";

View file

@ -5,4 +5,5 @@ author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_route homepage: https://github.com/angel-dart/angel_route
dev_dependencies: dev_dependencies:
browser: ">=0.10.0 < 0.11.0" browser: ">=0.10.0 < 0.11.0"
http: ">=0.11.3 <0.12.0"
test: ">=0.12.15 <0.13.0" test: ">=0.12.15 <0.13.0"

View file

@ -0,0 +1,8 @@
import 'package:test/test.dart';
import 'route/all_tests.dart' as route;
import 'router/all_tests.dart' as router;
main() {
group('route', route.main);
group('router', router.main);
}

View file

@ -1,8 +1,10 @@
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'route/all_tests.dart' as route; import 'route/all_tests.dart' as route;
import 'router/all_tests.dart' as router; import 'router/all_tests.dart' as router;
import 'server/all_tests.dart' as server;
main() { main() {
group('route', route.main); group('route', route.main);
group('router', router.main); group('router', router.main);
group('server', server.main);
} }

View file

@ -5,7 +5,7 @@
<title>Tests</title> <title>Tests</title>
</head> </head>
<body> <body>
<script src="all_tests.dart" type="application/dart"></script> <script src="all_tests.browser.dart" type="application/dart"></script>
<script src="packages/browser/dart.js"></script> <script src="packages/browser/dart.js"></script>
</body> </body>
</html> </html>

View file

@ -7,7 +7,7 @@ main() {
final bar = fooById.child('bar'); final bar = fooById.child('bar');
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']); final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
final bazById = baz.child(':bazId([A-Za-z]+)'); final bazById = baz.child(':bazId([A-Za-z]+)');
new Router(foo).dumpTree(); new Router(root: foo).dumpTree();
test('matching', () { test('matching', () {
expect(fooById.children.length, equals(1)); expect(fooById.children.length, equals(1));
@ -54,7 +54,7 @@ main() {
expect(bar.resolve('..'), equals(fooById)); expect(bar.resolve('..'), equals(fooById));
new Router(bar.parent).dumpTree(header: "POOP"); new Router(root: bar.parent).dumpTree(header: "POOP");
expect(bar.parent.resolve('bar/baz'), equals(baz)); expect(bar.parent.resolve('bar/baz'), equals(baz));
expect(bar.resolve('/2/bar/baz'), equals(baz)); expect(bar.resolve('/2/bar/baz'), equals(baz));
expect(bar.resolve('../bar'), equals(bar)); expect(bar.resolve('../bar'), equals(bar));

View file

@ -9,7 +9,8 @@ main() {
final router = new Router(); final router = new Router();
final indexRoute = router.get('/', () => ':)'); final indexRoute = router.get('/', () => ':)');
final fizz = router.post('/user/fizz', null); final fizz = router.post('/user/fizz', null);
final deleteUserById = router.delete('/user/:id/detail', (id) => num.parse(id)); final deleteUserById =
router.delete('/user/:id/detail', (id) => num.parse(id));
Route lower; Route lower;
final letters = router.group('/letter///', (router) { final letters = router.group('/letter///', (router) {
@ -29,6 +30,7 @@ main() {
}); });
group('fallback', fallback.main); group('fallback', fallback.main);
test('group & use', use.main);
test('hierarchy', () { test('hierarchy', () {
expect(lower.absoluteParent, equals(router.root)); expect(lower.absoluteParent, equals(router.root));
@ -45,7 +47,4 @@ main() {
expect(router.resolve('letter/a/lower'), equals(lower)); expect(router.resolve('letter/a/lower'), equals(lower));
expect(router.resolve('letter/2/lower'), isNull); expect(router.resolve('letter/2/lower'), isNull);
}); });
test('use', use.main);
} }

View file

@ -1,17 +1,35 @@
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final String ARTIFICIAL_INDEX = 'artificial index';
tattle(x) => 'This ${x.runtimeType}.debug = ${x.debug}';
tattleAll(x) => x.map(tattle).join('\n');
main() { main() {
final parent = new Router()..debug = true; final parent = new Router(debug: true);
final child = new Router()..debug = true; final child = new Router(debug: true);
final a = child.get('a', ['c']); Route a, b, c;
a = child.get('a', ['c']);
child.group('b', (router) {
b = router.get('/', ARTIFICIAL_INDEX);
c = router.post('c', 'Hello nested');
});
parent.use('child', child); parent.use('child', child);
parent.dumpTree(); parent.dumpTree(header: tattleAll([parent, child, a]));
group('no params', () { group('no params', () {
test('resolve', () { test('resolve', () {
expect(child.resolve('a'), equals(a));
expect(child.resolve('b'), equals(b));
expect(child.resolve('b/c'), equals(c));
expect(parent.resolve('child/a'), equals(a)); expect(parent.resolve('child/a'), equals(a));
expect(parent.resolve('a'), isNull); expect(parent.resolve('a'), isNull);
expect(parent.resolve('child/b'), equals(b));
expect(parent.resolve('child/b/c'), equals(c));
}); });
}); });
} }

114
test/server/all_tests.dart Normal file
View file

@ -0,0 +1,114 @@
import 'dart:async';
import 'dart:math' as math;
import 'dart:io';
import 'package:angel_route/angel_route.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';
typedef Future<bool> RequestHandler(HttpRequest request);
final String MIDDLEWARE_GREETING = 'Hi, I am a middleware!';
main() {
http.Client client;
Router router;
HttpServer server;
String url;
setUp(() async {
client = new http.Client();
router = new Router(debug: true);
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0);
url = 'http://${server.address.address}:${server.port}';
server.listen((request) async {
final resolved = router.resolve(request.uri.toString(), (route) {
print(
'$route matches ${request.method} ${request.uri}? ${route.method == request.method || route.method == '*'}');
return route.method == request.method || route.method == '*';
});
if (resolved == null) {
request.response.statusCode = 404;
request.response.write('404 Not Found');
await request.response.close();
} else {
// Easy middleware pipeline
for (final handler in resolved.handlerSequence) {
if (handler is String) {
if (!await router.requestMiddleware[handler](request)) break;
} else if (!await handler(request)) {
break;
}
}
await request.response.close();
}
});
router.get('foo', (HttpRequest request) async {
request.response.write('bar');
return false;
});
Route square;
square = router.post('square/:num([0-9]+)', (HttpRequest request) async {
final params = square.parseParameters(request.uri.toString());
final squared = math.pow(params['num'], 2);
request.response.statusCode = squared;
request.response.write(squared);
return false;
});
router.group('todos', (router) {
router.get('/', (HttpRequest request) async {
print('TODO INDEX???');
request.response.write([]);
return false;
});
}, middleware: [
(HttpRequest request) async {
request.response.write(MIDDLEWARE_GREETING);
return true;
}
]);
router.dumpTree();
});
tearDown(() async {
client.close();
client = null;
router = null;
url = null;
await server.close();
});
group('group', () {
test('todo index', () async {
final response = await client.get('$url/todos');
expect(response.statusCode, equals(200));
expect(response.body, equals('$MIDDLEWARE_GREETING[]'));
});
});
group('top-level route', () {
test('no params', () async {
final response = await client.get('$url/foo');
expect(response.statusCode, equals(200));
expect(response.body, equals('bar'));
});
test('with params', () async {
final response = await client.post('$url/square/16');
expect(response.statusCode, equals(256));
expect(response.body, equals(response.statusCode.toString()));
});
test('throw 404', () async {
final response = await client.get('$url/abc');
expect(response.statusCode, equals(404));
});
});
}