Add 'packages/test/' from commit '7508913df7203436eeddb271a53fb922aaeb77db'
git-subtree-dir: packages/test git-subtree-mainline:c6d7d5f416
git-subtree-split:7508913df7
This commit is contained in:
commit
5834fbe416
18 changed files with 1048 additions and 0 deletions
74
packages/test/.gitignore
vendored
Normal file
74
packages/test/.gitignore
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# See https://www.dartlang.org/tools/private-files.html
|
||||||
|
|
||||||
|
# Files and directories created by pub
|
||||||
|
.buildlog
|
||||||
|
.packages
|
||||||
|
.project
|
||||||
|
.pub/
|
||||||
|
.scripts-bin/
|
||||||
|
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
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources/
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
|
||||||
|
.dart_tool
|
17
packages/test/.idea/angel_test.iml
Normal file
17
packages/test/.idea/angel_test.iml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
7
packages/test/.idea/jsLibraryMappings.xml
Normal file
7
packages/test/.idea/jsLibraryMappings.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="ECMAScript 6" />
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
28
packages/test/.idea/misc.xml
Normal file
28
packages/test/.idea/misc.xml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectInspectionProfilesVisibleTreeState">
|
||||||
|
<entry key="Project Default">
|
||||||
|
<profile-state>
|
||||||
|
<expanded-state>
|
||||||
|
<State>
|
||||||
|
<id />
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>General</id>
|
||||||
|
</State>
|
||||||
|
<State>
|
||||||
|
<id>XPath</id>
|
||||||
|
</State>
|
||||||
|
</expanded-state>
|
||||||
|
<selected-state>
|
||||||
|
<State>
|
||||||
|
<id>AngularJS</id>
|
||||||
|
</State>
|
||||||
|
</selected-state>
|
||||||
|
</profile-state>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
8
packages/test/.idea/modules.xml
Normal file
8
packages/test/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/angel_test.iml" filepath="$PROJECT_DIR$/.idea/angel_test.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
7
packages/test/.idea/runConfigurations/Simple_Tests.xml
Normal file
7
packages/test/.idea/runConfigurations/Simple_Tests.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Simple Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/test/simple_test.dart" />
|
||||||
|
<option name="testRunnerOptions" value="-j 4" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
6
packages/test/.idea/vcs.xml
Normal file
6
packages/test/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
4
packages/test/.travis.yml
Normal file
4
packages/test/.travis.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
language: dart
|
||||||
|
dart:
|
||||||
|
- dev
|
||||||
|
- stable
|
23
packages/test/CHANGELOG.md
Normal file
23
packages/test/CHANGELOG.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# 2.0.1
|
||||||
|
* Update badge.
|
||||||
|
* Handle userInfo + basic auth.
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
* Update to work with `client@2`.
|
||||||
|
|
||||||
|
# 2.0.0-alpha.4
|
||||||
|
# 2.0.0-alpha.3
|
||||||
|
* Update `http` dependency.
|
||||||
|
|
||||||
|
# 2.0.0-alpha.2
|
||||||
|
* Explicitly import `package:angel_framework/http.dart`.
|
||||||
|
|
||||||
|
# 2.0.0-alpha.1
|
||||||
|
* Update for compliance with newer `angel_client`.
|
||||||
|
|
||||||
|
# 2.0.0-alpha
|
||||||
|
* Depend on Dart 2 and Angel 2.
|
||||||
|
|
||||||
|
# 1.1.0+1
|
||||||
|
* Dart 2/strong mode fixes.
|
||||||
|
* Pass a `useZone` flag to `AngelHttp` through `TestServer`.
|
21
packages/test/LICENSE
Normal file
21
packages/test/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 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.
|
72
packages/test/README.md
Normal file
72
packages/test/README.md
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# angel_test
|
||||||
|
[](https://pub.dartlang.org/packages/angel_test)
|
||||||
|
[](https://travis-ci.org/angel-dart/test)
|
||||||
|
|
||||||
|
Testing utility library for the Angel framework.
|
||||||
|
|
||||||
|
# TestClient
|
||||||
|
The `TestClient` class is a custom `angel_client` that sends mock requests to your server.
|
||||||
|
This means that you will not have to bind your server to HTTP to run.
|
||||||
|
Plus, it is an `angel_client`, and thus supports services and other goodies.
|
||||||
|
|
||||||
|
The `TestClient` also supports WebSockets. WebSockets cannot be mocked (yet!) within this library,
|
||||||
|
so calling the `websocket()` function will also bind your server to HTTP, if it is not already listening.
|
||||||
|
|
||||||
|
The return value is a `WebSockets` client instance
|
||||||
|
(from [`package:angel_websocket`](https://github.com/angel-dart/websocket));
|
||||||
|
|
||||||
|
```dart
|
||||||
|
var ws = await client.websocket('/ws');
|
||||||
|
ws.service('api/users').onCreated.listen(...);
|
||||||
|
|
||||||
|
// To receive all blobs of data sent on the WebSocket:
|
||||||
|
ws.onData.listen(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Matchers
|
||||||
|
Several `Matcher`s are bundled with this package, and run on any `package:http` `Response`,
|
||||||
|
not just those returned by Angel.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
test('foo', () async {
|
||||||
|
var res = await client.get('/foo');
|
||||||
|
expect(res, allOf([
|
||||||
|
isJson({'foo': 'bar'}),
|
||||||
|
hasStatus(200),
|
||||||
|
hasContentType(ContentType.JSON),
|
||||||
|
hasContentType('application/json'),
|
||||||
|
hasHeader('server'), // Assert header present
|
||||||
|
hasHeader('server', 'angel'), // Assert header present with value
|
||||||
|
hasHeader('foo', ['bar', 'baz']), // ... Or multiple values
|
||||||
|
hasBody(), // Assert non-empty body
|
||||||
|
hasBody('{"foo":"bar"}') // Assert specific body
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error', () async {
|
||||||
|
var res = await client.get('/error');
|
||||||
|
expect(res, isAngelHttpException());
|
||||||
|
expect(res, isAngelHttpException(statusCode: 404, message: ..., errors: [...])) // Optional
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`hasValidBody` is one of the most powerful `Matcher`s in this library,
|
||||||
|
because it allows you to validate a JSON body against a
|
||||||
|
[validation schema](https://github.com/angel-dart/validate).
|
||||||
|
|
||||||
|
Angel provides a comprehensive validation library that integrates tightly
|
||||||
|
with the very `matcher` package that you already use for testing. :)
|
||||||
|
|
||||||
|
[https://github.com/angel-dart/validate](https://github.com/angel-dart/validate)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
test('validate response', () async {
|
||||||
|
var res = await client.get('/bar');
|
||||||
|
expect(res, hasValidBody(new Validator({
|
||||||
|
'foo': isBoolean,
|
||||||
|
'bar': [isString, equals('baz')],
|
||||||
|
'age*': [],
|
||||||
|
'nested': someNestedValidator
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
```
|
3
packages/test/analysis_options.yaml
Normal file
3
packages/test/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
142
packages/test/example/main.dart
Normal file
142
packages/test/example/main.dart
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:angel_validate/angel_validate.dart';
|
||||||
|
import 'package:angel_websocket/server.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..get('/hello', (req, res) => 'Hello')
|
||||||
|
..get(
|
||||||
|
'/error',
|
||||||
|
(req, res) => throw new AngelHttpException.forbidden(message: 'Test')
|
||||||
|
..errors.addAll(['foo', 'bar']))
|
||||||
|
..get('/body', (req, res) {
|
||||||
|
res
|
||||||
|
..write('OK')
|
||||||
|
..close();
|
||||||
|
})
|
||||||
|
..get(
|
||||||
|
'/valid',
|
||||||
|
(req, res) => {
|
||||||
|
'michael': 'jackson',
|
||||||
|
'billie': {'jean': 'hee-hee', 'is_my_lover': false}
|
||||||
|
})
|
||||||
|
..post('/hello', (req, res) async {
|
||||||
|
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
|
return {'bar': body['foo']};
|
||||||
|
})
|
||||||
|
..get('/gzip', (req, res) async {
|
||||||
|
res
|
||||||
|
..headers['content-encoding'] = 'gzip'
|
||||||
|
..add(gzip.encode('Poop'.codeUnits))
|
||||||
|
..close();
|
||||||
|
})
|
||||||
|
..use(
|
||||||
|
'/foo',
|
||||||
|
new AnonymousService(
|
||||||
|
index: ([params]) async => [
|
||||||
|
{'michael': 'jackson'}
|
||||||
|
],
|
||||||
|
create: (data, [params]) async => {'foo': 'bar'}));
|
||||||
|
|
||||||
|
var ws = new AngelWebSocket(app);
|
||||||
|
await app.configure(ws.configureServer);
|
||||||
|
app.all('/ws', ws.handleRequest);
|
||||||
|
|
||||||
|
app.errorHandler = (e, req, res) => e.toJson();
|
||||||
|
|
||||||
|
client = await connectTo(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await client.close();
|
||||||
|
app = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
group('matchers', () {
|
||||||
|
group('isJson+hasStatus', () {
|
||||||
|
test('get', () async {
|
||||||
|
final response = await client.get('/hello');
|
||||||
|
expect(response, isJson('Hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('post', () async {
|
||||||
|
final response = await client.post('/hello', body: {'foo': 'baz'});
|
||||||
|
expect(response, allOf(hasStatus(200), isJson({'bar': 'baz'})));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isAngelHttpException', () async {
|
||||||
|
var res = await client.get('/error');
|
||||||
|
print(res.body);
|
||||||
|
expect(res, isAngelHttpException());
|
||||||
|
expect(
|
||||||
|
res,
|
||||||
|
isAngelHttpException(
|
||||||
|
statusCode: 403, message: 'Test', errors: ['foo', 'bar']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasBody', () async {
|
||||||
|
var res = await client.get('/body');
|
||||||
|
expect(res, hasBody());
|
||||||
|
expect(res, hasBody('OK'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasHeader', () async {
|
||||||
|
var res = await client.get('/hello');
|
||||||
|
expect(res, hasHeader('server'));
|
||||||
|
expect(res, hasHeader('server', 'angel'));
|
||||||
|
expect(res, hasHeader('server', ['angel']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasValidBody+hasContentType', () async {
|
||||||
|
var res = await client.get('/valid');
|
||||||
|
expect(res, hasContentType('application/json'));
|
||||||
|
expect(
|
||||||
|
res,
|
||||||
|
hasValidBody(new Validator({
|
||||||
|
'michael*': [isString, isNotEmpty, equals('jackson')],
|
||||||
|
'billie': new Validator({
|
||||||
|
'jean': [isString, isNotEmpty],
|
||||||
|
'is_my_lover': [isBool, isFalse]
|
||||||
|
})
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gzip decode', () async {
|
||||||
|
var res = await client.get('/gzip');
|
||||||
|
expect(res, hasHeader('content-encoding', 'gzip'));
|
||||||
|
expect(res, hasBody('Poop'));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('service', () {
|
||||||
|
test('index', () async {
|
||||||
|
var foo = client.service('foo');
|
||||||
|
var result = await foo.index();
|
||||||
|
expect(result, [
|
||||||
|
{'michael': 'jackson'}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var foo = client.service('foo');
|
||||||
|
var result = await foo.create({});
|
||||||
|
expect(result, {'foo': 'bar'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('websocket', () async {
|
||||||
|
var ws = await client.websocket();
|
||||||
|
var foo = ws.service('foo');
|
||||||
|
foo.create({});
|
||||||
|
var result = await foo.onCreated.first;
|
||||||
|
expect(result.data, equals({'foo': 'bar'}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
2
packages/test/lib/angel_test.dart
Normal file
2
packages/test/lib/angel_test.dart
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export 'src/client.dart';
|
||||||
|
export 'src/matchers.dart';
|
193
packages/test/lib/src/client.dart
Normal file
193
packages/test/lib/src/client.dart
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_client/base_angel_client.dart' as client;
|
||||||
|
import 'package:angel_client/io.dart' as client;
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_framework/http.dart';
|
||||||
|
import 'package:angel_websocket/io.dart' as client;
|
||||||
|
import 'package:http/http.dart' as http hide StreamedResponse;
|
||||||
|
import 'package:http/io_client.dart' as http;
|
||||||
|
import 'package:http/src/streamed_response.dart';
|
||||||
|
import 'package:mock_request/mock_request.dart';
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
import 'package:web_socket_channel/io.dart';
|
||||||
|
//import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
final RegExp _straySlashes = new RegExp(r"(^/)|(/+$)");
|
||||||
|
/*const Map<String, String> _readHeaders = const {'Accept': 'application/json'};
|
||||||
|
const Map<String, String> _writeHeaders = const {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
final Uuid _uuid = new Uuid();*/
|
||||||
|
|
||||||
|
/// Shorthand for bootstrapping a [TestClient].
|
||||||
|
Future<TestClient> connectTo(Angel app,
|
||||||
|
{Map initialSession,
|
||||||
|
bool autoDecodeGzip: true,
|
||||||
|
bool useZone: false}) async {
|
||||||
|
if (!app.isProduction) app.configuration.putIfAbsent('testMode', () => true);
|
||||||
|
|
||||||
|
for (var plugin in app.startupHooks) await plugin(app);
|
||||||
|
return new TestClient(app,
|
||||||
|
autoDecodeGzip: autoDecodeGzip != false, useZone: useZone)
|
||||||
|
..session.addAll(initialSession ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An `angel_client` that sends mock requests to a server, rather than actual HTTP transactions.
|
||||||
|
class TestClient extends client.BaseAngelClient {
|
||||||
|
final Map<String, client.Service> _services = {};
|
||||||
|
|
||||||
|
/// Session info to be sent to the server on every request.
|
||||||
|
final HttpSession session = new MockHttpSession(id: 'angel-test-client');
|
||||||
|
|
||||||
|
/// A list of cookies to be sent to and received from the server.
|
||||||
|
final List<Cookie> cookies = [];
|
||||||
|
|
||||||
|
/// If `true` (default), the client will automatically decode GZIP response bodies.
|
||||||
|
final bool autoDecodeGzip;
|
||||||
|
|
||||||
|
/// The server instance to mock.
|
||||||
|
final Angel server;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String authToken;
|
||||||
|
|
||||||
|
AngelHttp _http;
|
||||||
|
|
||||||
|
TestClient(this.server, {this.autoDecodeGzip: true, bool useZone: false})
|
||||||
|
: super(new http.IOClient(), '/') {
|
||||||
|
_http = new AngelHttp(server, useZone: useZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future close() {
|
||||||
|
this.client.close();
|
||||||
|
return server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens a WebSockets connection to the server. This will automatically bind the server
|
||||||
|
/// over HTTP, if it is not already listening. Unfortunately, WebSockets cannot be mocked (yet!).
|
||||||
|
Future<client.WebSockets> websocket(
|
||||||
|
{String path: '/ws', Duration timeout}) async {
|
||||||
|
if (_http.server == null) await _http.startServer();
|
||||||
|
var url = _http.uri.replace(scheme: 'ws', path: path);
|
||||||
|
var ws = new _MockWebSockets(this, url.toString());
|
||||||
|
await ws.connect(timeout: timeout);
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StreamedResponse> send(http.BaseRequest request) async {
|
||||||
|
var rq = new MockHttpRequest(request.method, request.url);
|
||||||
|
request.headers.forEach(rq.headers.add);
|
||||||
|
|
||||||
|
if (request.url.userInfo.isNotEmpty) {
|
||||||
|
// Attempt to send as Basic auth
|
||||||
|
var encoded = base64Url.encode(utf8.encode(request.url.userInfo));
|
||||||
|
rq.headers.add('authorization', 'Basic $encoded');
|
||||||
|
} else if (rq.headers.value('authorization')?.startsWith('Basic ') ==
|
||||||
|
true) {
|
||||||
|
var encoded = rq.headers.value('authorization').substring(6);
|
||||||
|
var decoded = utf8.decode(base64Url.decode(encoded));
|
||||||
|
var oldRq = rq;
|
||||||
|
var newRq =
|
||||||
|
new MockHttpRequest(rq.method, rq.uri.replace(userInfo: decoded));
|
||||||
|
oldRq.headers.forEach(newRq.headers.add);
|
||||||
|
rq = newRq;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authToken?.isNotEmpty == true)
|
||||||
|
rq.headers.add('authorization', 'Bearer $authToken');
|
||||||
|
|
||||||
|
rq..cookies.addAll(cookies)..session.addAll(session);
|
||||||
|
|
||||||
|
await request.finalize().pipe(rq);
|
||||||
|
|
||||||
|
await _http.handleRequest(rq);
|
||||||
|
|
||||||
|
var rs = rq.response;
|
||||||
|
session
|
||||||
|
..clear()
|
||||||
|
..addAll(rq.session);
|
||||||
|
|
||||||
|
Map<String, String> extractedHeaders = {};
|
||||||
|
|
||||||
|
rs.headers.forEach((k, v) {
|
||||||
|
extractedHeaders[k] = v.join(',');
|
||||||
|
});
|
||||||
|
|
||||||
|
Stream<List<int>> stream = rs;
|
||||||
|
|
||||||
|
if (autoDecodeGzip != false &&
|
||||||
|
rs.headers['content-encoding']?.contains('gzip') == true) {
|
||||||
|
stream = stream.transform(gzip.decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StreamedResponse(stream, rs.statusCode,
|
||||||
|
contentLength: rs.contentLength,
|
||||||
|
isRedirect: rs.headers['location'] != null,
|
||||||
|
headers: extractedHeaders,
|
||||||
|
persistentConnection:
|
||||||
|
rq.headers.value('connection')?.toLowerCase()?.trim() ==
|
||||||
|
'keep-alive' ||
|
||||||
|
rq.headers.persistentConnection == true,
|
||||||
|
reasonPhrase: rs.reasonPhrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String basePath;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String> authenticateViaPopup(String url, {String eventName: 'token'}) {
|
||||||
|
throw new UnsupportedError(
|
||||||
|
'MockClient does not support authentication via popup.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future configure(client.AngelConfigurer configurer) =>
|
||||||
|
new Future.sync(() => configurer(this));
|
||||||
|
|
||||||
|
@override
|
||||||
|
client.Service<Id, Data> service<Id, Data>(String path,
|
||||||
|
{Type type, client.AngelDeserializer<Data> deserializer}) {
|
||||||
|
String uri = path.toString().replaceAll(_straySlashes, "");
|
||||||
|
return _services.putIfAbsent(
|
||||||
|
uri,
|
||||||
|
() => new _MockService<Id, Data>(this, uri,
|
||||||
|
deserializer: deserializer)) as client.Service<Id, Data>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockService<Id, Data> extends client.BaseAngelService<Id, Data> {
|
||||||
|
final TestClient _app;
|
||||||
|
|
||||||
|
_MockService(this._app, String basePath,
|
||||||
|
{client.AngelDeserializer<Data> deserializer})
|
||||||
|
: super(null, _app, basePath, deserializer: deserializer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamedResponse> send(http.BaseRequest request) {
|
||||||
|
if (app.authToken != null && app.authToken.isNotEmpty) {
|
||||||
|
request.headers['authorization'] ??= 'Bearer ${app.authToken}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return _app.send(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockWebSockets extends client.WebSockets {
|
||||||
|
final TestClient app;
|
||||||
|
|
||||||
|
_MockWebSockets(this.app, String url) : super(url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WebSocketChannel> getConnectedWebSocket() async {
|
||||||
|
Map<String, String> headers = {};
|
||||||
|
|
||||||
|
if (app.authToken?.isNotEmpty == true)
|
||||||
|
headers['authorization'] = 'Bearer ${app.authToken}';
|
||||||
|
|
||||||
|
var socket = await WebSocket.connect(baseUrl.toString(), headers: headers);
|
||||||
|
return new IOWebSocketChannel(socket);
|
||||||
|
}
|
||||||
|
}
|
253
packages/test/lib/src/matchers.dart
Normal file
253
packages/test/lib/src/matchers.dart
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||||
|
import 'package:angel_validate/angel_validate.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:matcher/matcher.dart';
|
||||||
|
|
||||||
|
/// Expects a response to be a JSON representation of an `AngelHttpException`.
|
||||||
|
///
|
||||||
|
/// You can optionally check for a matching [message], [statusCode] and [errors].
|
||||||
|
Matcher isAngelHttpException(
|
||||||
|
{String message, int statusCode, Iterable<String> errors: const []}) =>
|
||||||
|
new _IsAngelHttpException(
|
||||||
|
message: message, statusCode: statusCode, errors: errors);
|
||||||
|
|
||||||
|
/// Expects a given response, when parsed as JSON,
|
||||||
|
/// to equal a desired value.
|
||||||
|
Matcher isJson(value) => new _IsJson(value);
|
||||||
|
|
||||||
|
/// Expects a response to have the given content type, whether a `String` or [ContentType].
|
||||||
|
Matcher hasContentType(contentType) => new _HasContentType(contentType);
|
||||||
|
|
||||||
|
/// Expects a response to have the given body.
|
||||||
|
///
|
||||||
|
/// If `true` is passed as the value (default), then this matcher will simply assert
|
||||||
|
/// that the response has a non-empty body.
|
||||||
|
///
|
||||||
|
/// If value is a `List<int>`, then it will be matched against `res.bodyBytes`.
|
||||||
|
/// Otherwise, the string value will be matched against `res.body`.
|
||||||
|
Matcher hasBody([value]) => new _HasBody(value ?? true);
|
||||||
|
|
||||||
|
/// Expects a response to have a header named [key] which contains [value]. [value] can be a `String`, or a List of `String`s.
|
||||||
|
///
|
||||||
|
/// If `value` is true (default), then this matcher will simply assert that the header is present.
|
||||||
|
Matcher hasHeader(String key, [value]) => new _HasHeader(key, value ?? true);
|
||||||
|
|
||||||
|
/// Expects a response to have the given status code.
|
||||||
|
Matcher hasStatus(int status) => new _HasStatus(status);
|
||||||
|
|
||||||
|
/// Expects a response to have a JSON body that is a `Map` and satisfies the given [validator] schema.
|
||||||
|
Matcher hasValidBody(Validator validator) => new _HasValidBody(validator);
|
||||||
|
|
||||||
|
class _IsJson extends Matcher {
|
||||||
|
var value;
|
||||||
|
|
||||||
|
_IsJson(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
return description.add('equals the desired JSON response: $value');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) =>
|
||||||
|
item is http.Response &&
|
||||||
|
equals(value).matches(json.decode(item.body), matchState);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HasBody extends Matcher {
|
||||||
|
final body;
|
||||||
|
|
||||||
|
_HasBody(this.body);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) =>
|
||||||
|
description.add('has body $body');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) {
|
||||||
|
if (item is http.Response) {
|
||||||
|
if (body == true) return isNotEmpty.matches(item.bodyBytes, matchState);
|
||||||
|
if (body is List<int>)
|
||||||
|
return equals(body).matches(item.bodyBytes, matchState);
|
||||||
|
else
|
||||||
|
return equals(body.toString()).matches(item.body, matchState);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HasContentType extends Matcher {
|
||||||
|
var contentType;
|
||||||
|
|
||||||
|
_HasContentType(this.contentType);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
var str = contentType is ContentType
|
||||||
|
? ((contentType as ContentType).value)
|
||||||
|
: contentType.toString();
|
||||||
|
return description.add('has content type ' + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) {
|
||||||
|
if (item is http.Response) {
|
||||||
|
if (!item.headers.containsKey('content-type')) return false;
|
||||||
|
|
||||||
|
if (contentType is ContentType) {
|
||||||
|
var compare = ContentType.parse(item.headers['content-type']);
|
||||||
|
return equals(contentType.mimeType)
|
||||||
|
.matches(compare.mimeType, matchState);
|
||||||
|
} else {
|
||||||
|
return equals(contentType.toString())
|
||||||
|
.matches(item.headers['content-type'], matchState);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HasHeader extends Matcher {
|
||||||
|
final String key;
|
||||||
|
final value;
|
||||||
|
|
||||||
|
_HasHeader(this.key, this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
if (value == true)
|
||||||
|
return description.add('contains header $key');
|
||||||
|
else
|
||||||
|
return description.add('contains header $key with value(s) $value');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) {
|
||||||
|
if (item is http.Response) {
|
||||||
|
if (value == true) {
|
||||||
|
return contains(key.toLowerCase())
|
||||||
|
.matches(item.headers.keys, matchState);
|
||||||
|
} else {
|
||||||
|
if (!item.headers.containsKey(key.toLowerCase())) return false;
|
||||||
|
Iterable v = value is Iterable ? (value as Iterable) : [value];
|
||||||
|
return v
|
||||||
|
.map((x) => x.toString())
|
||||||
|
.every(item.headers[key.toLowerCase()].split(',').contains);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HasStatus extends Matcher {
|
||||||
|
int status;
|
||||||
|
|
||||||
|
_HasStatus(this.status);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
return description.add('has status code $status');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) =>
|
||||||
|
item is http.Response &&
|
||||||
|
equals(status).matches(item.statusCode, matchState);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HasValidBody extends Matcher {
|
||||||
|
final Validator validator;
|
||||||
|
|
||||||
|
_HasValidBody(this.validator);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) =>
|
||||||
|
description.add('matches validation schema ${validator.rules}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) {
|
||||||
|
if (item is http.Response) {
|
||||||
|
final jsons = json.decode(item.body);
|
||||||
|
if (jsons is! Map) return false;
|
||||||
|
return validator.matches(jsons, matchState);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IsAngelHttpException extends Matcher {
|
||||||
|
String message;
|
||||||
|
int statusCode;
|
||||||
|
final List<String> errors = [];
|
||||||
|
|
||||||
|
_IsAngelHttpException(
|
||||||
|
{this.message, this.statusCode, Iterable<String> errors: const []}) {
|
||||||
|
this.errors.addAll(errors ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
if (message?.isNotEmpty != true && statusCode == null && errors.isEmpty)
|
||||||
|
return description.add('is an Angel HTTP Exception');
|
||||||
|
else {
|
||||||
|
var buf = new StringBuffer('is an Angel HTTP Exception with');
|
||||||
|
|
||||||
|
if (statusCode != null) buf.write(' status code $statusCode');
|
||||||
|
|
||||||
|
if (message?.isNotEmpty == true) {
|
||||||
|
if (statusCode != null && errors.isNotEmpty)
|
||||||
|
buf.write(',');
|
||||||
|
else if (statusCode != null && errors.isEmpty) buf.write(' and');
|
||||||
|
buf.write(' message "$message"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.isNotEmpty) {
|
||||||
|
if (statusCode != null || message?.isNotEmpty == true)
|
||||||
|
buf.write(' and errors $errors');
|
||||||
|
else
|
||||||
|
buf.write(' errors $errors');
|
||||||
|
}
|
||||||
|
|
||||||
|
return description.add(buf.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) {
|
||||||
|
if (item is http.Response) {
|
||||||
|
final jsons = json.decode(item.body);
|
||||||
|
|
||||||
|
if (jsons is Map && jsons['isError'] == true) {
|
||||||
|
var exc = new AngelHttpException.fromMap(jsons);
|
||||||
|
print(exc.toJson());
|
||||||
|
|
||||||
|
if (message?.isNotEmpty != true && statusCode == null && errors.isEmpty)
|
||||||
|
return true;
|
||||||
|
else {
|
||||||
|
if (statusCode != null) if (!equals(statusCode)
|
||||||
|
.matches(exc.statusCode, matchState)) return false;
|
||||||
|
|
||||||
|
if (message?.isNotEmpty == true) if (!equals(message)
|
||||||
|
.matches(exc.message, matchState)) return false;
|
||||||
|
|
||||||
|
if (errors.isNotEmpty) {
|
||||||
|
if (!errors
|
||||||
|
.every((err) => contains(err).matches(exc.errors, matchState)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
packages/test/pubspec.yaml
Normal file
19
packages/test/pubspec.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
description: Testing utility library for the Angel framework. Use with package:test.
|
||||||
|
homepage: https://github.com/angel-dart/test.git
|
||||||
|
name: angel_test
|
||||||
|
version: 2.0.1
|
||||||
|
dependencies:
|
||||||
|
angel_client: ^2.0.0
|
||||||
|
angel_framework: ^2.0.0-alpha
|
||||||
|
angel_http_exception: ^1.0.0
|
||||||
|
angel_validate: ^2.0.0
|
||||||
|
angel_websocket: ^2.0.0
|
||||||
|
http: ^0.12.0
|
||||||
|
matcher: ^0.12.0
|
||||||
|
mock_request: ^1.0.0
|
||||||
|
web_socket_channel: ^1.0.0
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.0.0
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.0.0-dev <3.0.0"
|
169
packages/test/test/simple_test.dart
Normal file
169
packages/test/test/simple_test.dart
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:angel_validate/angel_validate.dart';
|
||||||
|
import 'package:angel_websocket/server.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..get('/hello', (req, res) => 'Hello')
|
||||||
|
..get('/user_info', (req, res) => {'u': req.uri.userInfo})
|
||||||
|
..get(
|
||||||
|
'/error',
|
||||||
|
(req, res) => throw new AngelHttpException.forbidden(message: 'Test')
|
||||||
|
..errors.addAll(['foo', 'bar']))
|
||||||
|
..get('/body', (req, res) {
|
||||||
|
res
|
||||||
|
..write('OK')
|
||||||
|
..close();
|
||||||
|
})
|
||||||
|
..get(
|
||||||
|
'/valid',
|
||||||
|
(req, res) => {
|
||||||
|
'michael': 'jackson',
|
||||||
|
'billie': {'jean': 'hee-hee', 'is_my_lover': false}
|
||||||
|
})
|
||||||
|
..post('/hello', (req, res) async {
|
||||||
|
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
|
return {'bar': body['foo']};
|
||||||
|
})
|
||||||
|
..get('/gzip', (req, res) async {
|
||||||
|
res
|
||||||
|
..headers['content-encoding'] = 'gzip'
|
||||||
|
..add(gzip.encode('Poop'.codeUnits))
|
||||||
|
..close();
|
||||||
|
})
|
||||||
|
..use(
|
||||||
|
'/foo',
|
||||||
|
new AnonymousService<String, Map<String, dynamic>>(
|
||||||
|
index: ([params]) async => [
|
||||||
|
<String, dynamic>{'michael': 'jackson'}
|
||||||
|
],
|
||||||
|
create: (data, [params]) async =>
|
||||||
|
<String, dynamic>{'foo': 'bar'}));
|
||||||
|
|
||||||
|
var ws = new AngelWebSocket(app);
|
||||||
|
await app.configure(ws.configureServer);
|
||||||
|
app.all('/ws', ws.handleRequest);
|
||||||
|
|
||||||
|
app.errorHandler = (e, req, res) => e.toJson();
|
||||||
|
|
||||||
|
client = await connectTo(app, useZone: false);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await client.close();
|
||||||
|
app = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
group('matchers', () {
|
||||||
|
group('isJson+hasStatus', () {
|
||||||
|
test('get', () async {
|
||||||
|
final response = await client.get('/hello');
|
||||||
|
expect(response, isJson('Hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('post', () async {
|
||||||
|
final response = await client.post('/hello', body: {'foo': 'baz'});
|
||||||
|
expect(response, allOf(hasStatus(200), isJson({'bar': 'baz'})));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isAngelHttpException', () async {
|
||||||
|
var res = await client.get('/error');
|
||||||
|
print(res.body);
|
||||||
|
expect(res, isAngelHttpException());
|
||||||
|
expect(
|
||||||
|
res,
|
||||||
|
isAngelHttpException(
|
||||||
|
statusCode: 403, message: 'Test', errors: ['foo', 'bar']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('userInfo from Uri', () async {
|
||||||
|
var url = new Uri(userInfo: 'foo:bar', path: '/user_info');
|
||||||
|
print('URL: $url');
|
||||||
|
var res = await client.get(url);
|
||||||
|
print(res.body);
|
||||||
|
var m = json.decode(res.body) as Map;
|
||||||
|
expect(m, {'u': 'foo:bar'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('userInfo from Basic auth header', () async {
|
||||||
|
var url = new Uri(path: '/user_info');
|
||||||
|
print('URL: $url');
|
||||||
|
var res = await client.get(url, headers: {
|
||||||
|
'authorization': 'Basic ' + (base64Url.encode(utf8.encode('foo:bar')))
|
||||||
|
});
|
||||||
|
print(res.body);
|
||||||
|
var m = json.decode(res.body) as Map;
|
||||||
|
expect(m, {'u': 'foo:bar'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasBody', () async {
|
||||||
|
var res = await client.get('/body');
|
||||||
|
expect(res, hasBody());
|
||||||
|
expect(res, hasBody('OK'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasHeader', () async {
|
||||||
|
var res = await client.get('/hello');
|
||||||
|
expect(res, hasHeader('server'));
|
||||||
|
expect(res, hasHeader('server', 'angel'));
|
||||||
|
expect(res, hasHeader('server', ['angel']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasValidBody+hasContentType', () async {
|
||||||
|
var res = await client.get('/valid');
|
||||||
|
print('Body: ${res.body}');
|
||||||
|
expect(res, hasContentType('application/json'));
|
||||||
|
expect(res, hasContentType(new ContentType('application', 'json')));
|
||||||
|
expect(
|
||||||
|
res,
|
||||||
|
hasValidBody(new Validator({
|
||||||
|
'michael*': [isString, isNotEmpty, equals('jackson')],
|
||||||
|
'billie': new Validator({
|
||||||
|
'jean': [isString, isNotEmpty],
|
||||||
|
'is_my_lover': [isBool, isFalse]
|
||||||
|
})
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gzip decode', () async {
|
||||||
|
var res = await client.get('/gzip');
|
||||||
|
print('Body: ${res.body}');
|
||||||
|
expect(res, hasHeader('content-encoding', 'gzip'));
|
||||||
|
expect(res, hasBody('Poop'));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('service', () {
|
||||||
|
test('index', () async {
|
||||||
|
var foo = client.service('foo');
|
||||||
|
var result = await foo.index();
|
||||||
|
expect(result, [
|
||||||
|
<String, dynamic>{'michael': 'jackson'}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var foo = client.service('foo');
|
||||||
|
var result = await foo.create({});
|
||||||
|
expect(result, <String, dynamic>{'foo': 'bar'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('websocket', () async {
|
||||||
|
var ws = await client.websocket();
|
||||||
|
var foo = ws.service('foo');
|
||||||
|
foo.create(<String, dynamic>{});
|
||||||
|
var result = await foo.onCreated.first;
|
||||||
|
expect(result is Map ? result : result.data,
|
||||||
|
equals(<String, dynamic>{'foo': 'bar'}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue