Migrated range_header
This commit is contained in:
parent
1e92db46a6
commit
3b2fc97da1
11 changed files with 82 additions and 73 deletions
|
@ -26,7 +26,7 @@
|
||||||
* Migrated production to 3.0.0 (0/0 tests passed)
|
* Migrated production to 3.0.0 (0/0 tests passed)
|
||||||
* Added html_builder and migrated to 2.0.0 (1/1 tests passed)
|
* Added html_builder and migrated to 2.0.0 (1/1 tests passed)
|
||||||
* Migrated hot to 4.0.0 (0/0 tests passed)
|
* Migrated hot to 4.0.0 (0/0 tests passed)
|
||||||
* Added range_header and migrated to 2.0.0 (16/16 tests passed)
|
* Added range_header and migrated to 3.0.0 (12/12 tests passed)
|
||||||
* Updated static to 3.0.0 (in progress)
|
* Updated static to 3.0.0 (in progress)
|
||||||
* Update basic-sdk-2.12.x boilerplate (in progress)
|
* Update basic-sdk-2.12.x boilerplate (in progress)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ var file = new File('some_video.mp4');
|
||||||
handleRequest(HttpRequest request) async {
|
handleRequest(HttpRequest request) async {
|
||||||
// Parse the header
|
// Parse the header
|
||||||
var header =
|
var header =
|
||||||
new RangeHeader.parse(request.headers.value(HttpHeaders.rangeHeader));
|
new RangeHeader.parse(request.headers.value(HttpHeaders.rangeHeader)!);
|
||||||
|
|
||||||
// Optimize/canonicalize it
|
// Optimize/canonicalize it
|
||||||
var items = RangeHeader.foldItems(header.items);
|
var items = RangeHeader.foldItems(header.items);
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import 'dart:async';
|
//import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'dart:io' show HttpHeaders, HttpStatus;
|
//import 'package:angel_framework/http.dart';
|
||||||
import 'dart:typed_data';
|
//import 'package:angel_static/angel_static.dart';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_framework/http.dart';
|
|
||||||
import 'package:angel_static/angel_static.dart';
|
|
||||||
import 'package:file/file.dart';
|
|
||||||
import 'package:file/local.dart';
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:range_header/range_header.dart';
|
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
|
/*
|
||||||
var app = new Angel();
|
var app = new Angel();
|
||||||
var http = new AngelHttp(app);
|
var http = new AngelHttp(app);
|
||||||
var fs = const LocalFileSystem();
|
var fs = const LocalFileSystem();
|
||||||
|
@ -32,8 +25,9 @@ main() async {
|
||||||
app.fallback((req, res) => throw new AngelHttpException.notFound());
|
app.fallback((req, res) => throw new AngelHttpException.notFound());
|
||||||
await http.startServer('127.0.0.1', 3000);
|
await http.startServer('127.0.0.1', 3000);
|
||||||
print('Listening at ${http.uri}');
|
print('Listening at ${http.uri}');
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
class _RangingVirtualDirectory extends VirtualDirectory {
|
class _RangingVirtualDirectory extends VirtualDirectory {
|
||||||
_RangingVirtualDirectory(Angel app, Directory source)
|
_RangingVirtualDirectory(Angel app, Directory source)
|
||||||
: super(app, source.fileSystem,
|
: super(app, source.fileSystem,
|
||||||
|
@ -106,3 +100,4 @@ class _RangingVirtualDirectory extends VirtualDirectory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -16,9 +16,9 @@ class RangeHeaderTransformer
|
||||||
final int totalLength;
|
final int totalLength;
|
||||||
|
|
||||||
RangeHeaderTransformer(this.header, this.mimeType, this.totalLength,
|
RangeHeaderTransformer(this.header, this.mimeType, this.totalLength,
|
||||||
{String boundary})
|
{String? boundary})
|
||||||
: this.boundary = boundary ?? _randomString() {
|
: this.boundary = boundary ?? _randomString() {
|
||||||
if (header == null || header.items.isEmpty) {
|
if (header.items.isEmpty) {
|
||||||
throw new ArgumentError('`header` cannot be null or empty.');
|
throw new ArgumentError('`header` cannot be null or empty.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,12 @@ class RangeHeaderParseException extends FormatException {
|
||||||
@override
|
@override
|
||||||
String toString() => 'Range header parse exception: $message';
|
String toString() => 'Range header parse exception: $message';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InvalidRangeHeaderException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
InvalidRangeHeaderException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Range header parse exception: $message';
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ enum TokenType { RANGE_UNIT, COMMA, INT, DASH, EQUALS }
|
||||||
|
|
||||||
class Token {
|
class Token {
|
||||||
final TokenType type;
|
final TokenType type;
|
||||||
final SourceSpan span;
|
final SourceSpan? span;
|
||||||
|
|
||||||
Token(this.type, this.span);
|
Token(this.type, this.span);
|
||||||
}
|
}
|
||||||
|
@ -57,31 +57,31 @@ List<Token> scan(String text, List<String> allowedRangeUnits) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
Token _current;
|
Token? _current;
|
||||||
int _index = -1;
|
int _index = -1;
|
||||||
final List<Token> tokens;
|
final List<Token> tokens;
|
||||||
|
|
||||||
Parser(this.tokens);
|
Parser(this.tokens);
|
||||||
|
|
||||||
Token get current => _current;
|
Token? get current => _current;
|
||||||
|
|
||||||
bool get done => _index >= tokens.length - 1;
|
bool get done => _index >= tokens.length - 1;
|
||||||
|
|
||||||
RangeHeaderParseException _expected(String type) {
|
RangeHeaderParseException _expected(String type) {
|
||||||
int offset = current?.span?.start?.offset;
|
int? offset = current?.span?.start.offset;
|
||||||
|
|
||||||
if (offset == null) return new RangeHeaderParseException('Expected $type.');
|
if (offset == null) return new RangeHeaderParseException('Expected $type.');
|
||||||
|
|
||||||
Token peek;
|
Token? peek;
|
||||||
|
|
||||||
if (_index < tokens.length - 1) peek = tokens[_index + 1];
|
if (_index < tokens.length - 1) peek = tokens[_index + 1];
|
||||||
|
|
||||||
if (peek != null && peek.span != null) {
|
if (peek != null && peek.span != null) {
|
||||||
return new RangeHeaderParseException(
|
return new RangeHeaderParseException(
|
||||||
'Expected $type at offset $offset, found "${peek.span.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
|
'Expected $type at offset $offset, found "${peek.span!.text}" instead. \nSource:\n${peek.span?.highlight() ?? peek.type}');
|
||||||
} else
|
} else
|
||||||
return new RangeHeaderParseException(
|
return new RangeHeaderParseException(
|
||||||
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current.span?.highlight() ?? current.type}');
|
'Expected $type at offset $offset, but the header string ended without one.\nSource:\n${current!.span?.highlight() ?? current!.type}');
|
||||||
}
|
}
|
||||||
|
|
||||||
bool next(TokenType type) {
|
bool next(TokenType type) {
|
||||||
|
@ -95,13 +95,13 @@ class Parser {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeHeader parseRangeHeader() {
|
RangeHeader? parseRangeHeader() {
|
||||||
if (next(TokenType.RANGE_UNIT)) {
|
if (next(TokenType.RANGE_UNIT)) {
|
||||||
var unit = current.span.text;
|
var unit = current!.span!.text;
|
||||||
next(TokenType.EQUALS); // Consume =, if any.
|
next(TokenType.EQUALS); // Consume =, if any.
|
||||||
|
|
||||||
List<RangeHeaderItem> items = [];
|
List<RangeHeaderItem> items = [];
|
||||||
RangeHeaderItem item = parseHeaderItem();
|
RangeHeaderItem? item = parseHeaderItem();
|
||||||
|
|
||||||
while (item != null) {
|
while (item != null) {
|
||||||
items.add(item);
|
items.add(item);
|
||||||
|
@ -120,13 +120,13 @@ class Parser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeHeaderItem parseHeaderItem() {
|
RangeHeaderItem? parseHeaderItem() {
|
||||||
if (next(TokenType.INT)) {
|
if (next(TokenType.INT)) {
|
||||||
// i.e 500-544, or 600-
|
// i.e 500-544, or 600-
|
||||||
var start = int.parse(current.span.text);
|
var start = int.parse(current!.span!.text);
|
||||||
if (next(TokenType.DASH)) {
|
if (next(TokenType.DASH)) {
|
||||||
if (next(TokenType.INT)) {
|
if (next(TokenType.INT)) {
|
||||||
return new RangeHeaderItem(start, int.parse(current.span.text));
|
return new RangeHeaderItem(start, int.parse(current!.span!.text));
|
||||||
} else
|
} else
|
||||||
return new RangeHeaderItem(start);
|
return new RangeHeaderItem(start);
|
||||||
} else
|
} else
|
||||||
|
@ -134,7 +134,7 @@ class Parser {
|
||||||
} else if (next(TokenType.DASH)) {
|
} else if (next(TokenType.DASH)) {
|
||||||
// i.e. -599
|
// i.e. -599
|
||||||
if (next(TokenType.INT)) {
|
if (next(TokenType.INT)) {
|
||||||
return new RangeHeaderItem(-1, int.parse(current.span.text));
|
return new RangeHeaderItem(-1, int.parse(current!.span!.text));
|
||||||
} else
|
} else
|
||||||
throw _expected('integer');
|
throw _expected('integer');
|
||||||
} else
|
} else
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'exception.dart';
|
||||||
import 'parser.dart';
|
import 'parser.dart';
|
||||||
import 'range_header_item.dart';
|
import 'range_header_item.dart';
|
||||||
import 'range_header_impl.dart';
|
import 'range_header_impl.dart';
|
||||||
|
@ -9,12 +10,12 @@ abstract class RangeHeader {
|
||||||
UnmodifiableListView<RangeHeaderItem> get items;
|
UnmodifiableListView<RangeHeaderItem> get items;
|
||||||
|
|
||||||
const factory RangeHeader(Iterable<RangeHeaderItem> items,
|
const factory RangeHeader(Iterable<RangeHeaderItem> items,
|
||||||
{String rangeUnit}) = _ConstantRangeHeader;
|
{String? rangeUnit}) = _ConstantRangeHeader;
|
||||||
|
|
||||||
/// Eliminates any overlapping [items], sorts them, and folds them all into the most efficient representation possible.
|
/// Eliminates any overlapping [items], sorts them, and folds them all into the most efficient representation possible.
|
||||||
static UnmodifiableListView<RangeHeaderItem> foldItems(
|
static UnmodifiableListView<RangeHeaderItem> foldItems(
|
||||||
Iterable<RangeHeaderItem> items) {
|
Iterable<RangeHeaderItem> items) {
|
||||||
var out = new Set<RangeHeaderItem>();
|
var out = Set<RangeHeaderItem>();
|
||||||
|
|
||||||
for (var item in items) {
|
for (var item in items) {
|
||||||
// Remove any overlapping items, consolidate them.
|
// Remove any overlapping items, consolidate them.
|
||||||
|
@ -37,27 +38,30 @@ abstract class RangeHeader {
|
||||||
///
|
///
|
||||||
/// If [fold] is `true`, the items will be folded into the most compact
|
/// If [fold] is `true`, the items will be folded into the most compact
|
||||||
/// possible representation.
|
/// possible representation.
|
||||||
|
///
|
||||||
factory RangeHeader.parse(String text,
|
factory RangeHeader.parse(String text,
|
||||||
{Iterable<String> allowedRangeUnits, bool fold: true}) {
|
{Iterable<String>? allowedRangeUnits, bool fold: true}) {
|
||||||
var tokens = scan(text, allowedRangeUnits?.toList() ?? ['bytes']);
|
var tokens = scan(text, allowedRangeUnits?.toList() ?? ['bytes']);
|
||||||
var parser = new Parser(tokens);
|
var parser = new Parser(tokens);
|
||||||
var header = parser.parseRangeHeader();
|
var header = parser.parseRangeHeader();
|
||||||
if (header == null) return null;
|
if (header == null) {
|
||||||
|
throw InvalidRangeHeaderException('Header is null');
|
||||||
|
}
|
||||||
var items = foldItems(header.items);
|
var items = foldItems(header.items);
|
||||||
return RangeHeaderImpl(header.rangeUnit, items);
|
return RangeHeaderImpl(header.rangeUnit, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this header's range unit. Most commonly, this is `bytes`.
|
/// Returns this header's range unit. Most commonly, this is `bytes`.
|
||||||
String get rangeUnit;
|
String? get rangeUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConstantRangeHeader implements RangeHeader {
|
class _ConstantRangeHeader implements RangeHeader {
|
||||||
final Iterable<RangeHeaderItem> items_;
|
final Iterable<RangeHeaderItem> items_;
|
||||||
final String rangeUnit;
|
final String? rangeUnit;
|
||||||
|
|
||||||
const _ConstantRangeHeader(this.items_, {this.rangeUnit: 'bytes'});
|
const _ConstantRangeHeader(this.items_, {this.rangeUnit: 'bytes'});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
UnmodifiableListView<RangeHeaderItem> get items =>
|
UnmodifiableListView<RangeHeaderItem> get items =>
|
||||||
new UnmodifiableListView(items_);
|
UnmodifiableListView(items_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import 'range_header_item.dart';
|
||||||
|
|
||||||
/// Represents the contents of a parsed `Range` header.
|
/// Represents the contents of a parsed `Range` header.
|
||||||
class RangeHeaderImpl implements RangeHeader {
|
class RangeHeaderImpl implements RangeHeader {
|
||||||
UnmodifiableListView<RangeHeaderItem> _cached;
|
UnmodifiableListView<RangeHeaderItem>? _cached;
|
||||||
final List<RangeHeaderItem> _items = [];
|
final List<RangeHeaderItem> _items = [];
|
||||||
|
|
||||||
RangeHeaderImpl(this.rangeUnit, [List<RangeHeaderItem> items = const []]) {
|
RangeHeaderImpl(this.rangeUnit, [List<RangeHeaderItem> items = const []]) {
|
||||||
this._items.addAll(items ?? []);
|
this._items.addAll(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -16,5 +16,5 @@ class RangeHeaderImpl implements RangeHeader {
|
||||||
_cached ??= new UnmodifiableListView<RangeHeaderItem>(_items);
|
_cached ??= new UnmodifiableListView<RangeHeaderItem>(_items);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String rangeUnit;
|
final String? rangeUnit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ class RangeHeaderItem implements Comparable<RangeHeaderItem> {
|
||||||
/// Please adhere to the standard!!!
|
/// Please adhere to the standard!!!
|
||||||
/// http://httpwg.org/specs/rfc7233.html
|
/// http://httpwg.org/specs/rfc7233.html
|
||||||
|
|
||||||
String toContentRange([int totalSize]) {
|
String toContentRange([int? totalSize]) {
|
||||||
// var maxIndex = totalSize != null ? (totalSize - 1).toString() : '*';
|
// var maxIndex = totalSize != null ? (totalSize - 1).toString() : '*';
|
||||||
var s = start > -1 ? start : 0;
|
var s = start > -1 ? start : 0;
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
name: range_header
|
name: range_header
|
||||||
version: 2.0.2+2
|
version: 3.0.0
|
||||||
description: Range header parser for Dart. Beyond parsing, a stream transformer is included.
|
description: Range header parser for Dart. Beyond parsing, a stream transformer is included.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/thosakwe/range_header
|
homepage: https://github.com/thosakwe/range_header
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.0.0-dev <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
async: ^2.0.0
|
async: ^2.6.0
|
||||||
charcode: ^1.0.0
|
charcode: ^1.2.0
|
||||||
quiver_hashcode: ^2.0.0
|
quiver_hashcode: ^3.0.0+1
|
||||||
source_span: ^1.0.0
|
source_span: ^1.8.1
|
||||||
string_scanner: ^1.0.0
|
string_scanner: ^1.1.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_framework:
|
#angel_framework:
|
||||||
angel_static: ^2.0.0
|
#angel_static: ^2.0.0
|
||||||
file:
|
file: ^6.1.0
|
||||||
http_parser: ^3.0.0
|
http_parser: ^4.0.0
|
||||||
logging: ^0.11.0
|
logging: ^1.0.1
|
||||||
test: ^1.0.0
|
test: ^1.17.3
|
|
@ -1,27 +1,32 @@
|
||||||
import 'package:range_header/range_header.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../lib/range_header.dart';
|
||||||
|
import '../lib/src/range_header.dart';
|
||||||
|
|
||||||
final Matcher throwsRangeParseException =
|
final Matcher throwsRangeParseException =
|
||||||
throwsA(const TypeMatcher<RangeHeaderParseException>());
|
throwsA(const TypeMatcher<RangeHeaderParseException>());
|
||||||
|
|
||||||
|
final Matcher throwsInvalidRangeHeaderException =
|
||||||
|
throwsA(const TypeMatcher<InvalidRangeHeaderException>());
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
group('one item', () {
|
group('one item', () {
|
||||||
test('start and end', () {
|
test('start and end', () {
|
||||||
var r = new RangeHeader.parse('bytes 1-200');
|
var r = RangeHeader.parse('bytes 1-200');
|
||||||
expect(r.items, hasLength(1));
|
expect(r.items, hasLength(1));
|
||||||
expect(r.items.first.start, 1);
|
expect(r.items.first.start, 1);
|
||||||
expect(r.items.first.end, 200);
|
expect(r.items.first.end, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('start only', () {
|
test('start only', () {
|
||||||
var r = new RangeHeader.parse('bytes 1-');
|
var r = RangeHeader.parse('bytes 1-');
|
||||||
expect(r.items, hasLength(1));
|
expect(r.items, hasLength(1));
|
||||||
expect(r.items.first.start, 1);
|
expect(r.items.first.start, 1);
|
||||||
expect(r.items.first.end, -1);
|
expect(r.items.first.end, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('end only', () {
|
test('end only', () {
|
||||||
var r = new RangeHeader.parse('bytes -200');
|
var r = RangeHeader.parse('bytes -200');
|
||||||
print(r.items);
|
print(r.items);
|
||||||
expect(r.items, hasLength(1));
|
expect(r.items, hasLength(1));
|
||||||
expect(r.items.first.start, -1);
|
expect(r.items.first.start, -1);
|
||||||
|
@ -31,7 +36,7 @@ main() {
|
||||||
|
|
||||||
group('multiple items', () {
|
group('multiple items', () {
|
||||||
test('three items', () {
|
test('three items', () {
|
||||||
var r = new RangeHeader.parse('bytes 1-20, 21-40, 41-60');
|
var r = RangeHeader.parse('bytes 1-20, 21-40, 41-60');
|
||||||
print(r.items);
|
print(r.items);
|
||||||
expect(r.items, hasLength(3));
|
expect(r.items, hasLength(3));
|
||||||
expect(r.items[0].start, 1);
|
expect(r.items[0].start, 1);
|
||||||
|
@ -43,7 +48,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('one item without end', () {
|
test('one item without end', () {
|
||||||
var r = new RangeHeader.parse('bytes 1-20, 21-');
|
var r = RangeHeader.parse('bytes 1-20, 21-');
|
||||||
print(r.items);
|
print(r.items);
|
||||||
expect(r.items, hasLength(2));
|
expect(r.items, hasLength(2));
|
||||||
expect(r.items[0].start, 1);
|
expect(r.items[0].start, 1);
|
||||||
|
@ -55,42 +60,38 @@ main() {
|
||||||
|
|
||||||
group('failures', () {
|
group('failures', () {
|
||||||
test('no start with no end', () {
|
test('no start with no end', () {
|
||||||
expect(new RangeHeader.parse('-'), isNull);
|
expect(() => RangeHeader.parse('-'), throwsInvalidRangeHeaderException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('exceptions', () {
|
group('exceptions', () {
|
||||||
test('invalid character', () {
|
test('invalid character', () {
|
||||||
expect(() => new RangeHeader.parse('!!!'), throwsRangeParseException);
|
expect(() => RangeHeader.parse('!!!'), throwsRangeParseException);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no ranges', () {
|
test('no ranges', () {
|
||||||
expect(() => new RangeHeader.parse('bytes'), throwsRangeParseException);
|
expect(() => RangeHeader.parse('bytes'), throwsRangeParseException);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no dash after int', () {
|
test('no dash after int', () {
|
||||||
expect(() => new RangeHeader.parse('bytes 3'), throwsRangeParseException);
|
expect(() => RangeHeader.parse('bytes 3'), throwsRangeParseException);
|
||||||
expect(
|
expect(() => RangeHeader.parse('bytes 3,'), throwsRangeParseException);
|
||||||
() => new RangeHeader.parse('bytes 3,'), throwsRangeParseException);
|
expect(() => RangeHeader.parse('bytes 3 24'), throwsRangeParseException);
|
||||||
expect(
|
|
||||||
() => new RangeHeader.parse('bytes 3 24'), throwsRangeParseException);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no int after dash', () {
|
test('no int after dash', () {
|
||||||
expect(
|
expect(() => RangeHeader.parse('bytes -,'), throwsRangeParseException);
|
||||||
() => new RangeHeader.parse('bytes -,'), throwsRangeParseException);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('complete coverage', () {
|
group('complete coverage', () {
|
||||||
test('exception toString()', () {
|
test('exception toString()', () {
|
||||||
var m = new RangeHeaderParseException('hey');
|
var m = RangeHeaderParseException('hey');
|
||||||
expect(m.toString(), contains('hey'));
|
expect(m.toString(), contains('hey'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('content-range', () {
|
test('content-range', () {
|
||||||
expect(
|
expect(RangeHeader.parse('bytes 1-2').items[0].toContentRange(3), '1-2/3');
|
||||||
new RangeHeader.parse('bytes 1-2').items[0].toContentRange(3), '1-2/3');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue