Added merge_map and mock_request packages

This commit is contained in:
thomashii 2021-03-19 07:10:19 +08:00
parent aefe1f1ab8
commit f8ec10d4e2
26 changed files with 1285 additions and 6 deletions

View file

@ -8,6 +8,9 @@
* Updated angel_route to 5.0.0
* Updated angel_model 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)
* Changed Dart SDK requirements for all packages to ">=2.10.0 <3.0.0"

View file

@ -1,5 +1,5 @@
name: angel_framework
version: 3.0.0
version: 4.0.0
description: A high-powered HTTP server with dependency injection, routing and much more.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework
@ -10,25 +10,29 @@ dependencies:
angel_container:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
ref: sdk-2.12.x_nnbd
path: packages/container/angel_container
angel_http_exception:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
ref: sdk-2.12.x_nnbd
path: packages/http_exception
angel_model:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
ref: sdk-2.12.x_nnbd
path: packages/model
angel_route:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
ref: sdk-2.12.x_nnbd
path: packages/route
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
http_parser: ^4.0.0
http_server: ^0.9.0

76
packages/merge_map/.gitignore vendored Normal file
View 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

View 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

View 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.

View 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}}
}
```

View 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}}
}

View 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;
}

View 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

View 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
View 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

View file

@ -0,0 +1 @@
language: dart

View 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

View 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.

View 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.

View file

@ -0,0 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

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

View 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';

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

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

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

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

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

View 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.');
}
}

View 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

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