From 4cab64f14f2ced3177cb06c337488893eefd189f Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 15 Sep 2016 15:53:01 -0400 Subject: [PATCH] Got rid of 'part', added 'export'. Also fixed attachment --- TODO.md | 1 - lib/angel_framework.dart | 3 +- lib/{ => src}/defs.dart | 0 lib/src/{http => }/extensible.dart | 4 +- lib/src/http/angel_base.dart | 15 ++ ...{errors.dart => angel_http_exception.dart} | 6 +- lib/src/http/controller.dart | 48 ++-- ...ervice_hooked.dart => hooked_service.dart} | 23 +- lib/src/http/http.dart | 37 +-- .../memory.dart => memory_service.dart} | 10 +- lib/src/http/{metadata => }/metadata.dart | 2 +- lib/src/http/request_context.dart | 21 +- lib/src/http/response_context.dart | 22 +- lib/src/http/routable.dart | 49 ++-- lib/src/http/route.dart | 2 +- lib/src/http/server.dart | 27 +- lib/src/http/service.dart | 28 +- lib/src/util.dart | 26 ++ test/all_tests.dart | 12 + test/common.dart | 4 +- test/controller.dart | 85 ++++--- test/hooked.dart | 4 +- test/routing.dart | 240 +++++++++--------- test/services.dart | 190 +++++++------- 24 files changed, 472 insertions(+), 387 deletions(-) rename lib/{ => src}/defs.dart (100%) rename lib/src/{http => }/extensible.dart (92%) create mode 100644 lib/src/http/angel_base.dart rename lib/src/http/{errors.dart => angel_http_exception.dart} (96%) rename lib/src/http/{service_hooked.dart => hooked_service.dart} (92%) rename lib/src/http/{services/memory.dart => memory_service.dart} (90%) rename lib/src/http/{metadata => }/metadata.dart (93%) create mode 100644 lib/src/util.dart create mode 100644 test/all_tests.dart diff --git a/TODO.md b/TODO.md index 79881fd8..3381eeda 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/lib/angel_framework.dart b/lib/angel_framework.dart index 7a624d51..636c6ca2 100644 --- a/lib/angel_framework.dart +++ b/lib/angel_framework.dart @@ -1,4 +1,5 @@ /// An easily-extensible web server framework in Dart. library angel_framework; -export 'src/http/http.dart'; \ No newline at end of file +export 'src/http/http.dart'; +export 'src/defs.dart'; \ No newline at end of file diff --git a/lib/defs.dart b/lib/src/defs.dart similarity index 100% rename from lib/defs.dart rename to lib/src/defs.dart diff --git a/lib/src/http/extensible.dart b/lib/src/extensible.dart similarity index 92% rename from lib/src/http/extensible.dart rename to lib/src/extensible.dart index 5ed88f44..b14b8255 100644 --- a/lib/src/http/extensible.dart +++ b/lib/src/extensible.dart @@ -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 { diff --git a/lib/src/http/angel_base.dart b/lib/src/http/angel_base.dart new file mode 100644 index 00000000..d95e5cf4 --- /dev/null +++ b/lib/src/http/angel_base.dart @@ -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 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."; +} \ No newline at end of file diff --git a/lib/src/http/errors.dart b/lib/src/http/angel_http_exception.dart similarity index 96% rename from lib/src/http/errors.dart rename to lib/src/http/angel_http_exception.dart index 84fd482f..3e91b312 100644 --- a/lib/src/http/errors.dart +++ b/lib/src/http/angel_http_exception.dart @@ -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 diff --git a/lib/src/http/controller.dart b/lib/src/http/controller.dart index 369b9b7b..99d6c0d7 100644 --- a/lib/src/http/controller.dart +++ b/lib/src/http/controller.dart @@ -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 routes = []; - Map _mappings = {}; + Map 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); } \ No newline at end of file diff --git a/lib/src/http/service_hooked.dart b/lib/src/http/hooked_service.dart similarity index 92% rename from lib/src/http/service_hooked.dart rename to lib/src/http/hooked_service.dart index cad0fc75..3fe18df4 100644 --- a/lib/src/http/service_hooked.dart +++ b/lib/src/http/hooked_service.dart @@ -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 diff --git a/lib/src/http/http.dart b/lib/src/http/http.dart index da16ecd9..80028e27 100644 --- a/lib/src/http/http.dart +++ b/lib/src/http/http.dart @@ -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'; diff --git a/lib/src/http/services/memory.dart b/lib/src/http/memory_service.dart similarity index 90% rename from lib/src/http/services/memory.dart rename to lib/src/http/memory_service.dart index 7619f8c5..7e68599e 100644 --- a/lib/src/http/services/memory.dart +++ b/lib/src/http/memory_service.dart @@ -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 extends Service { diff --git a/lib/src/http/metadata/metadata.dart b/lib/src/http/metadata.dart similarity index 93% rename from lib/src/http/metadata/metadata.dart rename to lib/src/http/metadata.dart index af164495..1e96c449 100644 --- a/lib/src/http/metadata/metadata.dart +++ b/lib/src/http/metadata.dart @@ -1,4 +1,4 @@ -part of angel_framework.http; +library angel_framework.http.metadata; /// Annotation to map middleware onto a handler. class Middleware { diff --git a/lib/src/http/request_context.dart b/lib/src/http/request_context.dart index da2beca5..3ae4d88a 100644 --- a/lib/src/http/request_context.dart +++ b/lib/src/http/request_context.dart @@ -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 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 get cookies => underlyingRequest.cookies; @@ -66,7 +63,7 @@ class RequestContext extends Extensible { /// Magically transforms an [HttpRequest] into a RequestContext. static Future from(HttpRequest request, - Map parameters, Angel app, Route sourceRoute) async { + Map parameters, AngelBase app, Route sourceRoute) async { RequestContext context = new RequestContext(); context.app = app; diff --git a/lib/src/http/response_context.dart b/lib/src/http/response_context.dart index cfef32a9..5067b88c 100644 --- a/lib/src/http/response_context.dart +++ b/lib/src/http/response_context.dart @@ -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 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 from - (HttpResponse response, Angel app) async + (HttpResponse response, AngelBase app) async { ResponseContext context = new ResponseContext(response); context.app = app; diff --git a/lib/src/http/routable.dart b/lib/src/http/routable.dart index 45a6637e..4d65ce72 100644 --- a/lib/src/http/routable.dart +++ b/lib/src/http/routable.dart @@ -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 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 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); diff --git a/lib/src/http/route.dart b/lib/src/http/route.dart index 1abcb771..ab3588de 100644 --- a/lib/src/http/route.dart +++ b/lib/src/http/route.dart @@ -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 { diff --git a/lib/src/http/server.dart b/lib/src/http/server.dart index 7bc9bce0..001dee81 100644 --- a/lib/src/http/server.dart +++ b/lib/src/http/server.dart @@ -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 ServerGenerator(InternetAddress address, int port); @@ -7,11 +20,11 @@ typedef Future 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(); var _afterProcessed = new StreamController(); var _onController = new StreamController.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 = []; diff --git a/lib/src/http/service.dart b/lib/src/http/service.dart index cdee9f21..c12e8a35 100644 --- a/lib/src/http/service.dart +++ b/lib/src/http/service.dart @@ -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 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 diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 00000000..ad614889 --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'dart:mirrors'; + +matchingAnnotation(List 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; +} \ No newline at end of file diff --git a/test/all_tests.dart b/test/all_tests.dart new file mode 100644 index 00000000..1df0368c --- /dev/null +++ b/test/all_tests.dart @@ -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); +} \ No newline at end of file diff --git a/test/common.dart b/test/common.dart index 54a28992..cb9ffe1e 100644 --- a/test/common.dart +++ b/test/common.dart @@ -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; diff --git a/test/controller.dart b/test/controller.dart index 41faed23..c85a9037 100644 --- a/test/controller.dart +++ b/test/controller.dart @@ -19,61 +19,62 @@ class TodoController extends Controller { } @Expose("/namedRoute/:foo", as: "foo") - Future someRandomRoute(RequestContext req, ResponseContext res) async { + Future 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!\"")); }); } diff --git a/test/hooked.dart b/test/hooked.dart index c4d5616f..b72d7930 100644 --- a/test/hooked.dart +++ b/test/hooked.dart @@ -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")); }); - }); } \ No newline at end of file diff --git a/test/routing.dart b/test/routing.dart index cdcd3caf..d72b1a90 100644 --- a/test/routing.dart +++ b/test/routing.dart @@ -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")); }); } diff --git a/test/services.dart b/test/services.dart index 160ab668..70cf063e 100644 --- a/test/services.dart +++ b/test/services.dart @@ -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(); - 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(); + 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)); }); }); } \ No newline at end of file