Got rid of 'part', added 'export'. Also fixed attachment
This commit is contained in:
parent
6d9fa4304c
commit
4cab64f14f
24 changed files with 472 additions and 387 deletions
1
TODO.md
1
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
|
||||
|
|
|
@ -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';
|
|
@ -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 {
|
15
lib/src/http/angel_base.dart
Normal file
15
lib/src/http/angel_base.dart
Normal 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.";
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
|
@ -1,4 +1,4 @@
|
|||
part of angel_framework.http;
|
||||
library angel_framework.http.metadata;
|
||||
|
||||
/// Annotation to map middleware onto a handler.
|
||||
class Middleware {
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = [];
|
||||
|
||||
|
|
|
@ -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
26
lib/src/util.dart
Normal 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
12
test/all_tests.dart
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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!\""));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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"));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue