1.1.0-alpha

This commit is contained in:
Tobe O 2017-09-24 00:12:53 -04:00
parent 6ff96bff49
commit eabf105bd6
9 changed files with 194 additions and 225 deletions

View file

@ -4,7 +4,7 @@ library angel_client;
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:http/src/response.dart' as http; 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. /// A function that configures an [Angel] client in some way.
typedef Future AngelConfigurer(Angel app); typedef Future AngelConfigurer(Angel app);

View file

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; 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:collection/collection.dart';
import 'package:http/src/base_client.dart' as http; import 'package:http/src/base_client.dart' as http;
import 'package:http/src/base_request.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 { String reviveEndpoint: '/auth/token'}) async {
if (type == null) { if (type == null) {
final url = '$basePath$reviveEndpoint'; 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: { final response = await client.post(url, headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer ${credentials['token']}' 'Authorization': 'Bearer $token'
}); });
try {
if (_invalid(response)) { if (_invalid(response)) {
throw failure(response); throw failure(response);
} }
try {
final json = JSON.decode(response.body); final json = JSON.decode(response.body);
if (json is! Map || if (json is! Map ||
@ -96,6 +108,8 @@ abstract class BaseAngelClient extends Angel {
var r = new AngelAuthResult.fromMap(json); var r = new AngelAuthResult.fromMap(json);
_onAuthenticated.add(r); _onAuthenticated.add(r);
return r; return r;
} on AngelHttpException {
rethrow;
} catch (e, st) { } catch (e, st) {
throw failure(response, error: e, stack: st); throw failure(response, error: e, stack: st);
} }
@ -110,11 +124,11 @@ abstract class BaseAngelClient extends Angel {
response = await client.post(url, headers: _writeHeaders); response = await client.post(url, headers: _writeHeaders);
} }
try {
if (_invalid(response)) { if (_invalid(response)) {
throw failure(response); throw failure(response);
} }
try {
final json = JSON.decode(response.body); final json = JSON.decode(response.body);
if (json is! Map || if (json is! Map ||
@ -128,6 +142,8 @@ abstract class BaseAngelClient extends Angel {
var r = new AngelAuthResult.fromMap(json); var r = new AngelAuthResult.fromMap(json);
_onAuthenticated.add(r); _onAuthenticated.add(r);
return r; return r;
} on AngelHttpException {
rethrow;
} catch (e, st) { } catch (e, st) {
throw failure(response, error: e, stack: st); throw failure(response, error: e, stack: st);
} }

View file

@ -1,14 +1,17 @@
name: angel_client name: angel_client
version: 1.0.7 version: 1.1.0-alpha
description: Client library for the Angel framework. description: Client library for the Angel framework.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_client homepage: https://github.com/angel-dart/angel_client
environment: environment:
sdk: ">=1.21.0" sdk: ">=1.21.0"
dependencies: dependencies:
angel_framework: ">=1.0.0-dev <2.0.0" angel_http_exception: ^1.0.0
http: ">= 0.11.3 < 0.12.0" http: ">= 0.11.3 < 0.12.0"
json_god: ">=2.0.0-beta <3.0.0" json_god: ">=2.0.0-beta <3.0.0"
merge_map: ">=1.0.0 <2.0.0" merge_map: ">=1.0.0 <2.0.0"
dev_dependencies: 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" test: ">= 0.12.13 < 0.13.0"

94
test/all_test.dart Normal file
View 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'}),
);
});
});
}

View file

@ -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
View 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();
}
}

View file

@ -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;
}
}
''';

View file

@ -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));
});
});
}

View file

@ -1,4 +1,4 @@
import "package:angel_framework/common.dart"; import 'package:angel_model/angel_model.dart';
class Postcard extends Model { class Postcard extends Model {
String location; String location;