Added merge_map and mock_request packages
This commit is contained in:
parent
aefe1f1ab8
commit
f8ec10d4e2
26 changed files with 1285 additions and 6 deletions
|
@ -8,6 +8,9 @@
|
||||||
* Updated angel_route to 5.0.0
|
* Updated angel_route to 5.0.0
|
||||||
* Updated angel_model to 3.0.0
|
* Updated angel_model to 3.0.0
|
||||||
* Updated angel_container to 3.0.0
|
* Updated angel_container to 3.0.0
|
||||||
|
* Updated angel_framework to 4.0.0
|
||||||
|
- merge_map
|
||||||
|
- mock_request
|
||||||
|
|
||||||
# 3.0.0 (Non NNBD)
|
# 3.0.0 (Non NNBD)
|
||||||
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"
|
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: angel_framework
|
name: angel_framework
|
||||||
version: 3.0.0
|
version: 4.0.0
|
||||||
description: A high-powered HTTP server with dependency injection, routing and much more.
|
description: A high-powered HTTP server with dependency injection, routing and much more.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_framework
|
homepage: https://github.com/angel-dart/angel_framework
|
||||||
|
@ -10,25 +10,29 @@ dependencies:
|
||||||
angel_container:
|
angel_container:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
url: https://github.com/dukefirehawk/angel.git
|
||||||
ref: sdk-2.12.x
|
ref: sdk-2.12.x_nnbd
|
||||||
path: packages/container/angel_container
|
path: packages/container/angel_container
|
||||||
angel_http_exception:
|
angel_http_exception:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
url: https://github.com/dukefirehawk/angel.git
|
||||||
ref: sdk-2.12.x
|
ref: sdk-2.12.x_nnbd
|
||||||
path: packages/http_exception
|
path: packages/http_exception
|
||||||
angel_model:
|
angel_model:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
url: https://github.com/dukefirehawk/angel.git
|
||||||
ref: sdk-2.12.x
|
ref: sdk-2.12.x_nnbd
|
||||||
path: packages/model
|
path: packages/model
|
||||||
angel_route:
|
angel_route:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/dukefirehawk/angel.git
|
url: https://github.com/dukefirehawk/angel.git
|
||||||
ref: sdk-2.12.x
|
ref: sdk-2.12.x_nnbd
|
||||||
path: packages/route
|
path: packages/route
|
||||||
charcode: ^1.0.0
|
charcode: ^1.0.0
|
||||||
combinator: ^1.0.0
|
combinator:
|
||||||
|
git:
|
||||||
|
url: https://github.com/dukefirehawk/angel.git
|
||||||
|
ref: sdk-2.12.x_nnbd
|
||||||
|
path: packages/combinator
|
||||||
file: ^6.1.0
|
file: ^6.1.0
|
||||||
http_parser: ^4.0.0
|
http_parser: ^4.0.0
|
||||||
http_server: ^0.9.0
|
http_server: ^0.9.0
|
||||||
|
|
76
packages/merge_map/.gitignore
vendored
Normal file
76
packages/merge_map/.gitignore
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### 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
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.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 template
|
||||||
|
# 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
|
||||||
|
|
||||||
|
.dart_tool
|
7
packages/merge_map/CHANGELOG.md
Normal file
7
packages/merge_map/CHANGELOG.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# 1.0.2
|
||||||
|
* Add an example, for Pub's sake.
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
* Add a specific constraint on Dart versions, to prevent Pub from rejecting all packages that depend on
|
||||||
|
`merge_map` (the entire Angel framework).
|
||||||
|
* Add generic type support
|
21
packages/merge_map/LICENSE
Normal file
21
packages/merge_map/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Tobe O
|
||||||
|
|
||||||
|
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.
|
19
packages/merge_map/README.md
Normal file
19
packages/merge_map/README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# merge_map
|
||||||
|
Combine multiple Maps into one. Equivalent to
|
||||||
|
[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
|
||||||
|
in JS.
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import "package:merge_map/merge_map.dart";
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Map map1 = {'hello': 'world'};
|
||||||
|
Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}};
|
||||||
|
Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}};
|
||||||
|
Map merged = mergeMap(map1, map2, map3);
|
||||||
|
|
||||||
|
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
||||||
|
}
|
||||||
|
```
|
11
packages/merge_map/example/main.dart
Normal file
11
packages/merge_map/example/main.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:merge_map/merge_map.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Map map1 = {'hello': 'world'};
|
||||||
|
Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}};
|
||||||
|
Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}};
|
||||||
|
Map merged = mergeMap([map1, map2, map3]);
|
||||||
|
print(merged);
|
||||||
|
|
||||||
|
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
||||||
|
}
|
34
packages/merge_map/lib/merge_map.dart
Normal file
34
packages/merge_map/lib/merge_map.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/// Exposes the [mergeMap] function, which... merges Maps.
|
||||||
|
library merge_map;
|
||||||
|
|
||||||
|
_copyValues<K, V>(
|
||||||
|
Map<K, V> from, Map<K, V?>? to, bool recursive, bool acceptNull) {
|
||||||
|
for (var key in from.keys) {
|
||||||
|
if (from[key] is Map<K, V> && recursive) {
|
||||||
|
if (!(to![key] is Map<K, V>)) {
|
||||||
|
to[key] = <K, V>{} as V;
|
||||||
|
}
|
||||||
|
_copyValues(from[key] as Map, to[key] as Map?, recursive, acceptNull);
|
||||||
|
} else {
|
||||||
|
if (from[key] != null || acceptNull) to![key] = from[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges the values of the given maps together.
|
||||||
|
///
|
||||||
|
/// `recursive` is set to `true` by default. If set to `true`,
|
||||||
|
/// then nested maps will also be merged. Otherwise, nested maps
|
||||||
|
/// will overwrite others.
|
||||||
|
///
|
||||||
|
/// `acceptNull` is set to `false` by default. If set to `false`,
|
||||||
|
/// then if the value on a map is `null`, it will be ignored, and
|
||||||
|
/// that `null` will not be copied.
|
||||||
|
Map<K, V> mergeMap<K, V>(Iterable<Map<K, V>> maps,
|
||||||
|
{bool recursive: true, bool acceptNull: false}) {
|
||||||
|
Map<K, V> result = <K, V>{};
|
||||||
|
maps.forEach((Map<K, V> map) {
|
||||||
|
if (map != null) _copyValues(map, result, recursive, acceptNull);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
9
packages/merge_map/pubspec.yaml
Normal file
9
packages/merge_map/pubspec.yaml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name: merge_map
|
||||||
|
description: Combine multiple Maps into one. Equivalent to Object.assign in JS.
|
||||||
|
version: 3.0.0
|
||||||
|
homepage: https://github.com/thosakwe/merge_map
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.16.8
|
94
packages/merge_map/test/all_test.dart
Normal file
94
packages/merge_map/test/all_test.dart
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import "package:merge_map/merge_map.dart";
|
||||||
|
import "package:test/test.dart";
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('can merge two simple maps', () {
|
||||||
|
Map merged = mergeMap([
|
||||||
|
{'hello': 'world'},
|
||||||
|
{'hello': 'dolly'}
|
||||||
|
]);
|
||||||
|
expect(merged['hello'], equals('dolly'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the last map's values supersede those of prior", () {
|
||||||
|
Map merged = mergeMap([
|
||||||
|
{'letter': 'a'},
|
||||||
|
{'letter': 'b'},
|
||||||
|
{'letter': 'c'}
|
||||||
|
]);
|
||||||
|
expect(merged['letter'], equals('c'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can merge two once-nested maps", () {
|
||||||
|
Map map1 = {
|
||||||
|
'hello': 'world',
|
||||||
|
'foo': {'nested': false}
|
||||||
|
};
|
||||||
|
Map map2 = {
|
||||||
|
'goodbye': 'sad life',
|
||||||
|
'foo': {'nested': true, 'it': 'works'}
|
||||||
|
};
|
||||||
|
Map merged = mergeMap([map1, map2]);
|
||||||
|
|
||||||
|
expect(merged['hello'], equals('world'));
|
||||||
|
expect(merged['goodbye'], equals('sad life'));
|
||||||
|
expect(merged['foo']['nested'], equals(true));
|
||||||
|
expect(merged['foo']['it'], equals('works'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("once-nested map supersession", () {
|
||||||
|
Map map1 = {
|
||||||
|
'hello': 'world',
|
||||||
|
'foo': {'nested': false}
|
||||||
|
};
|
||||||
|
Map map2 = {
|
||||||
|
'goodbye': 'sad life',
|
||||||
|
'foo': {'nested': true, 'it': 'works'}
|
||||||
|
};
|
||||||
|
Map map3 = {
|
||||||
|
'foo': {'nested': 'supersession'}
|
||||||
|
};
|
||||||
|
|
||||||
|
Map merged = mergeMap([map1, map2, map3]);
|
||||||
|
expect(merged['foo']['nested'], equals('supersession'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can merge two twice-nested maps", () {
|
||||||
|
Map map1 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'd'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map map2 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'D', 'e': 'f'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map merged = mergeMap([map1, map2]);
|
||||||
|
|
||||||
|
expect(merged['a']['b']['c'], equals('D'));
|
||||||
|
expect(merged['a']['b']['e'], equals('f'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("twice-nested map supersession", () {
|
||||||
|
Map map1 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'd'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map map2 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'c': 'D', 'e': 'f'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map map3 = {
|
||||||
|
'a': {
|
||||||
|
'b': {'e': 'supersession'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Map merged = mergeMap([map1, map2, map3]);
|
||||||
|
|
||||||
|
expect(merged['a']['b']['c'], equals('D'));
|
||||||
|
expect(merged['a']['b']['e'], equals('supersession'));
|
||||||
|
});
|
||||||
|
}
|
72
packages/mock_request/.gitignore
vendored
Normal file
72
packages/mock_request/.gitignore
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# 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
|
||||||
|
### 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
|
||||||
|
.idea/dictionaries
|
||||||
|
|
||||||
|
# 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
|
1
packages/mock_request/.travis.yml
Normal file
1
packages/mock_request/.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
17
packages/mock_request/CHANGELOG.md
Normal file
17
packages/mock_request/CHANGELOG.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# 1.0.7
|
||||||
|
* Prepare for upcoming Dart SDK change where `HttpHeaders` methods
|
||||||
|
`add` and `set` take an additional optional parameter `preserveHeaderCase` (thanks @domesticmouse!).
|
||||||
|
|
||||||
|
# 1.0.6
|
||||||
|
* Prepare for upcoming Dart SDK change whereby `HttpRequest` implements
|
||||||
|
`Stream<Uint8List>` rather than `Stream<List<int>>`.
|
||||||
|
|
||||||
|
# 1.0.5
|
||||||
|
* Add `toString` to `MockHttpHeaders`.
|
||||||
|
|
||||||
|
# 1.0.4
|
||||||
|
* Fix for `ifModifiedSince`
|
||||||
|
|
||||||
|
# 1.0.3
|
||||||
|
* Dart2 fixes
|
||||||
|
* Apparently fix hangs that break Angel tests
|
21
packages/mock_request/LICENSE
Normal file
21
packages/mock_request/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Tobe O
|
||||||
|
|
||||||
|
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.
|
25
packages/mock_request/README.md
Normal file
25
packages/mock_request/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# mock_request
|
||||||
|
|
||||||
|
[![Pub](https://img.shields.io/pub/v/mock_request.svg)](https://pub.dartlang.org/packages/mock_request)
|
||||||
|
[![build status](https://travis-ci.org/thosakwe/mock_request.svg)](https://travis-ci.org/thosakwe/mock_request)
|
||||||
|
|
||||||
|
Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc.
|
||||||
|
This makes it possible to test server-side Dart applications without
|
||||||
|
having to ever bind to a port.
|
||||||
|
|
||||||
|
This package was originally designed to testing
|
||||||
|
[Angel](https://github.com/angel-dart/angel/wiki)
|
||||||
|
applications smoother, but works with any Dart-based server. :)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
```dart
|
||||||
|
var rq = new MockHttpRequest('GET', Uri.parse('/foo'));
|
||||||
|
await rq.close();
|
||||||
|
await app.handleRequest(rq); // Run within your server-side application
|
||||||
|
var rs = rq.response;
|
||||||
|
expect(rs.statusCode, equals(200));
|
||||||
|
expect(await rs.transform(UTF8.decoder).join(),
|
||||||
|
equals(JSON.encode('Hello, world!')));
|
||||||
|
```
|
||||||
|
|
||||||
|
More examples can be found in the included tests.
|
4
packages/mock_request/analysis_options.yaml
Normal file
4
packages/mock_request/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
7
packages/mock_request/example/main.dart
Normal file
7
packages/mock_request/example/main.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:mock_request/mock_request.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
var rq = MockHttpRequest('GET', Uri.parse('/foo'));
|
||||||
|
await rq.close();
|
||||||
|
}
|
6
packages/mock_request/lib/mock_request.dart
Normal file
6
packages/mock_request/lib/mock_request.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export 'src/connection_info.dart';
|
||||||
|
export 'src/headers.dart';
|
||||||
|
export 'src/lockable_headers.dart';
|
||||||
|
export 'src/request.dart';
|
||||||
|
export 'src/response.dart';
|
||||||
|
export 'src/session.dart';
|
10
packages/mock_request/lib/src/connection_info.dart
Normal file
10
packages/mock_request/lib/src/connection_info.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
class MockHttpConnectionInfo implements HttpConnectionInfo {
|
||||||
|
@override
|
||||||
|
final InternetAddress remoteAddress;
|
||||||
|
@override
|
||||||
|
final int localPort, remotePort;
|
||||||
|
|
||||||
|
MockHttpConnectionInfo({this.remoteAddress, this.localPort, this.remotePort});
|
||||||
|
}
|
151
packages/mock_request/lib/src/headers.dart
Normal file
151
packages/mock_request/lib/src/headers.dart
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
class MockHttpHeaders extends HttpHeaders {
|
||||||
|
final Map<String, List<String>> _data = {};
|
||||||
|
final List<String> _noFolding = [];
|
||||||
|
Uri _host;
|
||||||
|
|
||||||
|
List<String> get doNotFold => List<String>.unmodifiable(_noFolding);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ContentType get contentType {
|
||||||
|
if (_data.containsKey(HttpHeaders.contentTypeHeader)) {
|
||||||
|
return ContentType.parse(_data[HttpHeaders.contentTypeHeader].join(','));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set contentType(ContentType value) =>
|
||||||
|
set(HttpHeaders.contentTypeHeader, value.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get date => _data.containsKey(HttpHeaders.dateHeader)
|
||||||
|
? HttpDate.parse(_data[HttpHeaders.dateHeader].join(','))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set date(DateTime value) =>
|
||||||
|
set(HttpHeaders.dateHeader, HttpDate.format(value));
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get expires => _data.containsKey(HttpHeaders.expiresHeader)
|
||||||
|
? HttpDate.parse(_data[HttpHeaders.expiresHeader].join(','))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set expires(DateTime value) =>
|
||||||
|
set(HttpHeaders.expiresHeader, HttpDate.format(value));
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get ifModifiedSince =>
|
||||||
|
_data.containsKey(HttpHeaders.ifModifiedSinceHeader)
|
||||||
|
? HttpDate.parse(_data[HttpHeaders.ifModifiedSinceHeader].join(','))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set ifModifiedSince(DateTime value) =>
|
||||||
|
set(HttpHeaders.ifModifiedSinceHeader, HttpDate.format(value));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get host {
|
||||||
|
if (_host != null) {
|
||||||
|
return _host.host;
|
||||||
|
} else if (_data.containsKey(HttpHeaders.hostHeader)) {
|
||||||
|
_host = Uri.parse(_data[HttpHeaders.hostHeader].join(','));
|
||||||
|
return _host.host;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get port {
|
||||||
|
host; // Parse it
|
||||||
|
return _host?.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> operator [](String name) => _data[name.toLowerCase()];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void add(String name, Object value, {bool preserveHeaderCase = false}) {
|
||||||
|
var lower = preserveHeaderCase ? name : name.toLowerCase();
|
||||||
|
|
||||||
|
if (_data.containsKey(lower)) {
|
||||||
|
if (value is Iterable) {
|
||||||
|
_data[lower].addAll(value.map((x) => x.toString()).toList());
|
||||||
|
} else {
|
||||||
|
_data[lower].add(value.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value is Iterable) {
|
||||||
|
_data[lower] = value.map((x) => x.toString()).toList();
|
||||||
|
} else {
|
||||||
|
_data[lower] = [value.toString()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() {
|
||||||
|
_data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forEach(void Function(String name, List<String> values) f) {
|
||||||
|
_data.forEach(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void noFolding(String name) {
|
||||||
|
_noFolding.add(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void remove(String name, Object value) {
|
||||||
|
var lower = name.toLowerCase();
|
||||||
|
|
||||||
|
if (_data.containsKey(lower)) {
|
||||||
|
if (value is Iterable) {
|
||||||
|
for (var x in value) {
|
||||||
|
_data[lower].remove(x.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_data[lower].remove(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeAll(String name) {
|
||||||
|
_data.remove(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void set(String name, Object value, {bool preserveHeaderCase = false}) {
|
||||||
|
var lower = preserveHeaderCase ? name : name.toLowerCase();
|
||||||
|
_data.remove(lower);
|
||||||
|
|
||||||
|
if (value is Iterable) {
|
||||||
|
_data[lower] = value.map((x) => x.toString()).toList();
|
||||||
|
} else {
|
||||||
|
_data[lower] = [value.toString()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String value(String name) => _data[name.toLowerCase()]?.join(',');
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var b = StringBuffer();
|
||||||
|
_data.forEach((k, v) {
|
||||||
|
b.write('$k: ');
|
||||||
|
b.write(v.join(','));
|
||||||
|
b.writeln();
|
||||||
|
});
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
67
packages/mock_request/lib/src/lockable_headers.dart
Normal file
67
packages/mock_request/lib/src/lockable_headers.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'headers.dart';
|
||||||
|
|
||||||
|
/// Headers that can be locked to editing, i.e. after a request body has been written.
|
||||||
|
class LockableMockHttpHeaders extends MockHttpHeaders {
|
||||||
|
bool _locked = false;
|
||||||
|
|
||||||
|
StateError _stateError() =>
|
||||||
|
StateError('Cannot modify headers after they have been write-locked.');
|
||||||
|
|
||||||
|
void lock() {
|
||||||
|
_locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void add(String name, Object value, {bool preserveHeaderCase = false}) {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.add(name, value, preserveHeaderCase: preserveHeaderCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void noFolding(String name) {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.noFolding(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void remove(String name, Object value) {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.remove(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeAll(String name) {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.removeAll(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void set(String name, Object value, {bool preserveHeaderCase = false}) {
|
||||||
|
if (_locked) {
|
||||||
|
throw _stateError();
|
||||||
|
} else {
|
||||||
|
super.set(name, value, preserveHeaderCase: preserveHeaderCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
317
packages/mock_request/lib/src/request.dart
Normal file
317
packages/mock_request/lib/src/request.dart
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:charcode/ascii.dart';
|
||||||
|
import 'connection_info.dart';
|
||||||
|
import 'lockable_headers.dart';
|
||||||
|
import 'response.dart';
|
||||||
|
import 'session.dart';
|
||||||
|
|
||||||
|
class MockHttpRequest
|
||||||
|
implements HttpRequest, StreamSink<List<int>>, StringSink {
|
||||||
|
int _contentLength = 0;
|
||||||
|
BytesBuilder _buf;
|
||||||
|
final Completer _done = Completer();
|
||||||
|
final LockableMockHttpHeaders _headers = LockableMockHttpHeaders();
|
||||||
|
Uri _requestedUri;
|
||||||
|
MockHttpSession _session;
|
||||||
|
final StreamController<Uint8List> _stream = StreamController<Uint8List>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<Cookie> cookies = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
HttpConnectionInfo connectionInfo =
|
||||||
|
MockHttpConnectionInfo(remoteAddress: InternetAddress.loopbackIPv4);
|
||||||
|
|
||||||
|
@override
|
||||||
|
MockHttpResponse response = MockHttpResponse();
|
||||||
|
|
||||||
|
@override
|
||||||
|
HttpSession get session => _session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Uri uri;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool persistentConnection = true;
|
||||||
|
|
||||||
|
/// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor.
|
||||||
|
MockHttpRequest(this.method, this.uri,
|
||||||
|
{bool copyBuffer = true,
|
||||||
|
String protocolVersion,
|
||||||
|
String sessionId,
|
||||||
|
this.certificate,
|
||||||
|
this.persistentConnection}) {
|
||||||
|
_buf = BytesBuilder(copy: copyBuffer != false);
|
||||||
|
_session = MockHttpSession(id: sessionId ?? 'mock-http-session');
|
||||||
|
this.protocolVersion =
|
||||||
|
protocolVersion?.isNotEmpty == true ? protocolVersion : '1.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get contentLength => _contentLength;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HttpHeaders get headers => _headers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Uri get requestedUri {
|
||||||
|
if (_requestedUri != null) {
|
||||||
|
return _requestedUri;
|
||||||
|
} else {
|
||||||
|
return _requestedUri = Uri(
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'example.com',
|
||||||
|
path: uri.path,
|
||||||
|
query: uri.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set requestedUri(Uri value) {
|
||||||
|
_requestedUri = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String protocolVersion;
|
||||||
|
|
||||||
|
@override
|
||||||
|
X509Certificate certificate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void add(List<int> data) {
|
||||||
|
if (_done.isCompleted) {
|
||||||
|
throw StateError('Cannot add to closed MockHttpRequest.');
|
||||||
|
} else {
|
||||||
|
_headers.lock();
|
||||||
|
_contentLength += data.length;
|
||||||
|
_buf.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addError(error, [StackTrace stackTrace]) {
|
||||||
|
if (_done.isCompleted) {
|
||||||
|
throw StateError('Cannot add to closed MockHttpRequest.');
|
||||||
|
} else {
|
||||||
|
_stream.addError(error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future addStream(Stream<List<int>> stream) {
|
||||||
|
var c = Completer();
|
||||||
|
stream.listen(add, onError: addError, onDone: c.complete);
|
||||||
|
return c.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future close() async {
|
||||||
|
await flush();
|
||||||
|
_headers.lock();
|
||||||
|
scheduleMicrotask(_stream.close);
|
||||||
|
_done.complete();
|
||||||
|
return await _done.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future get done => _done.future;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
Future flush() async {
|
||||||
|
_contentLength += _buf.length;
|
||||||
|
_stream.add(_buf.takeBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(Object obj) {
|
||||||
|
obj?.toString()?.codeUnits?.forEach(writeCharCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeAll(Iterable objects, [String separator = '']) {
|
||||||
|
write(objects.join(separator ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeCharCode(int charCode) {
|
||||||
|
add([charCode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeln([Object obj = '']) {
|
||||||
|
write(obj ?? '');
|
||||||
|
add([$cr, $lf]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> any(bool Function(Uint8List element) test) {
|
||||||
|
return _stream.stream.any((List<int> e) {
|
||||||
|
return test(Uint8List.fromList(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> asBroadcastStream({
|
||||||
|
void Function(StreamSubscription<Uint8List> subscription) onListen,
|
||||||
|
void Function(StreamSubscription<Uint8List> subscription) onCancel,
|
||||||
|
}) {
|
||||||
|
return _stream.stream
|
||||||
|
.asBroadcastStream(onListen: onListen, onCancel: onCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<E> asyncExpand<E>(Stream<E> Function(Uint8List event) convert) =>
|
||||||
|
_stream.stream.asyncExpand(convert);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) =>
|
||||||
|
_stream.stream.asyncMap(convert);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> contains(Object needle) => _stream.stream.contains(needle);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> distinct(
|
||||||
|
[bool Function(Uint8List previous, Uint8List next) equals]) =>
|
||||||
|
_stream.stream.distinct(equals);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<E> drain<E>([E futureValue]) => _stream.stream.drain(futureValue);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> elementAt(int index) => _stream.stream.elementAt(index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> every(bool Function(Uint8List element) test) =>
|
||||||
|
_stream.stream.every(test);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<S> expand<S>(Iterable<S> Function(Uint8List value) convert) =>
|
||||||
|
_stream.stream.expand(convert);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> get first => _stream.stream.first;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> firstWhere(bool Function(Uint8List element) test,
|
||||||
|
{List<int> Function() orElse}) =>
|
||||||
|
_stream.stream
|
||||||
|
.firstWhere(test, orElse: () => Uint8List.fromList(orElse()));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<S> fold<S>(
|
||||||
|
S initialValue, S Function(S previous, Uint8List element) combine) =>
|
||||||
|
_stream.stream.fold(initialValue, combine);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future forEach(void Function(Uint8List element) action) =>
|
||||||
|
_stream.stream.forEach(action);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> handleError(Function onError,
|
||||||
|
{bool Function(Object) test}) =>
|
||||||
|
_stream.stream.handleError(onError, test: test);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isBroadcast => _stream.stream.isBroadcast;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get isEmpty => _stream.stream.isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> join([String separator = '']) =>
|
||||||
|
_stream.stream.join(separator ?? '');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> get last => _stream.stream.last;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> lastWhere(bool Function(Uint8List element) test,
|
||||||
|
{List<int> Function() orElse}) =>
|
||||||
|
_stream.stream
|
||||||
|
.lastWhere(test, orElse: () => Uint8List.fromList(orElse()));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> get length => _stream.stream.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
StreamSubscription<Uint8List> listen(
|
||||||
|
void Function(Uint8List event) onData, {
|
||||||
|
Function onError,
|
||||||
|
void Function() onDone,
|
||||||
|
bool cancelOnError,
|
||||||
|
}) {
|
||||||
|
return _stream.stream.listen(
|
||||||
|
onData,
|
||||||
|
onError: onError,
|
||||||
|
onDone: onDone,
|
||||||
|
cancelOnError: cancelOnError == true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<S> map<S>(S Function(Uint8List event) convert) =>
|
||||||
|
_stream.stream.map(convert);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future pipe(StreamConsumer<List<int>> streamConsumer) =>
|
||||||
|
_stream.stream.cast<List<int>>().pipe(streamConsumer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> reduce(
|
||||||
|
List<int> Function(Uint8List previous, Uint8List element) combine) {
|
||||||
|
return _stream.stream.reduce((Uint8List previous, Uint8List element) {
|
||||||
|
return Uint8List.fromList(combine(previous, element));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> get single => _stream.stream.single;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List> singleWhere(bool Function(Uint8List element) test,
|
||||||
|
{List<int> Function() orElse}) =>
|
||||||
|
_stream.stream
|
||||||
|
.singleWhere(test, orElse: () => Uint8List.fromList(orElse()));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> skip(int count) => _stream.stream.skip(count);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) =>
|
||||||
|
_stream.stream.skipWhile(test);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> take(int count) => _stream.stream.take(count);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) =>
|
||||||
|
_stream.stream.takeWhile(test);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> timeout(Duration timeLimit,
|
||||||
|
{void Function(EventSink<Uint8List> sink) onTimeout}) =>
|
||||||
|
_stream.stream.timeout(timeLimit, onTimeout: onTimeout);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Uint8List>> toList() => _stream.stream.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Set<Uint8List>> toSet() => _stream.stream.toSet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) =>
|
||||||
|
_stream.stream.cast<List<int>>().transform(streamTransformer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Uint8List> where(bool Function(Uint8List event) test) =>
|
||||||
|
_stream.stream.where(test);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<R> cast<R>() => Stream.castFrom<List<int>, R>(this);
|
||||||
|
}
|
150
packages/mock_request/lib/src/response.dart
Normal file
150
packages/mock_request/lib/src/response.dart
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:charcode/ascii.dart';
|
||||||
|
import 'connection_info.dart';
|
||||||
|
import 'lockable_headers.dart';
|
||||||
|
|
||||||
|
class MockHttpResponse extends Stream<List<int>> implements HttpResponse {
|
||||||
|
BytesBuilder _buf = BytesBuilder();
|
||||||
|
bool _bufferOutput = true;
|
||||||
|
final Completer _done = Completer();
|
||||||
|
final LockableMockHttpHeaders _headers = LockableMockHttpHeaders();
|
||||||
|
final StreamController<List<int>> _stream = StreamController<List<int>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<Cookie> cookies = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
HttpConnectionInfo connectionInfo =
|
||||||
|
MockHttpConnectionInfo(remoteAddress: InternetAddress.anyIPv4);
|
||||||
|
|
||||||
|
/// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor.
|
||||||
|
MockHttpResponse(
|
||||||
|
{bool copyBuffer = true,
|
||||||
|
this.statusCode,
|
||||||
|
this.reasonPhrase,
|
||||||
|
this.contentLength,
|
||||||
|
this.deadline,
|
||||||
|
this.encoding,
|
||||||
|
this.persistentConnection,
|
||||||
|
bool bufferOutput}) {
|
||||||
|
_buf = BytesBuilder(copy: copyBuffer != false);
|
||||||
|
_bufferOutput = bufferOutput != false;
|
||||||
|
statusCode ??= 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get bufferOutput => _bufferOutput;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set bufferOutput(bool value) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int contentLength;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration deadline;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool persistentConnection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String reasonPhrase;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int statusCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Encoding encoding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HttpHeaders get headers => _headers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future get done => _done.future;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void add(List<int> data) {
|
||||||
|
if (_done.isCompleted) {
|
||||||
|
throw StateError('Cannot add to closed MockHttpResponse.');
|
||||||
|
} else {
|
||||||
|
_headers.lock();
|
||||||
|
if (_bufferOutput == true) {
|
||||||
|
_buf.add(data);
|
||||||
|
} else {
|
||||||
|
_stream.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addError(error, [StackTrace stackTrace]) {
|
||||||
|
if (_done.isCompleted) {
|
||||||
|
throw StateError('Cannot add to closed MockHttpResponse.');
|
||||||
|
} else {
|
||||||
|
_stream.addError(error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future addStream(Stream<List<int>> stream) {
|
||||||
|
var c = Completer();
|
||||||
|
stream.listen(add, onError: addError, onDone: c.complete);
|
||||||
|
return c.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future close() async {
|
||||||
|
_headers.lock();
|
||||||
|
await flush();
|
||||||
|
scheduleMicrotask(_stream.close);
|
||||||
|
_done.complete();
|
||||||
|
//return await _done.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Socket> detachSocket({bool writeHeaders = true}) {
|
||||||
|
throw UnsupportedError('MockHttpResponses have no socket to detach.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future flush() async {
|
||||||
|
_stream.add(_buf.takeBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future redirect(Uri location,
|
||||||
|
{int status = HttpStatus.movedTemporarily}) async {
|
||||||
|
statusCode = status ?? HttpStatus.movedTemporarily;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(Object obj) {
|
||||||
|
obj?.toString()?.codeUnits?.forEach(writeCharCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeAll(Iterable objects, [String separator = '']) {
|
||||||
|
write(objects.join(separator ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeCharCode(int charCode) {
|
||||||
|
add([charCode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeln([Object obj = '']) {
|
||||||
|
write(obj ?? '');
|
||||||
|
add([$cr, $lf]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
StreamSubscription<List<int>> listen(void Function(List<int> event) onData,
|
||||||
|
{Function onError, void Function() onDone, bool cancelOnError}) =>
|
||||||
|
_stream.stream.listen(onData,
|
||||||
|
onError: onError,
|
||||||
|
onDone: onDone,
|
||||||
|
cancelOnError: cancelOnError == true);
|
||||||
|
}
|
74
packages/mock_request/lib/src/session.dart
Normal file
74
packages/mock_request/lib/src/session.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
class MockHttpSession extends MapBase implements HttpSession {
|
||||||
|
final Map _data = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String id;
|
||||||
|
|
||||||
|
MockHttpSession({this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get length => _data.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic operator [](Object key) => _data[key];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void operator []=(key, value) {
|
||||||
|
_data[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAll(Map other) => _data.addAll(other);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() {
|
||||||
|
_data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsKey(Object key) => _data.containsKey(key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsValue(Object value) => _data.containsValue(value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void destroy() {
|
||||||
|
print('destroy() was called on a MockHttpSession, which does nothing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forEach(void Function(dynamic, dynamic) f) {
|
||||||
|
_data.forEach(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEmpty => _data.isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isNew => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isNotEmpty => _data.isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable get keys => _data.keys;
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic putIfAbsent(key, dynamic Function() ifAbsent) =>
|
||||||
|
_data.putIfAbsent(key, ifAbsent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic remove(Object key) => _data.remove(key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable get values => _data.values;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set onTimeout(void Function() callback) {
|
||||||
|
print(
|
||||||
|
'An onTimeout callback was set on a MockHttpSession, which will do nothing.');
|
||||||
|
}
|
||||||
|
}
|
13
packages/mock_request/pubspec.yaml
Normal file
13
packages/mock_request/pubspec.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: mock_request
|
||||||
|
version: 2.0.0
|
||||||
|
description: Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
homepage: https://github.com/thosakwe/mock_request
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.0.0 <3.0.0"
|
||||||
|
dependencies:
|
||||||
|
charcode: ">=1.0.0 <2.0.0"
|
||||||
|
dev_dependencies:
|
||||||
|
angel_framework: ^2.1.0
|
||||||
|
http: ^0.12.0
|
||||||
|
test: ^1.16.8
|
66
packages/mock_request/test/all_test.dart
Normal file
66
packages/mock_request/test/all_test.dart
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_framework/http.dart';
|
||||||
|
import 'package:mock_request/mock_request.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var uri = Uri.parse('http://localhost:3000');
|
||||||
|
var app = Angel()
|
||||||
|
..get('/foo', (req, res) => 'Hello, world!')
|
||||||
|
..post('/body',
|
||||||
|
(req, res) => req.parseBody().then((_) => req.bodyAsMap.length))
|
||||||
|
..get('/session', (req, res) async {
|
||||||
|
req.session['foo'] = 'bar';
|
||||||
|
})
|
||||||
|
..get('/conn', (RequestContext req, res) {
|
||||||
|
return res.serialize(req.ip == InternetAddress.loopbackIPv4.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
var http = AngelHttp(app);
|
||||||
|
|
||||||
|
test('receive a response', () async {
|
||||||
|
var rq = MockHttpRequest('GET', uri.resolve('/foo'));
|
||||||
|
await rq.close();
|
||||||
|
await http.handleRequest(rq);
|
||||||
|
var rs = rq.response;
|
||||||
|
expect(rs.statusCode, equals(200));
|
||||||
|
expect(await rs.transform(utf8.decoder).join(),
|
||||||
|
equals(json.encode('Hello, world!')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('send a body', () async {
|
||||||
|
var rq = MockHttpRequest('POST', uri.resolve('/body'));
|
||||||
|
rq
|
||||||
|
..headers.set(HttpHeaders.contentTypeHeader, ContentType.json.mimeType)
|
||||||
|
..write(json.encode({'foo': 'bar', 'bar': 'baz', 'baz': 'quux'}));
|
||||||
|
await rq.close();
|
||||||
|
await http.handleRequest(rq);
|
||||||
|
var rs = rq.response;
|
||||||
|
expect(rs.statusCode, equals(200));
|
||||||
|
expect(await rs.transform(utf8.decoder).join(), equals(json.encode(3)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('session', () async {
|
||||||
|
var rq = MockHttpRequest('GET', uri.resolve('/session'));
|
||||||
|
await rq.close();
|
||||||
|
await http.handleRequest(rq);
|
||||||
|
expect(rq.session.keys, contains('foo'));
|
||||||
|
expect(rq.session['foo'], equals('bar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connection info', () async {
|
||||||
|
var rq = MockHttpRequest('GET', uri.resolve('/conn'));
|
||||||
|
await rq.close();
|
||||||
|
await http.handleRequest(rq);
|
||||||
|
var rs = rq.response;
|
||||||
|
expect(await rs.transform(utf8.decoder).join(), equals(json.encode(true)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requested uri', () {
|
||||||
|
var rq = MockHttpRequest('GET', uri.resolve('/mock'));
|
||||||
|
expect(rq.uri.path, '/mock');
|
||||||
|
expect(rq.requestedUri.toString(), 'http://example.com/mock');
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue