1.1.0-alpha
This commit is contained in:
parent
6ff96bff49
commit
eabf105bd6
9 changed files with 194 additions and 225 deletions
|
@ -4,7 +4,7 @@ library angel_client;
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:http/src/response.dart' as http;
|
||||
export 'package:angel_framework/src/http/angel_http_exception.dart';
|
||||
export 'package:angel_http_exception/angel_http_exception.dart';
|
||||
|
||||
/// A function that configures an [Angel] client in some way.
|
||||
typedef Future AngelConfigurer(Angel app);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:angel_framework/src/http/angel_http_exception.dart';
|
||||
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:http/src/base_client.dart' as http;
|
||||
import 'package:http/src/base_request.dart' as http;
|
||||
|
@ -72,17 +72,29 @@ abstract class BaseAngelClient extends Angel {
|
|||
String reviveEndpoint: '/auth/token'}) async {
|
||||
if (type == null) {
|
||||
final url = '$basePath$reviveEndpoint';
|
||||
String token;
|
||||
|
||||
if (credentials is String)
|
||||
token = credentials;
|
||||
else if (credentials is Map && credentials.containsKey('token'))
|
||||
token = credentials['token'];
|
||||
|
||||
if (token == null) {
|
||||
throw new ArgumentError(
|
||||
'If `type` is not set, a JWT is expected as the `credentials` argument.');
|
||||
}
|
||||
|
||||
final response = await client.post(url, headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ${credentials['token']}'
|
||||
'Authorization': 'Bearer $token'
|
||||
});
|
||||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
throw failure(response);
|
||||
}
|
||||
if (_invalid(response)) {
|
||||
throw failure(response);
|
||||
}
|
||||
|
||||
try {
|
||||
final json = JSON.decode(response.body);
|
||||
|
||||
if (json is! Map ||
|
||||
|
@ -96,6 +108,8 @@ abstract class BaseAngelClient extends Angel {
|
|||
var r = new AngelAuthResult.fromMap(json);
|
||||
_onAuthenticated.add(r);
|
||||
return r;
|
||||
} on AngelHttpException {
|
||||
rethrow;
|
||||
} catch (e, st) {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
|
@ -110,11 +124,11 @@ abstract class BaseAngelClient extends Angel {
|
|||
response = await client.post(url, headers: _writeHeaders);
|
||||
}
|
||||
|
||||
try {
|
||||
if (_invalid(response)) {
|
||||
throw failure(response);
|
||||
}
|
||||
if (_invalid(response)) {
|
||||
throw failure(response);
|
||||
}
|
||||
|
||||
try {
|
||||
final json = JSON.decode(response.body);
|
||||
|
||||
if (json is! Map ||
|
||||
|
@ -128,6 +142,8 @@ abstract class BaseAngelClient extends Angel {
|
|||
var r = new AngelAuthResult.fromMap(json);
|
||||
_onAuthenticated.add(r);
|
||||
return r;
|
||||
} on AngelHttpException {
|
||||
rethrow;
|
||||
} catch (e, st) {
|
||||
throw failure(response, error: e, stack: st);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
name: angel_client
|
||||
version: 1.0.7
|
||||
version: 1.1.0-alpha
|
||||
description: Client library for the Angel framework.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_client
|
||||
environment:
|
||||
sdk: ">=1.21.0"
|
||||
dependencies:
|
||||
angel_framework: ">=1.0.0-dev <2.0.0"
|
||||
angel_http_exception: ^1.0.0
|
||||
http: ">= 0.11.3 < 0.12.0"
|
||||
json_god: ">=2.0.0-beta <3.0.0"
|
||||
merge_map: ">=1.0.0 <2.0.0"
|
||||
dev_dependencies:
|
||||
angel_framework: ^1.1.0-alpha
|
||||
angel_model: ^1.0.0
|
||||
mock_request: ^1.0.0
|
||||
test: ">= 0.12.13 < 0.13.0"
|
||||
|
|
94
test/all_test.dart
Normal file
94
test/all_test.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'dart:convert';
|
||||
import 'package:angel_client/angel_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
var app = new MockAngel();
|
||||
Service todoService = app.service('api/todos');
|
||||
|
||||
test('sets method,body,headers,path', () async {
|
||||
await app.post('/post', headers: {'method': 'post'}, body: 'post');
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.path, '/post');
|
||||
expect(app.client.spec.headers['method'], 'post');
|
||||
expect(await read(app.client.spec.request.finalize()), 'post');
|
||||
});
|
||||
|
||||
group('service methods', () {
|
||||
test('index', () async {
|
||||
await todoService.index();
|
||||
expect(app.client.spec.method, 'GET');
|
||||
expect(app.client.spec.path, '/api/todos');
|
||||
});
|
||||
|
||||
test('read', () async {
|
||||
await todoService.read('sleep');
|
||||
expect(app.client.spec.method, 'GET');
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
await todoService.create({});
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos/');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('modify', () async {
|
||||
await todoService.modify('sleep', {});
|
||||
expect(app.client.spec.method, 'PATCH');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('update', () async {
|
||||
await todoService.update('sleep', {});
|
||||
expect(app.client.spec.method, 'POST');
|
||||
expect(app.client.spec.headers['content-type'],
|
||||
startsWith('application/json'));
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
expect(await read(app.client.spec.request.finalize()), '{}');
|
||||
});
|
||||
|
||||
test('remove', () async {
|
||||
await todoService.remove('sleep');
|
||||
expect(app.client.spec.method, 'DELETE');
|
||||
expect(app.client.spec.path, '/api/todos/sleep');
|
||||
});
|
||||
});
|
||||
|
||||
group('authentication', () {
|
||||
test('no type, no token throws', () async {
|
||||
expect(app.authenticate, throwsArgumentError);
|
||||
});
|
||||
|
||||
test('no type defaults to token', () async {
|
||||
await app.authenticate(credentials: '<jwt>');
|
||||
expect(app.client.spec.path, '/auth/token');
|
||||
});
|
||||
|
||||
test('sets type', () async {
|
||||
await app.authenticate(type: 'local');
|
||||
expect(app.client.spec.path, '/auth/local');
|
||||
});
|
||||
|
||||
test('token sends headers', () async {
|
||||
await app.authenticate(credentials: '<jwt>');
|
||||
expect(app.client.spec.headers['authorization'], 'Bearer <jwt>');
|
||||
});
|
||||
|
||||
test('credentials send right body', () async {
|
||||
await app
|
||||
.authenticate(type: 'local', credentials: {'username': 'password'});
|
||||
expect(
|
||||
await read(app.client.spec.request.finalize()),
|
||||
JSON.encode({'username': 'password'}),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
@TestOn('browser')
|
||||
import 'package:angel_client/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'for_browser_tests.dart';
|
||||
|
||||
main() {
|
||||
test("list todos", () async {
|
||||
var channel = spawnHybridCode(SERVER);
|
||||
String url = await channel.stream.first;
|
||||
print(url);
|
||||
var app = new Rest(url);
|
||||
var todoService = app.service("todos");
|
||||
|
||||
var todos = await todoService.index();
|
||||
expect(todos, isEmpty);
|
||||
});
|
||||
|
||||
test('create todos', () async {
|
||||
var channel = spawnHybridCode(SERVER);
|
||||
String url = await channel.stream.first;
|
||||
print(url);
|
||||
var app = new Rest(url);
|
||||
var todoService = app.service("todos");
|
||||
|
||||
var data = {'hello': 'world'};
|
||||
var response = await todoService.create(data);
|
||||
print('Created response: $response');
|
||||
|
||||
var todos = await todoService.index();
|
||||
expect(todos, hasLength(1));
|
||||
|
||||
Map todo = todos.first;
|
||||
expect(todo, equals(data));
|
||||
});
|
||||
}
|
67
test/common.dart
Normal file
67
test/common.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:angel_client/base_angel_client.dart';
|
||||
import 'package:http/src/base_client.dart' as http;
|
||||
import 'package:http/src/base_request.dart' as http;
|
||||
import 'package:http/src/streamed_response.dart' as http;
|
||||
|
||||
Future<String> read(Stream<List<int>> stream) =>
|
||||
stream.transform(UTF8.decoder).join();
|
||||
|
||||
class MockAngel extends BaseAngelClient {
|
||||
@override
|
||||
final SpecClient client = new SpecClient();
|
||||
|
||||
MockAngel() : super(null, 'http://localhost:3000');
|
||||
|
||||
@override
|
||||
authenticateViaPopup(String url, {String eventName: 'token'}) {
|
||||
throw new UnsupportedError('Nope');
|
||||
}
|
||||
}
|
||||
|
||||
class SpecClient extends http.BaseClient {
|
||||
Spec _spec;
|
||||
|
||||
Spec get spec => _spec;
|
||||
|
||||
@override
|
||||
send(http.BaseRequest request) {
|
||||
_spec = new Spec(request, request.method, request.url.path, request.headers,
|
||||
request.contentLength);
|
||||
var data = {'text': 'Clean your room!', 'completed': true};
|
||||
|
||||
if (request.url.path.contains('auth'))
|
||||
data = {
|
||||
'token': '<jwt>',
|
||||
'data': {'username': 'password'}
|
||||
};
|
||||
|
||||
return new Future<http.StreamedResponse>.value(new http.StreamedResponse(
|
||||
new Stream<List<int>>.fromIterable([UTF8.encode(JSON.encode(data))]),
|
||||
200,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class Spec {
|
||||
final http.BaseRequest request;
|
||||
final String method, path;
|
||||
final Map<String, String> headers;
|
||||
final int contentLength;
|
||||
|
||||
Spec(this.request, this.method, this.path, this.headers, this.contentLength);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return {
|
||||
'method': method,
|
||||
'path': path,
|
||||
'headers': headers,
|
||||
'content_length': contentLength,
|
||||
}.toString();
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
const String SERVER = '''
|
||||
import 'dart:io';
|
||||
import "package:angel_framework/angel_framework.dart";
|
||||
import "package:angel_framework/common.dart";
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
hybridMain(StreamChannel channel) async {
|
||||
var app = new Angel();
|
||||
|
||||
app.before.add((req, ResponseContext res) {
|
||||
res.headers["Access-Control-Allow-Origin"] = "*";
|
||||
return true;
|
||||
});
|
||||
|
||||
app.use("/todos", new TypedService<Todo>(new MapService()));
|
||||
|
||||
var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||
|
||||
print("Server up; listening at http://localhost:\${server.port}");
|
||||
channel.sink.add('http://\${server.address.address}:\${server.port}');
|
||||
}
|
||||
|
||||
class Todo extends Model {
|
||||
String hello;
|
||||
|
||||
Todo({int id, this.hello}) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
''';
|
|
@ -1,146 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_client/io.dart' as client;
|
||||
import 'package:angel_framework/angel_framework.dart' as server;
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
import 'package:test/test.dart';
|
||||
import 'shared.dart';
|
||||
|
||||
main() {
|
||||
group("rest", () {
|
||||
server.Angel serverApp = new server.Angel();
|
||||
server.HookedService serverPostcards;
|
||||
client.Angel clientApp;
|
||||
client.Service clientPostcards;
|
||||
client.Service clientTypedPostcards;
|
||||
HttpServer httpServer;
|
||||
String url;
|
||||
|
||||
setUp(() async {
|
||||
httpServer =
|
||||
await serverApp.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
|
||||
url = "http://localhost:${httpServer.port}";
|
||||
serverApp.use("/postcards", new server.TypedService<Postcard>(new server.MapService()));
|
||||
serverPostcards = serverApp.service("postcards");
|
||||
|
||||
clientApp = new client.Rest(url);
|
||||
clientPostcards = clientApp.service("postcards");
|
||||
clientTypedPostcards = clientApp.service("postcards", type: Postcard);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await httpServer.close(force: true);
|
||||
});
|
||||
|
||||
test('plain requests', () async {
|
||||
final response = await clientApp.get('/foo');
|
||||
print(response.body);
|
||||
});
|
||||
|
||||
test("index", () async {
|
||||
Map niagara = await clientPostcards.create(
|
||||
new Postcard(location: "Niagara Falls", message: "Missing you!"));
|
||||
Postcard niagaraFalls = new Postcard.fromJson(niagara);
|
||||
|
||||
print('Niagara Falls: ${niagaraFalls.toJson()}');
|
||||
|
||||
List indexed = await clientPostcards.index();
|
||||
print(indexed);
|
||||
|
||||
expect(indexed.length, equals(1));
|
||||
expect(indexed[0].keys.length, equals(3));
|
||||
expect(indexed[0]['id'], equals(niagaraFalls.id));
|
||||
expect(indexed[0]['location'], equals(niagaraFalls.location));
|
||||
expect(indexed[0]['message'], equals(niagaraFalls.message));
|
||||
|
||||
Map l = await clientPostcards.create(new Postcard(
|
||||
location: "The Louvre", message: "The Mona Lisa was watching me!"));
|
||||
Postcard louvre = new Postcard.fromJson(l);
|
||||
print(god.serialize(louvre));
|
||||
List typedIndexed = await clientTypedPostcards.index();
|
||||
expect(typedIndexed.length, equals(2));
|
||||
expect(typedIndexed[1], equals(louvre));
|
||||
});
|
||||
|
||||
test("create/read", () async {
|
||||
Map opry = {"location": "Grand Ole Opry", "message": "Yeehaw!"};
|
||||
var created = await clientPostcards.create(opry);
|
||||
print(created);
|
||||
|
||||
expect(created['id'] == null, equals(false));
|
||||
expect(created["location"], equals(opry["location"]));
|
||||
expect(created["message"], equals(opry["message"]));
|
||||
|
||||
var read = await clientPostcards.read(created['id']);
|
||||
print(read);
|
||||
expect(read['id'], equals(created['id']));
|
||||
expect(read['location'], equals(created['location']));
|
||||
expect(read['message'], equals(created['message']));
|
||||
|
||||
Postcard canyon = new Postcard(
|
||||
location: "Grand Canyon",
|
||||
message: "But did you REALLY experience it???");
|
||||
created = await clientTypedPostcards.create(canyon);
|
||||
print(god.serialize(created));
|
||||
|
||||
expect(created.location, equals(canyon.location));
|
||||
expect(created.message, equals(canyon.message));
|
||||
|
||||
read = await clientTypedPostcards.read(created.id);
|
||||
print(god.serialize(read));
|
||||
expect(read.id, equals(created.id));
|
||||
expect(read.location, equals(created.location));
|
||||
expect(read.message, equals(created.message));
|
||||
});
|
||||
|
||||
test("modify/update", () async {
|
||||
var innerPostcards =
|
||||
serverPostcards.inner as server.TypedService<Postcard>;
|
||||
print(innerPostcards.items);
|
||||
Postcard mecca = await clientTypedPostcards
|
||||
.create(new Postcard(location: "Mecca", message: "Pilgrimage"));
|
||||
print(god.serialize(mecca));
|
||||
|
||||
// I'm too lazy to write the tests twice, because I know it works
|
||||
// So I'll modify using the type-based client, and update using the
|
||||
// map-based one
|
||||
|
||||
print("Postcards on server: " +
|
||||
god.serialize(await serverPostcards.index()));
|
||||
print("Postcards on client: " +
|
||||
god.serialize(await clientPostcards.index()));
|
||||
|
||||
Postcard modified = await clientTypedPostcards
|
||||
.modify(mecca.id, {"location": "Saudi Arabia"});
|
||||
print(god.serialize(modified));
|
||||
expect(modified.id, equals(mecca.id));
|
||||
expect(modified.location, equals("Saudi Arabia"));
|
||||
expect(modified.message, equals(mecca.message));
|
||||
|
||||
Map updated = await clientPostcards
|
||||
.update(mecca.id, {"location": "Full", "message": "Overwrite"});
|
||||
print(updated);
|
||||
|
||||
expect(updated.keys.length, equals(3));
|
||||
expect(updated['id'], equals(mecca.id));
|
||||
expect(updated['location'], equals("Full"));
|
||||
expect(updated['message'], equals("Overwrite"));
|
||||
});
|
||||
|
||||
test("remove", () async {
|
||||
Postcard remove1 = await clientTypedPostcards
|
||||
.create({"location": "remove", "message": "#1"});
|
||||
Postcard remove2 = await clientTypedPostcards
|
||||
.create({"location": "remove", "message": "#2"});
|
||||
print(god.serialize([remove1, remove2]));
|
||||
|
||||
Map removed1 = await clientPostcards.remove(remove1.id);
|
||||
expect(removed1.keys.length, equals(3));
|
||||
expect(removed1['id'], equals(remove1.id));
|
||||
expect(removed1['location'], equals(remove1.location));
|
||||
expect(removed1['message'], equals(remove1.message));
|
||||
|
||||
Postcard removed2 = await clientTypedPostcards.remove(remove2.id);
|
||||
expect(removed2, equals(remove2));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import "package:angel_framework/common.dart";
|
||||
import 'package:angel_model/angel_model.dart';
|
||||
|
||||
class Postcard extends Model {
|
||||
String location;
|
||||
|
|
Loading…
Reference in a new issue