Add 'packages/rethink/' from commit '6cc85e04095bf1665bf18d1f626930f0e61c0cb2'
git-subtree-dir: packages/rethink git-subtree-mainline:ed10592b5d
git-subtree-split:6cc85e0409
This commit is contained in:
commit
61c716502b
12 changed files with 512 additions and 0 deletions
31
packages/rethink/.gitignore
vendored
Normal file
31
packages/rethink/.gitignore
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# See https://www.dartlang.org/tools/private-files.html
|
||||||
|
|
||||||
|
# Files and directories created by pub
|
||||||
|
.buildlog
|
||||||
|
.packages
|
||||||
|
.project
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
**/packages/
|
||||||
|
|
||||||
|
# Files created by dart2js
|
||||||
|
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||||
|
# rules if you intend to use dart2js directly
|
||||||
|
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||||
|
# differentiate from explicit Javascript files)
|
||||||
|
*.dart.js
|
||||||
|
*.part.js
|
||||||
|
*.js.deps
|
||||||
|
*.js.map
|
||||||
|
*.info.json
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
doc/api/
|
||||||
|
|
||||||
|
# Don't commit pubspec lock file
|
||||||
|
# (Library packages only! Remove pattern if developing an application package)
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
rethinkdb_data/
|
||||||
|
.idea
|
||||||
|
.dart_tool
|
4
packages/rethink/.travis.yml
Normal file
4
packages/rethink/.travis.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
language: dart
|
||||||
|
addons:
|
||||||
|
rethinkdb: '2.3'
|
||||||
|
before_script: 'dart test/bootstrap.dart'
|
3
packages/rethink/CHANGELOG.md
Normal file
3
packages/rethink/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 1.1.0
|
||||||
|
* Moved to `package:rethinkdb_driver`
|
||||||
|
* Fixed references to old hooked event names.
|
21
packages/rethink/LICENSE
Normal file
21
packages/rethink/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 The Angel Framework
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
87
packages/rethink/README.md
Normal file
87
packages/rethink/README.md
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# rethink
|
||||||
|
[![version 1.0.7](https://img.shields.io/badge/pub-1.0.7-brightgreen.svg)](https://pub.dartlang.org/packages/angel_rethink)
|
||||||
|
[![build status](https://travis-ci.org/angel-dart/rethink.svg?branch=master)](https://travis-ci.org/angel-dart/rethink)
|
||||||
|
|
||||||
|
RethinkDB-enabled services for the Angel framework.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
Add the following to your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
angel_rethink: ^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`package:rethinkdb_driver2` will be installed as well.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
This library exposes one class: `RethinkService`. By default, these services will even
|
||||||
|
listen to [changefeeds](https://www.rethinkdb.com/docs/changefeeds/ruby/) from the database,
|
||||||
|
which makes them very suitable for WebSocket use.
|
||||||
|
|
||||||
|
However, only `CREATED`, `UPDATED` and `REMOVED` events will be fired. This is technically not
|
||||||
|
a problem, as it lowers the numbers of events you have to handle on the client side. ;)
|
||||||
|
|
||||||
|
## Model
|
||||||
|
`Model` is class with no real functionality; however, it represents a basic document, and your services should host inherited classes.
|
||||||
|
Other Angel service providers host `Model` as well, so you will easily be able to modify your application if you ever switch databases.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class User extends Model {
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
}
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
var r = new RethinkDb();
|
||||||
|
var conn = await r.connect();
|
||||||
|
|
||||||
|
app.use('/api/users', new RethinkService(conn, r.table('users')));
|
||||||
|
|
||||||
|
// Add type de/serialization if you want
|
||||||
|
app.use('/api/users', new TypedService<User>(new RethinkService(conn, r.table('users'))));
|
||||||
|
|
||||||
|
// You don't have to even use a table...
|
||||||
|
app.use('/api/pro_users', new RethinkService(conn, r.table('users').filter({'membership': 'pro'})));
|
||||||
|
|
||||||
|
app.service('api/users').afterCreated.listen((event) {
|
||||||
|
print("New user: ${event.result}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## RethinkService
|
||||||
|
This class interacts with a `Query` (usually a table) and serializes data to and from Maps.
|
||||||
|
|
||||||
|
## RethinkTypedService<T>
|
||||||
|
Does the same as above, but serializes to and from a target class using `package:json_god` and its support for reflection.
|
||||||
|
|
||||||
|
## Querying
|
||||||
|
You can query these services as follows:
|
||||||
|
|
||||||
|
/path/to/service?foo=bar
|
||||||
|
|
||||||
|
The above will query the database to find records where 'foo' equals 'bar'.
|
||||||
|
|
||||||
|
The former will sort result in ascending order of creation, and so will the latter.
|
||||||
|
|
||||||
|
You can use advanced queries:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Pass an actual query...
|
||||||
|
service.index({'query': r.table('foo').filter(...)});
|
||||||
|
|
||||||
|
// Or, a function that creates a query from a table...
|
||||||
|
service.index({'query': (table) => table.getAll('foo')});
|
||||||
|
|
||||||
|
// Or, a Map, which will be transformed into a `filter` query:
|
||||||
|
service.index({'query': {'foo': 'bar', 'baz': 'quux'}});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also apply sorting by adding a `reql` parameter on the server-side.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
service.index({'reql': (query) => query.sort(...)});
|
||||||
|
```
|
||||||
|
|
||||||
|
See the tests for more usage examples.
|
1
packages/rethink/lib/angel_rethink.dart
Normal file
1
packages/rethink/lib/angel_rethink.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'src/rethink_service.dart';
|
237
packages/rethink/lib/src/rethink_service.dart
Normal file
237
packages/rethink/lib/src/rethink_service.dart
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
import 'dart:async';
|
||||||
|
//import 'dart:io';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:json_god/json_god.dart' as god;
|
||||||
|
import 'package:rethinkdb_driver/rethinkdb_driver.dart';
|
||||||
|
|
||||||
|
// Extends a RethinkDB query.
|
||||||
|
typedef RqlQuery QueryCallback(RqlQuery query);
|
||||||
|
|
||||||
|
/// Queries a single RethinkDB table or query.
|
||||||
|
class RethinkService extends Service {
|
||||||
|
/// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
|
||||||
|
///
|
||||||
|
/// `false` by default.
|
||||||
|
final bool allowRemoveAll;
|
||||||
|
|
||||||
|
/// If set to `true`, parameters in `req.query` are applied to the database query.
|
||||||
|
final bool allowQuery;
|
||||||
|
|
||||||
|
final bool debug;
|
||||||
|
|
||||||
|
/// If set to `true`, then a HookedService mounted over this instance
|
||||||
|
/// will fire events when RethinkDB pushes events.
|
||||||
|
///
|
||||||
|
/// Good for scaling. ;)
|
||||||
|
final bool listenForChanges;
|
||||||
|
|
||||||
|
final Connection connection;
|
||||||
|
|
||||||
|
/// Doesn't actually have to be a table, just a RethinkDB query.
|
||||||
|
///
|
||||||
|
/// However, a table is the most common usecase.
|
||||||
|
final RqlQuery table;
|
||||||
|
|
||||||
|
RethinkService(this.connection, this.table,
|
||||||
|
{this.allowRemoveAll: false,
|
||||||
|
this.allowQuery: true,
|
||||||
|
this.debug: false,
|
||||||
|
this.listenForChanges: true})
|
||||||
|
: super() {}
|
||||||
|
|
||||||
|
RqlQuery buildQuery(RqlQuery initialQuery, Map params) {
|
||||||
|
if (params != null)
|
||||||
|
params['broadcast'] = params.containsKey('broadcast')
|
||||||
|
? params['broadcast']
|
||||||
|
: (listenForChanges != true);
|
||||||
|
|
||||||
|
var q = _getQueryInner(initialQuery, params);
|
||||||
|
|
||||||
|
if (params?.containsKey('reql') == true && params['reql'] is QueryCallback)
|
||||||
|
q = params['reql'](q);
|
||||||
|
|
||||||
|
return q ?? initialQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
RqlQuery _getQueryInner(RqlQuery query, Map params) {
|
||||||
|
if (params == null || !params.containsKey('query'))
|
||||||
|
return query;
|
||||||
|
else {
|
||||||
|
if (params['query'] is RqlQuery)
|
||||||
|
return params['query'];
|
||||||
|
else if (params['query'] is QueryCallback)
|
||||||
|
return params['query'](table);
|
||||||
|
else if (params['query'] is! Map || allowQuery != true)
|
||||||
|
return query;
|
||||||
|
else {
|
||||||
|
Map q = params['query'];
|
||||||
|
return q.keys.fold<RqlQuery>(query, (out, key) {
|
||||||
|
var val = q[key];
|
||||||
|
|
||||||
|
if (val is RequestContext ||
|
||||||
|
val is ResponseContext ||
|
||||||
|
key == 'provider' ||
|
||||||
|
val is Providers)
|
||||||
|
return out;
|
||||||
|
else {
|
||||||
|
return out.filter({key.toString(): val});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _sendQuery(RqlQuery query) async {
|
||||||
|
var result = await query.run(connection);
|
||||||
|
|
||||||
|
if (result is Cursor)
|
||||||
|
return await result.toList();
|
||||||
|
else if (result is Map && result['generated_keys'] is List) {
|
||||||
|
if (result['generated_keys'].length == 1)
|
||||||
|
return await read(result['generated_keys'].first);
|
||||||
|
return await Future.wait(result['generated_keys'].map(read));
|
||||||
|
} else
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
_serialize(data) {
|
||||||
|
if (data is Map)
|
||||||
|
return data;
|
||||||
|
else if (data is Iterable)
|
||||||
|
return data.map(_serialize).toList();
|
||||||
|
else
|
||||||
|
return god.serializeObject(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_squeeze(data) {
|
||||||
|
if (data is Map)
|
||||||
|
return data.keys.fold<Map>({}, (map, k) => map..[k.toString()] = data[k]);
|
||||||
|
else if (data is Iterable)
|
||||||
|
return data.map(_squeeze).toList();
|
||||||
|
else
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onHooked(HookedService hookedService) {
|
||||||
|
if (listenForChanges == true) {
|
||||||
|
listenToQuery(table, hookedService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future listenToQuery(RqlQuery query, HookedService hookedService) async {
|
||||||
|
Feed feed = await query.changes({'include_types': true}).run(connection);
|
||||||
|
|
||||||
|
feed.listen((Map event) {
|
||||||
|
String type = event['type']?.toString();
|
||||||
|
var newVal = event['new_val'], oldVal = event['old_val'];
|
||||||
|
|
||||||
|
if (type == 'add') {
|
||||||
|
// Create
|
||||||
|
hookedService.fireEvent(
|
||||||
|
hookedService.afterCreated,
|
||||||
|
new HookedServiceEvent(
|
||||||
|
true, null, null, this, HookedServiceEvent.created,
|
||||||
|
result: newVal));
|
||||||
|
} else if (type == 'change') {
|
||||||
|
// Update
|
||||||
|
hookedService.fireEvent(
|
||||||
|
hookedService.afterCreated,
|
||||||
|
new HookedServiceEvent(
|
||||||
|
true, null, null, this, HookedServiceEvent.updated,
|
||||||
|
result: newVal, id: oldVal['id'], data: newVal));
|
||||||
|
} else if (type == 'remove') {
|
||||||
|
// Remove
|
||||||
|
hookedService.fireEvent(
|
||||||
|
hookedService.afterCreated,
|
||||||
|
new HookedServiceEvent(
|
||||||
|
true, null, null, this, HookedServiceEvent.removed,
|
||||||
|
result: oldVal, id: oldVal['id']));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future index([Map params]) async {
|
||||||
|
var query = buildQuery(table, params);
|
||||||
|
return await _sendQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future read(id, [Map params]) async {
|
||||||
|
var query = buildQuery(table.get(id?.toString()), params);
|
||||||
|
var found = await _sendQuery(query);
|
||||||
|
//print('Found for $id: $found');
|
||||||
|
|
||||||
|
if (found == null) {
|
||||||
|
throw new AngelHttpException.notFound(
|
||||||
|
message: 'No record found for ID $id');
|
||||||
|
} else
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future create(data, [Map params]) async {
|
||||||
|
if (table is! Table) throw new AngelHttpException.methodNotAllowed();
|
||||||
|
|
||||||
|
var d = _serialize(data);
|
||||||
|
var q = table as Table;
|
||||||
|
var query = buildQuery(q.insert(_squeeze(d)), params);
|
||||||
|
return await _sendQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future modify(id, data, [Map params]) async {
|
||||||
|
var d = _serialize(data);
|
||||||
|
|
||||||
|
if (d is Map && d.containsKey('id')) {
|
||||||
|
try {
|
||||||
|
await read(d['id'], params);
|
||||||
|
} on AngelHttpException catch (e) {
|
||||||
|
if (e.statusCode == 404)
|
||||||
|
return await create(data, params);
|
||||||
|
else
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = buildQuery(table.get(id?.toString()), params).update(d);
|
||||||
|
await _sendQuery(query);
|
||||||
|
return await read(id, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future update(id, data, [Map params]) async {
|
||||||
|
var d = _serialize(data);
|
||||||
|
|
||||||
|
if (d is Map && d.containsKey('id')) {
|
||||||
|
try {
|
||||||
|
await read(d['id'], params);
|
||||||
|
} on AngelHttpException catch (e) {
|
||||||
|
if (e.statusCode == 404)
|
||||||
|
return await create(data, params);
|
||||||
|
else
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d is Map && !d.containsKey('id')) d['id'] = id.toString();
|
||||||
|
var query = buildQuery(table.get(id?.toString()), params).replace(d);
|
||||||
|
await _sendQuery(query);
|
||||||
|
return await read(id, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future remove(id, [Map params]) async {
|
||||||
|
if (id == null ||
|
||||||
|
id == 'null' &&
|
||||||
|
(allowRemoveAll == true ||
|
||||||
|
params?.containsKey('provider') != true)) {
|
||||||
|
return await _sendQuery(table.delete());
|
||||||
|
} else {
|
||||||
|
var prior = await read(id, params);
|
||||||
|
var query = buildQuery(table.get(id), params).delete();
|
||||||
|
await _sendQuery(query);
|
||||||
|
return prior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
packages/rethink/pubspec.yaml
Normal file
16
packages/rethink/pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
name: angel_rethink
|
||||||
|
version: 1.1.0
|
||||||
|
description: RethinkDB-enabled services for the Angel framework.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
environment:
|
||||||
|
sdk: ">=1.19.0 <3.0.0"
|
||||||
|
homepage: https://github.com/angel-dart/rethink
|
||||||
|
dependencies:
|
||||||
|
angel_framework: ^1.1.0
|
||||||
|
json_god: ^2.0.0-beta
|
||||||
|
rethinkdb_driver: ^2.3.1
|
||||||
|
dev_dependencies:
|
||||||
|
angel_client: ^1.1.0
|
||||||
|
angel_test: ^1.1.0
|
||||||
|
logging: ^0.11.3
|
||||||
|
test: ^0.12.0
|
6
packages/rethink/test/README.md
Normal file
6
packages/rethink/test/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
The tests expect you to have installed RethinkDB. You must have a `test` database
|
||||||
|
available, and a server ready at the default port.
|
||||||
|
|
||||||
|
Also, the tests expect a table named `todos`.
|
11
packages/rethink/test/bootstrap.dart
Normal file
11
packages/rethink/test/bootstrap.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:rethinkdb_driver/rethinkdb_driver.dart';
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
var r = new Rethinkdb();
|
||||||
|
r.connect().then((conn) {
|
||||||
|
r.tableCreate('todos').run(conn);
|
||||||
|
print('Done');
|
||||||
|
exit(0);
|
||||||
|
});
|
||||||
|
}
|
10
packages/rethink/test/common.dart
Normal file
10
packages/rethink/test/common.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
class Todo {
|
||||||
|
String title;
|
||||||
|
bool completed;
|
||||||
|
|
||||||
|
Todo({this.title, this.completed: false});
|
||||||
|
|
||||||
|
Map toJson() {
|
||||||
|
return {'title': title, 'completed': completed == true};
|
||||||
|
}
|
||||||
|
}
|
85
packages/rethink/test/generic_test.dart
Normal file
85
packages/rethink/test/generic_test.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:angel_client/angel_client.dart' as c;
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_rethink/angel_rethink.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:rethinkdb_driver/rethinkdb_driver.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
Rethinkdb r;
|
||||||
|
c.Service todoService;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
r = new Rethinkdb();
|
||||||
|
var conn = await r.connect();
|
||||||
|
|
||||||
|
app = new Angel();
|
||||||
|
app.use('/todos', new RethinkService(conn, r.table('todos')));
|
||||||
|
|
||||||
|
app.errorHandler = (e, req, res) async {
|
||||||
|
print('Whoops: $e');
|
||||||
|
};
|
||||||
|
|
||||||
|
app.logger = new Logger.detached('angel')..onRecord.listen(print);
|
||||||
|
|
||||||
|
client = await connectTo(app);
|
||||||
|
todoService = client.service('todos');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.close());
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var result = await todoService.index();
|
||||||
|
print('Response: $result');
|
||||||
|
expect(result, isList);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create+read', () async {
|
||||||
|
var todo = new Todo(title: 'Clean your room');
|
||||||
|
var creation = await todoService.create(todo.toJson());
|
||||||
|
print('Creation: $creation');
|
||||||
|
|
||||||
|
var id = creation['id'];
|
||||||
|
var result = await todoService.read(id);
|
||||||
|
|
||||||
|
print('Response: $result');
|
||||||
|
expect(result, isMap);
|
||||||
|
expect(result['id'], equals(id));
|
||||||
|
expect(result['title'], equals(todo.title));
|
||||||
|
expect(result['completed'], equals(todo.completed));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('modify', () async {
|
||||||
|
var todo = new Todo(title: 'Clean your room');
|
||||||
|
var creation = await todoService.create(todo.toJson());
|
||||||
|
print('Creation: $creation');
|
||||||
|
|
||||||
|
var id = creation['id'];
|
||||||
|
var result = await todoService.modify(id, {'title': 'Eat healthy'});
|
||||||
|
|
||||||
|
print('Response: $result');
|
||||||
|
expect(result, isMap);
|
||||||
|
expect(result['id'], equals(id));
|
||||||
|
expect(result['title'], equals('Eat healthy'));
|
||||||
|
expect(result['completed'], equals(todo.completed));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('remove', () async {
|
||||||
|
var todo = new Todo(title: 'Clean your room');
|
||||||
|
var creation = await todoService.create(todo.toJson());
|
||||||
|
print('Creation: $creation');
|
||||||
|
|
||||||
|
var id = creation['id'];
|
||||||
|
var result = await todoService.remove(id);
|
||||||
|
|
||||||
|
print('Response: $result');
|
||||||
|
expect(result, isMap);
|
||||||
|
expect(result['id'], equals(id));
|
||||||
|
expect(result['title'], equals(todo.title));
|
||||||
|
expect(result['completed'], equals(todo.completed));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue