Got rid of 'part', added 'export'. Also fixed attachment

This commit is contained in:
thosakwe 2016-09-15 15:53:01 -04:00
parent 6d9fa4304c
commit 4cab64f14f
24 changed files with 472 additions and 387 deletions

View file

@ -4,5 +4,4 @@
* More docs
* Make tutorials, videos
* Launch!
* Get rid of `part` and `part of`, so that we can make as much isomorphic as possible
* Get a nice launch process, so we can pre-compile things before running. Also support a sort of hot-reload

View file

@ -1,4 +1,5 @@
/// An easily-extensible web server framework in Dart.
library angel_framework;
export 'src/http/http.dart';
export 'src/http/http.dart';
export 'src/defs.dart';

View file

@ -1,4 +1,6 @@
part of angel_framework.http;
library angel_framework.extensible;
import 'dart:mirrors';
/// Supports accessing members of a Map as though they were actual members.
class Extensible {

View file

@ -0,0 +1,15 @@
library angel_framework.http.angel_base;
import 'dart:async';
import 'routable.dart';
/// A function that asynchronously generates a view from the given path and data.
typedef Future<String> ViewGenerator(String path, [Map data]);
class AngelBase extends Routable {
/// A function that renders views.
///
/// Called by [ResponseContext]@`render`.
ViewGenerator viewGenerator = (String view,
[Map data]) async => "No view engine has been configured yet.";
}

View file

@ -1,4 +1,4 @@
part of angel_framework.http;
library angel_framework.http.angel_http_exception;
class _AngelHttpExceptionBase implements Exception {
/// An HTTP status code this exception will throw.
@ -18,7 +18,7 @@ class _AngelHttpExceptionBase implements Exception {
return "$statusCode: $message";
}
Map toMap() {
Map toJson() {
return {
'isError': true,
'statusCode': statusCode,
@ -26,6 +26,8 @@ class _AngelHttpExceptionBase implements Exception {
'errors': errors
};
}
Map toMap() => toJson();
}
/// Basically the same as

View file

@ -1,27 +1,22 @@
part of angel_framework.http;
library angel_framework.http.controller;
import 'dart:async';
import 'dart:mirrors';
import 'angel_base.dart';
import 'angel_http_exception.dart';
import 'metadata.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'routable.dart';
import 'route.dart';
class Controller {
Angel app;
AngelBase app;
List middleware = [];
List<Route> routes = [];
Map<String, Route> _mappings = {};
Map<String, Route> routeMappings = {};
Expose exposeDecl;
Future call(Angel app) async {
this.app = app;
Routable routable = new Routable()
..routes.addAll(routes);
app.use(exposeDecl.path, routable);
TypeMirror typeMirror = reflectType(this.runtimeType);
String name = exposeDecl.as;
if (name == null || name.isEmpty)
name = MirrorSystem.getName(typeMirror.simpleName);
app.controllers[name] = this;
}
Controller() {
// Load global expose decl
ClassMirror classMirror = reflectClass(this.runtimeType);
@ -91,9 +86,24 @@ class Controller {
if (name == null || name.isEmpty)
name = MirrorSystem.getName(key);
_mappings[name] = route;
routeMappings[name] = route;
}
}
});
}
Future call(AngelBase app) async {
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() => new Routable()..routes.addAll(routes);
}

View file

@ -1,4 +1,11 @@
part of angel_framework.http;
library angel_framework.http;
import 'dart:async';
import 'package:merge_map/merge_map.dart';
import '../util.dart';
import 'metadata.dart';
import 'route.dart';
import 'service.dart';
/// Wraps another service in a service that broadcasts events on actions.
class HookedService extends Service {
@ -38,22 +45,22 @@ class HookedService extends Service {
Map restProvider = {'provider': Providers.REST};
// Add global middleware if declared on the instance itself
Middleware before = _getAnnotation(inner, Middleware);
Middleware before = getAnnotation(inner, Middleware);
if (before != null) {
routes.add(new Route("*", "*", before.handlers));
}
Middleware indexMiddleware = _getAnnotation(inner.index, Middleware);
Middleware indexMiddleware = getAnnotation(inner.index, Middleware);
get('/', (req, res) async {
return await this.index(mergeMap([req.query, restProvider]));
}, middleware: (indexMiddleware == null) ? [] : indexMiddleware.handlers);
Middleware createMiddleware = _getAnnotation(inner.create, Middleware);
Middleware createMiddleware = getAnnotation(inner.create, Middleware);
post('/', (req, res) async => await this.create(req.body, restProvider),
middleware:
(createMiddleware == null) ? [] : createMiddleware.handlers);
Middleware readMiddleware = _getAnnotation(inner.read, Middleware);
Middleware readMiddleware = getAnnotation(inner.read, Middleware);
get(
'/:id',
@ -61,7 +68,7 @@ class HookedService extends Service {
.read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: (readMiddleware == null) ? [] : readMiddleware.handlers);
Middleware modifyMiddleware = _getAnnotation(inner.modify, Middleware);
Middleware modifyMiddleware = getAnnotation(inner.modify, Middleware);
patch(
'/:id',
(req, res) async =>
@ -69,7 +76,7 @@ class HookedService extends Service {
middleware:
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers);
Middleware updateMiddleware = _getAnnotation(inner.update, Middleware);
Middleware updateMiddleware = getAnnotation(inner.update, Middleware);
post(
'/:id',
(req, res) async =>
@ -77,7 +84,7 @@ class HookedService extends Service {
middleware:
(updateMiddleware == null) ? [] : updateMiddleware.handlers);
Middleware removeMiddleware = _getAnnotation(inner.remove, Middleware);
Middleware removeMiddleware = getAnnotation(inner.remove, Middleware);
delete(
'/:id',
(req, res) async => await this

View file

@ -1,27 +1,16 @@
/// HTTP logic
/// Various libraries useful for creating highly-extensible servers.
library angel_framework.http;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:mirrors';
import 'package:body_parser/body_parser.dart';
import 'package:json_god/json_god.dart' as god;
import 'package:merge_map/merge_map.dart';
import 'package:mime/mime.dart';
import '../../defs.dart';
part 'controller.dart';
part 'extensible.dart';
part 'errors.dart';
part 'metadata/metadata.dart';
part 'request_context.dart';
part 'response_context.dart';
part 'route.dart';
part 'routable.dart';
part 'server.dart';
part 'service.dart';
part 'service_hooked.dart';
part 'services/memory.dart';
export 'angel_base.dart';
export 'angel_http_exception.dart';
export 'controller.dart';
export 'hooked_service.dart';
export 'metadata.dart';
export 'memory_service.dart';
export 'request_context.dart';
export 'response_context.dart';
export 'routable.dart';
export 'route.dart';
export 'server.dart';
export 'service.dart';

View file

@ -1,4 +1,12 @@
part of angel_framework.http;
library angel_framework.http.memory_service;
import 'dart:async';
import 'dart:mirrors';
import 'package:json_god/json_god.dart' as god;
import 'package:merge_map/merge_map.dart';
import '../defs.dart';
import 'angel_http_exception.dart';
import 'service.dart';
/// An in-memory [Service].
class MemoryService<T> extends Service {

View file

@ -1,4 +1,4 @@
part of angel_framework.http;
library angel_framework.http.metadata;
/// Annotation to map middleware onto a handler.
class Middleware {

View file

@ -1,18 +1,15 @@
part of angel_framework.http;
/// A function that intercepts a request and determines whether handling of it should continue.
typedef Future<bool> RequestMiddleware(RequestContext req, ResponseContext res);
/// A function that receives an incoming [RequestContext] and responds to it.
typedef Future RequestHandler(RequestContext req, ResponseContext res);
/// A function that handles an [HttpRequest].
typedef Future RawRequestHandler(HttpRequest request);
library angel_framework.http.request_context;
import 'dart:async';
import 'dart:io';
import 'package:body_parser/body_parser.dart';
import '../../src/extensible.dart';
import 'angel_base.dart';
import 'route.dart';
/// A convenience wrapper around an incoming HTTP request.
class RequestContext extends Extensible {
/// The [Angel] instance that is responding to this request.
Angel app;
AngelBase app;
/// Any cookies sent with this request.
List<Cookie> get cookies => underlyingRequest.cookies;
@ -66,7 +63,7 @@ class RequestContext extends Extensible {
/// Magically transforms an [HttpRequest] into a RequestContext.
static Future<RequestContext> from(HttpRequest request,
Map parameters, Angel app, Route sourceRoute) async {
Map parameters, AngelBase app, Route sourceRoute) async {
RequestContext context = new RequestContext();
context.app = app;

View file

@ -1,12 +1,19 @@
part of angel_framework.http;
library angel_framework.http.response_context;
/// A function that asynchronously generates a view from the given path and data.
typedef Future<String> ViewGenerator(String path, [Map data]);
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:json_god/json_god.dart' as god;
import 'package:mime/mime.dart';
import '../extensible.dart';
import 'angel_base.dart';
import 'controller.dart';
import 'route.dart';
/// A convenience wrapper around an outgoing HTTP request.
class ResponseContext extends Extensible {
/// The [Angel] instance that is sending a response.
Angel app;
AngelBase app;
/// Can we still write to this response?
bool isOpen = true;
@ -32,8 +39,7 @@ class ResponseContext extends Extensible {
/// Sends a download as a response.
download(File file, {String filename}) {
header("Content-Disposition",
'Content-Disposition: attachment; filename="${filename ?? file.path}"');
header("Content-Disposition", 'attachment; filename="${filename ?? file.path}"');
header(HttpHeaders.CONTENT_TYPE, lookupMimeType(file.path));
header(HttpHeaders.CONTENT_LENGTH, file.lengthSync().toString());
responseData.add(file.readAsBytesSync());
@ -116,7 +122,7 @@ class ResponseContext extends Extensible {
if (controller == null)
throw new Exception("Could not find a controller named '${split[0]}'");
Route matched = controller._mappings[split[1]];
Route matched = controller.routeMappings[split[1]];
if (matched == null)
throw new Exception("Controller '${split[0]}' does not contain any action named '${split[1]}'");
@ -147,7 +153,7 @@ class ResponseContext extends Extensible {
/// Magically transforms an [HttpResponse] object into a ResponseContext.
static Future<ResponseContext> from
(HttpResponse response, Angel app) async
(HttpResponse response, AngelBase app) async
{
ResponseContext context = new ResponseContext(response);
context.app = app;

View file

@ -1,30 +1,29 @@
part of angel_framework.http;
library angel_framework.http.routable;
import 'dart:async';
import 'dart:io';
import 'dart:mirrors';
import '../extensible.dart';
import '../util.dart';
import 'angel_base.dart';
import 'controller.dart';
import 'hooked_service.dart';
import 'metadata.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'route.dart';
import 'service.dart';
typedef Route RouteAssigner(Pattern path, handler, {List middleware});
_matchingAnnotation(List<InstanceMirror> metadata, Type T) {
for (InstanceMirror metaDatum in metadata) {
if (metaDatum.hasReflectee) {
var reflectee = metaDatum.reflectee;
if (reflectee.runtimeType == T) {
return reflectee;
}
}
}
return null;
}
/// A function that intercepts a request and determines whether handling of it should continue.
typedef Future<bool> RequestMiddleware(RequestContext req, ResponseContext res);
_getAnnotation(obj, Type T) {
if (obj is Function || obj is Future) {
MethodMirror methodMirror = (reflect(obj) as ClosureMirror).function;
return _matchingAnnotation(methodMirror.metadata, T);
} else {
ClassMirror classMirror = reflectClass(obj.runtimeType);
return _matchingAnnotation(classMirror.metadata, T);
}
/// A function that receives an incoming [RequestContext] and responds to it.
typedef Future RequestHandler(RequestContext req, ResponseContext res);
return null;
}
/// A function that handles an [HttpRequest].
typedef Future RawRequestHandler(HttpRequest request);
/// A routable server that can handle dynamic requests.
class Routable extends Extensible {
@ -75,7 +74,7 @@ class Routable extends Extensible {
// If we need to hook this service, do it here. It has to be first, or
// else all routes will point to the old service.
if (_routable is Service) {
Hooked hookedDeclaration = _getAnnotation(_routable, Hooked);
Hooked hookedDeclaration = getAnnotation(_routable, Hooked);
Service service = (hookedDeclaration != null || hooked)
? new HookedService(_routable)
: _routable;
@ -84,7 +83,7 @@ class Routable extends Extensible {
_routable = service;
}
if (_routable is Angel) {
if (_routable is AngelBase) {
all(path, (RequestContext req, ResponseContext res) async {
req.app = _routable;
res.app = _routable;
@ -135,7 +134,7 @@ class Routable extends Extensible {
List handlers = [];
// Merge @Middleware declaration, if any
Middleware middlewareDeclaration = _getAnnotation(
Middleware middlewareDeclaration = getAnnotation(
handler, Middleware);
if (middlewareDeclaration != null) {
handlers.addAll(middlewareDeclaration.handlers);

View file

@ -1,4 +1,4 @@
part of angel_framework.http;
library angel_framework.http.route;
/// Represents an endpoint open for connection via the Internet.
class Route {

View file

@ -1,4 +1,17 @@
part of angel_framework.http;
library angel_framework.http.server;
import 'dart:async';
import 'dart:io';
import 'dart:math' show Random;
import 'package:json_god/json_god.dart' as god;
import 'angel_base.dart';
import 'angel_http_exception.dart';
import 'controller.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'routable.dart';
import 'route.dart';
import 'service.dart';
/// A function that binds an [Angel] server to an Internet address and port.
typedef Future<HttpServer> ServerGenerator(InternetAddress address, int port);
@ -7,11 +20,11 @@ typedef Future<HttpServer> ServerGenerator(InternetAddress address, int port);
typedef Future AngelErrorHandler(AngelHttpException err, RequestContext req,
ResponseContext res);
/// A function that configures an [Angel] server in some way.
typedef Future AngelConfigurer(Angel app);
/// A function that configures an [AngelBase] server in some way.
typedef Future AngelConfigurer(AngelBase app);
/// A powerful real-time/REST/MVC server class.
class Angel extends Routable {
class Angel extends AngelBase {
var _beforeProcessed = new StreamController<HttpRequest>();
var _afterProcessed = new StreamController<HttpRequest>();
var _onController = new StreamController<Controller>.broadcast();
@ -44,12 +57,6 @@ class Angel extends Routable {
res.end();
};
/// A function that renders views.
///
/// Called by [ResponseContext]@`render`.
ViewGenerator viewGenerator = (String view,
[Map data]) async => "No view engine has been configured yet.";
/// [RequestMiddleware] to be run before all requests.
List before = [];

View file

@ -1,4 +1,14 @@
part of angel_framework.http;
library angel_framework.http.service;
import 'dart:async';
import 'package:merge_map/merge_map.dart';
import '../defs.dart';
import '../util.dart';
import 'angel_base.dart';
import 'angel_http_exception.dart';
import 'metadata.dart';
import 'routable.dart';
import 'route.dart';
/// Indicates how the service was accessed.
///
@ -25,7 +35,7 @@ class Providers {
/// Heavily inspired by FeathersJS. <3
class Service extends Routable {
/// The [Angel] app powering this service.
Angel app;
AngelBase app;
/// Retrieves all resources.
Future<List> index([Map params]) {
@ -61,22 +71,22 @@ class Service extends Routable {
Map restProvider = {'provider': Providers.REST};
// Add global middleware if declared on the instance itself
Middleware before = _getAnnotation(this, Middleware);
Middleware before = getAnnotation(this, Middleware);
if (before != null) {
routes.add(new Route("*", "*", before.handlers));
}
Middleware indexMiddleware = _getAnnotation(this.index, Middleware);
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
get('/', (req, res) async {
return await this.index(mergeMap([req.query, restProvider]));
}, middleware: (indexMiddleware == null) ? [] : indexMiddleware.handlers);
Middleware createMiddleware = _getAnnotation(this.create, Middleware);
Middleware createMiddleware = getAnnotation(this.create, Middleware);
post('/', (req, res) async => await this.create(req.body, restProvider),
middleware:
(createMiddleware == null) ? [] : createMiddleware.handlers);
Middleware readMiddleware = _getAnnotation(this.read, Middleware);
Middleware readMiddleware = getAnnotation(this.read, Middleware);
get(
'/:id',
@ -84,7 +94,7 @@ class Service extends Routable {
.read(req.params['id'], mergeMap([req.query, restProvider])),
middleware: (readMiddleware == null) ? [] : readMiddleware.handlers);
Middleware modifyMiddleware = _getAnnotation(this.modify, Middleware);
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
patch(
'/:id',
(req, res) async =>
@ -92,7 +102,7 @@ class Service extends Routable {
middleware:
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers);
Middleware updateMiddleware = _getAnnotation(this.update, Middleware);
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
post(
'/:id',
(req, res) async =>
@ -100,7 +110,7 @@ class Service extends Routable {
middleware:
(updateMiddleware == null) ? [] : updateMiddleware.handlers);
Middleware removeMiddleware = _getAnnotation(this.remove, Middleware);
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
delete(
'/:id',
(req, res) async => await this

26
lib/src/util.dart Normal file
View file

@ -0,0 +1,26 @@
import 'dart:async';
import 'dart:mirrors';
matchingAnnotation(List<InstanceMirror> metadata, Type T) {
for (InstanceMirror metaDatum in metadata) {
if (metaDatum.hasReflectee) {
var reflectee = metaDatum.reflectee;
if (reflectee.runtimeType == T) {
return reflectee;
}
}
}
return null;
}
getAnnotation(obj, Type T) {
if (obj is Function || obj is Future) {
MethodMirror methodMirror = (reflect(obj) as ClosureMirror).function;
return matchingAnnotation(methodMirror.metadata, T);
} else {
ClassMirror classMirror = reflectClass(obj.runtimeType);
return matchingAnnotation(classMirror.metadata, T);
}
return null;
}

12
test/all_tests.dart Normal file
View file

@ -0,0 +1,12 @@
import 'package:test/test.dart';
import 'controller.dart' as controller;
import 'hooked.dart' as hooked;
import 'routing.dart' as routing;
import 'services.dart' as services;
main() {
group('controller', controller.main);
group('hooked', hooked.main);
group('routing', routing.main);
group('services', services.main);
}

View file

@ -1,6 +1,8 @@
library angel_framework.test.common;
class Todo {
import 'package:angel_framework/src/defs.dart';
class Todo extends MemoryModel {
String text;
String over;

View file

@ -19,61 +19,62 @@ class TodoController extends Controller {
}
@Expose("/namedRoute/:foo", as: "foo")
Future<String> someRandomRoute(RequestContext req, ResponseContext res) async {
Future<String> someRandomRoute(RequestContext req,
ResponseContext res) async {
return "${req.params['foo']}!";
}
}
main() {
group("controller", () {
Angel app = new Angel();
HttpServer server;
InternetAddress host = InternetAddress.LOOPBACK_IP_V4;
int port = 3000;
http.Client client;
String url = "http://${host.address}:$port";
Angel app = new Angel();
HttpServer server;
InternetAddress host = InternetAddress.LOOPBACK_IP_V4;
int port = 3000;
http.Client client;
String url = "http://${host.address}:$port";
setUp(() async {
app.registerMiddleware("foo", (req, res) async => res.write("Hello, "));
app.registerMiddleware("bar", (req, res) async => res.write("world!"));
app.get("/redirect", (req, ResponseContext res) async =>
res.redirectToAction("TodoController@foo", {"foo": "world"}));
await app.configure(new TodoController());
setUp(() async {
app.registerMiddleware("foo", (req, res) async => res.write("Hello, "));
app.registerMiddleware("bar", (req, res) async => res.write("world!"));
app.get("/redirect", (req, ResponseContext res) async =>
res.redirectToAction("TodoController@foo", {"foo": "world"}));
await app.configure(new TodoController());
print(app.controllers);
print("\nDUMPING ROUTES:");
app.routes.forEach((Route route) {
print("\t${route.method} ${route.path} -> ${route.handlers}");
});
print("\n");
server = await app.startServer(host, port);
client = new http.Client();
print(app.controllers);
print("\nDUMPING ROUTES:");
app.routes.forEach((Route route) {
print("\t${route.method} ${route.path} -> ${route.handlers}");
});
print("\n");
tearDown(() async {
await server.close(force: true);
client.close();
client = null;
});
server = await app.startServer(host, port);
client = new http.Client();
});
test("middleware", () async {
var response = await client.get("$url/todos/0");
print(response.body);
tearDown(() async {
await server.close(force: true);
client.close();
client = null;
});
expect(response.body.indexOf("Hello, "), equals(0));
test("middleware", () async {
var rgx = new RegExp("^Hello, world!");
var response = await client.get("$url/todos/0");
print(response.body);
Map todo = JSON.decode(response.body.substring(7));
expect(todo.keys.length, equals(2));
expect(todo['text'], equals("Hello"));
expect(todo['over'], equals("world"));
});
expect(response.body.indexOf("Hello, "), equals(0));
test("named actions", () async {
var response = await client.get("$url/redirect");
print(response.body);
Map todo = JSON.decode(response.body.replaceAll(rgx, ""));
print("Todo: $todo");
expect(todo.keys.length, equals(3));
expect(todo['text'], equals("Hello"));
expect(todo['over'], equals("world"));
});
expect(response.body, equals("Hello, \"world!\""));
});
test("named actions", () async {
var response = await client.get("$url/redirect");
print(response.body);
expect(response.body, equals("Hello, \"world!\""));
});
}

View file

@ -5,8 +5,7 @@ import 'package:test/test.dart';
import 'common.dart';
main() {
group('Hooked', () {
Map headers = {
Map headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
@ -77,5 +76,4 @@ main() {
List result = god.deserialize(response.body);
expect(result[0]["angel"], equals("framework"));
});
});
}

View file

@ -19,144 +19,140 @@ class QueryService extends Service {
}
main() {
group('routing', () {
Angel angel;
Angel nested;
Angel todos;
String url;
http.Client client;
Angel angel;
Angel nested;
Angel todos;
String url;
http.Client client;
setUp(() async {
angel = new Angel();
nested = new Angel();
todos = new Angel();
setUp(() async {
angel = new Angel();
nested = new Angel();
todos = new Angel();
angel
..registerMiddleware('interceptor', (req, res) async {
res.write('Middleware');
return false;
})
..registerMiddleware('intercept_service',
(RequestContext req, res) async {
print("Intercepting a service!");
return true;
});
todos.get('/action/:action', (req, res) => res.json(req.params));
nested.post('/ted/:route', (req, res) => res.json(req.params));
angel.get('/meta', testMiddlewareMetadata);
angel.get('/intercepted', 'This should not be shown',
middleware: ['interceptor']);
angel.get('/hello', 'world');
angel.get('/name/:first/last/:last', (req, res) => req.params);
angel.post('/lambda', (req, res) => req.body);
angel.use('/nes', nested);
angel.use('/todos/:id', todos);
angel
.get('/greet/:name',
(RequestContext req, res) async => "Hello ${req.params['name']}")
.as('Named routes');
angel.get('/named', (req, ResponseContext res) async {
res.redirectTo('Named routes', {'name': 'tests'});
});
angel.get('/log', (RequestContext req, res) async {
print("Query: ${req.query}");
return "Logged";
});
angel.use('/query', new QueryService());
angel.get('*', 'MJ');
print("DUMPING ROUTES: ");
for (Route route in angel.routes) {
print("${route.method} ${route.path} - ${route.handlers}");
}
client = new http.Client();
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${angel.httpServer.address.host}:${angel.httpServer.port}";
angel..registerMiddleware('interceptor', (req, res) async {
res.write('Middleware');
return false;
})..registerMiddleware('intercept_service',
(RequestContext req, res) async {
print("Intercepting a service!");
return true;
});
tearDown(() async {
await angel.httpServer.close(force: true);
angel = null;
nested = null;
todos = null;
client.close();
client = null;
url = null;
todos.get('/action/:action', (req, res) => res.json(req.params));
nested.post('/ted/:route', (req, res) => res.json(req.params));
angel.get('/meta', testMiddlewareMetadata);
angel.get('/intercepted', 'This should not be shown',
middleware: ['interceptor']);
angel.get('/hello', 'world');
angel.get('/name/:first/last/:last', (req, res) => req.params);
angel.post('/lambda', (req, res) => req.body);
angel.use('/nes', nested);
angel.use('/todos/:id', todos);
angel
.get('/greet/:name',
(RequestContext req, res) async => "Hello ${req.params['name']}")
.as('Named routes');
angel.get('/named', (req, ResponseContext res) async {
res.redirectTo('Named routes', {'name': 'tests'});
});
test('Can match basic url', () async {
var response = await client.get("$url/hello");
expect(response.body, equals('"world"'));
angel.get('/log', (RequestContext req, res) async {
print("Query: ${req.query}");
return "Logged";
});
angel.use('/query', new QueryService());
angel.get('*', 'MJ');
test('Can match url with multiple parameters', () async {
var response = await client.get('$url/name/HELLO/last/WORLD');
var json = god.deserialize(response.body);
expect(json['first'], equals('HELLO'));
expect(json['last'], equals('WORLD'));
});
print("DUMPING ROUTES: ");
for (Route route in angel.routes) {
print("${route.method} ${route.path} - ${route.handlers}");
}
test('Can nest another Angel instance', () async {
var response = await client.post('$url/nes/ted/foo');
var json = god.deserialize(response.body);
expect(json['route'], equals('foo'));
});
client = new http.Client();
await angel.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${angel.httpServer.address.host}:${angel.httpServer.port}";
});
test('Can parse parameters from a nested Angel instance', () async {
var response = await client.get('$url/todos/1337/action/test');
var json = god.deserialize(response.body);
expect(json['id'], equals(1337));
expect(json['action'], equals('test'));
});
tearDown(() async {
await angel.httpServer.close(force: true);
angel = null;
nested = null;
todos = null;
client.close();
client = null;
url = null;
});
test('Can add and use named middleware', () async {
var response = await client.get('$url/intercepted');
expect(response.body, equals('Middleware'));
});
test('Can match basic url', () async {
var response = await client.get("$url/hello");
expect(response.body, equals('"world"'));
});
test('Middleware via metadata', () async {
// Metadata
var response = await client.get('$url/meta');
expect(response.body, equals('Middleware'));
});
test('Can match url with multiple parameters', () async {
var response = await client.get('$url/name/HELLO/last/WORLD');
var json = god.deserialize(response.body);
expect(json['first'], equals('HELLO'));
expect(json['last'], equals('WORLD'));
});
test('Can serialize function result as JSON', () async {
Map headers = {'Content-Type': 'application/json'};
String postData = god.serialize({'it': 'works'});
var response =
await client.post("$url/lambda", headers: headers, body: postData);
expect(god.deserialize(response.body)['it'], equals('works'));
});
test('Can nest another Angel instance', () async {
var response = await client.post('$url/nes/ted/foo');
var json = god.deserialize(response.body);
expect(json['route'], equals('foo'));
});
test('Fallback routes', () async {
var response = await client.get('$url/my_favorite_artist');
expect(response.body, equals('"MJ"'));
});
test('Can parse parameters from a nested Angel instance', () async {
var response = await client.get('$url/todos/1337/action/test');
var json = god.deserialize(response.body);
expect(json['id'], equals(1337));
expect(json['action'], equals('test'));
});
test('Can name routes', () {
Route foo = angel.get('/framework/:id', 'Angel').as('frm');
String uri = foo.makeUri({'id': 'angel'});
print(uri);
expect(uri, equals('/framework/angel'));
});
test('Can add and use named middleware', () async {
var response = await client.get('$url/intercepted');
expect(response.body, equals('Middleware'));
});
test('Redirect to named routes', () async {
var response = await client.get('$url/named');
print(response.body);
expect(god.deserialize(response.body), equals('Hello tests'));
});
test('Middleware via metadata', () async {
// Metadata
var response = await client.get('$url/meta');
expect(response.body, equals('Middleware'));
});
test('Match routes, even with query params', () async {
var response =
await client.get("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo");
print(response.body);
expect(god.deserialize(response.body), equals('Logged'));
test('Can serialize function result as JSON', () async {
Map headers = {'Content-Type': 'application/json'};
String postData = god.serialize({'it': 'works'});
var response =
await client.post("$url/lambda", headers: headers, body: postData);
expect(god.deserialize(response.body)['it'], equals('works'));
});
response = await client.get("$url/query/foo?bar=baz");
print(response.body);
expect(response.body, equals("Middleware"));
});
test('Fallback routes', () async {
var response = await client.get('$url/my_favorite_artist');
expect(response.body, equals('"MJ"'));
});
test('Can name routes', () {
Route foo = angel.get('/framework/:id', 'Angel').as('frm');
String uri = foo.makeUri({'id': 'angel'});
print(uri);
expect(uri, equals('/framework/angel'));
});
test('Redirect to named routes', () async {
var response = await client.get('$url/named');
print(response.body);
expect(god.deserialize(response.body), equals('Hello tests'));
});
test('Match routes, even with query params', () async {
var response =
await client.get("$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo");
print(response.body);
expect(god.deserialize(response.body), equals('Logged'));
response = await client.get("$url/query/foo?bar=baz");
print(response.body);
expect(response.body, equals("Middleware"));
});
}

View file

@ -1,4 +1,4 @@
import 'package:angel_framework/defs.dart';
import 'package:angel_framework/src/defs.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:http/http.dart' as http;
import 'package:json_god/json_god.dart' as god;
@ -10,109 +10,107 @@ class Todo extends MemoryModel {
}
main() {
group('Services', () {
Map headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
Angel app;
String url;
http.Client client;
Map headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
Angel app;
String url;
http.Client client;
setUp(() async {
app = new Angel();
client = new http.Client();
Service todos = new MemoryService<Todo>();
app.use('/todos', todos);
print(app.service("todos"));
await app.startServer(null, 0);
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
setUp(() async {
app = new Angel();
client = new http.Client();
Service todos = new MemoryService<Todo>();
app.use('/todos', todos);
print(app.service("todos"));
await app.startServer(null, 0);
url = "http://${app.httpServer.address.host}:${app.httpServer.port}";
});
tearDown(() async {
app = null;
url = null;
client.close();
client = null;
});
group('memory', () {
test('can index an empty service', () async {
var response = await client.get("$url/todos/");
print(response.body);
expect(response.body, equals('[]'));
for (int i = 0; i < 3; i++) {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
}
response = await client.get("$url/todos");
print(response.body);
expect(god
.deserialize(response.body)
.length, equals(3));
});
tearDown(() async {
app = null;
url = null;
client.close();
client = null;
test('can create data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
var response = await client.post(
"$url/todos", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
});
group('memory', () {
test('can index an empty service', () async {
var response = await client.get("$url/todos/");
print(response.body);
expect(response.body, equals('[]'));
for (int i = 0; i < 3; i++) {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
}
response = await client.get("$url/todos");
print(response.body);
expect(god
.deserialize(response.body)
.length, equals(3));
});
test('can fetch data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
var response = await client.get(
"$url/todos/0");
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
});
test('can create data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
var response = await client.post(
"$url/todos", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
});
test('can modify data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
postData = god.serialize({'text': 'modified'});
var response = await client.patch(
"$url/todos/0", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('modified'));
});
test('can fetch data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
var response = await client.get(
"$url/todos/0");
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
});
test('can overwrite data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
postData = god.serialize({'over': 'write'});
var response = await client.post(
"$url/todos/0", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals(null));
expect(json['over'], equals('write'));
});
test('can modify data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
postData = god.serialize({'text': 'modified'});
var response = await client.patch(
"$url/todos/0", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('modified'));
});
test('can overwrite data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
postData = god.serialize({'over': 'write'});
var response = await client.post(
"$url/todos/0", headers: headers, body: postData);
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals(null));
expect(json['over'], equals('write'));
});
test('can delete data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
var response = await client.delete(
"$url/todos/0");
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
response = await client.get("$url/todos");
print(response.body);
expect(god
.deserialize(response.body)
.length, equals(0));
});
test('can delete data', () async {
String postData = god.serialize({'text': 'Hello, world!'});
await client.post(
"$url/todos", headers: headers, body: postData);
var response = await client.delete(
"$url/todos/0");
var json = god.deserialize(response.body);
print(json);
expect(json['text'], equals('Hello, world!'));
response = await client.get("$url/todos");
print(response.body);
expect(god
.deserialize(response.body)
.length, equals(0));
});
});
}