Merge pull request #112 from dukefirehawk/bug-fix/108

Bug fix/108
This commit is contained in:
Thomas 2023-11-05 17:41:48 +08:00 committed by GitHub
commit ae60a4f3e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 42 deletions

View file

@ -1,5 +1,13 @@
# Change Log # Change Log
## 8.0.1
* Fixed `hasStatus` mismatch message
* Fixed `hasContentType` mismatch message
* Fixed `hasValidBody` mismatch message
* Fixed `hasHeader` mismatch message
* Fixed `hasHeader` failed test case
## 8.0.0 ## 8.0.0
* Require Dart >= 3.0 * Require Dart >= 3.0

View file

@ -41,6 +41,8 @@ Matcher hasStatus(int status) => _HasStatus(status);
/// Expects a response to have a JSON body that is a `Map` and satisfies the given [validator] schema. /// Expects a response to have a JSON body that is a `Map` and satisfies the given [validator] schema.
Matcher hasValidBody(Validator validator) => _HasValidBody(validator); Matcher hasValidBody(Validator validator) => _HasValidBody(validator);
String notHttpResponse = "expected http.Response but got none\n";
class _IsJson extends Matcher { class _IsJson extends Matcher {
dynamic value; dynamic value;
@ -97,19 +99,35 @@ class _HasContentType extends Matcher {
@override @override
bool matches(item, Map matchState) { bool matches(item, Map matchState) {
if (item is http.Response) { if (item is http.Response) {
if (!item.headers.containsKey('content-type')) return false; //if (!item.headers.containsKey('content-type')) return false;
var headerContentType = item.headers['content-type'];
if (headerContentType == null) return false;
if (contentType is ContentType) { if (contentType is ContentType) {
var compare = ContentType.parse(item.headers['content-type']!); var compare = ContentType.parse(headerContentType);
return equals(contentType.mimeType) return equals(contentType.mimeType)
.matches(compare.mimeType, matchState); .matches(compare.mimeType, matchState);
} else { } else {
return equals(contentType.toString()) return equals(contentType.toString())
.matches(item.headers['content-type'], matchState); .matches(headerContentType, matchState);
} }
} else {
return false;
} }
return false;
}
@override
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is http.Response) {
var headerContentType = item.headers['content-type'] ?? 'none';
mismatchDescription
.add("expected '$contentType' but got '$headerContentType'\n");
} else {
mismatchDescription.add(notHttpResponse);
}
return mismatchDescription;
} }
} }
@ -135,16 +153,26 @@ class _HasHeader extends Matcher {
return contains(key.toLowerCase()) return contains(key.toLowerCase())
.matches(item.headers.keys, matchState); .matches(item.headers.keys, matchState);
} else { } else {
if (!item.headers.containsKey(key.toLowerCase())) return false; var headerKey = item.headers[key.toLowerCase()];
if (headerKey == null) return false;
var v = value is Iterable ? (value as Iterable) : [value]; var v = value is Iterable ? (value as Iterable) : [value];
return v return v.map((x) => x.toString()).every(headerKey.split(',').contains);
.map((x) => x.toString())
.every(item.headers[key.toLowerCase()]!.split(',').contains);
} }
} else { } else {
return false; return false;
} }
} }
@override
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is http.Response) {
mismatchDescription.add("expected '$key' but got none\n");
} else {
mismatchDescription.add(notHttpResponse);
}
return mismatchDescription;
}
} }
class _HasStatus extends Matcher { class _HasStatus extends Matcher {
@ -161,11 +189,24 @@ class _HasStatus extends Matcher {
bool matches(item, Map matchState) => bool matches(item, Map matchState) =>
item is http.Response && item is http.Response &&
equals(status).matches(item.statusCode, matchState); equals(status).matches(item.statusCode, matchState);
@override
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is http.Response) {
mismatchDescription.add('expected $status but got ${item.statusCode}\n');
} else {
mismatchDescription.add(notHttpResponse);
}
return mismatchDescription;
}
} }
class _HasValidBody extends Matcher { class _HasValidBody extends Matcher {
final Validator validator; final Validator validator;
final _errors = <String>[];
_HasValidBody(this.validator); _HasValidBody(this.validator);
@override @override
@ -176,11 +217,32 @@ class _HasValidBody extends Matcher {
bool matches(item, Map matchState) { bool matches(item, Map matchState) {
if (item is http.Response) { if (item is http.Response) {
final jsons = json.decode(item.body); final jsons = json.decode(item.body);
if (jsons is! Map) return false; if (jsons is Map) {
return validator.matches(jsons, matchState); try {
} else { return validator.matches(jsons, matchState);
return false; } catch (e) {
_errors.addAll((e as ValidationException).errors);
}
}
} }
return false;
}
@override
Description describeMismatch(Object? item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is http.Response) {
if (_errors.isEmpty) {
mismatchDescription.add("expected JSON but got invalid JSON\n");
} else {
for (var err in _errors) {
mismatchDescription.add("$err\n");
}
}
} else {
mismatchDescription.add(notHttpResponse);
}
return mismatchDescription;
} }
} }

View file

@ -1,5 +1,5 @@
name: angel3_test name: angel3_test
version: 8.0.0 version: 8.0.1
description: Testing utility library for the Angel3 framework. Use with package:test. description: Testing utility library for the Angel3 framework. Use with package:test.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/test repository: https://github.com/dukefirehawk/angel/tree/master/packages/test
@ -19,9 +19,9 @@ dependencies:
dev_dependencies: dev_dependencies:
test: ^1.24.0 test: ^1.24.0
lints: ^2.1.0 lints: ^2.1.0
# dependency_overrides: dependency_overrides:
# angel3_container: angel3_validate:
# path: ../container/angel_container path: ../validate
# angel3_framework: # angel3_framework:
# path: ../framework # path: ../framework
# angel3_http_exception: # angel3_http_exception:

View file

@ -1,7 +1,9 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_container/mirrors.dart'; import 'package:angel3_container/mirrors.dart';
import 'package:angel3_test/angel3_test.dart'; import 'package:angel3_test/angel3_test.dart';
import 'package:angel3_validate/angel3_validate.dart';
import 'package:angel3_websocket/server.dart'; import 'package:angel3_websocket/server.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -12,7 +14,7 @@ void main() {
setUp(() async { setUp(() async {
app = Angel(reflector: MirrorsReflector()) app = Angel(reflector: MirrorsReflector())
..get('/hello', (req, res) => 'Hello') ..get('/hello', (req, res) => 'Hello')
..get('/user_info', (req, res) => {'u': req.uri!.userInfo}) ..get('/user_info', (req, res) => {'u': req.uri?.userInfo})
..get( ..get(
'/error', '/error',
(req, res) => throw AngelHttpException.forbidden(message: 'Test') (req, res) => throw AngelHttpException.forbidden(message: 'Test')
@ -66,32 +68,23 @@ void main() {
final response = await client.get(Uri.parse('/hello')); final response = await client.get(Uri.parse('/hello'));
expect(response, isJson('Hello')); expect(response, isJson('Hello'));
}); });
});
});
/*
group('matchers', () {
group('isJson+hasStatus', () {
test('get', () async {
final response = await client.get('/hello');
expect(response, isJson('Hello'));
});
test('post', () async { test('post', () async {
final response = await client.post('/hello', body: {'foo': 'baz'}); final response =
await client.post(Uri.parse('/hello'), body: {'foo': 'baz'});
expect(response, allOf(hasStatus(200), isJson({'bar': 'baz'}))); expect(response, allOf(hasStatus(200), isJson({'bar': 'baz'})));
}); });
}); });
test('isAngelHttpException', () async { test('isAngelHttpException', () async {
var res = await client.get('/error'); var res = await client.get(Uri.parse('/error'));
print(res.body); print(res.body);
expect(res, isAngelHttpException()); expect(res, isAngelHttpException());
expect( expect(
res, res,
isAngelHttpException( isAngelHttpException(
statusCode: 403, message: 'Test', errors: ['foo', 'bar'])); statusCode: 403, message: 'Test', errors: ['foo', 'bar']));
}); }, skip: 'This is a bug to be fixed, skip for now');
test('userInfo from Uri', () async { test('userInfo from Uri', () async {
var url = Uri(userInfo: 'foo:bar', path: '/user_info'); var url = Uri(userInfo: 'foo:bar', path: '/user_info');
@ -106,7 +99,7 @@ void main() {
var url = Uri(path: '/user_info'); var url = Uri(path: '/user_info');
print('URL: $url'); print('URL: $url');
var res = await client.get(url, headers: { var res = await client.get(url, headers: {
'authorization': 'Basic ' + (base64Url.encode(utf8.encode('foo:bar'))) 'authorization': 'Basic ${base64Url.encode(utf8.encode('foo:bar'))}'
}); });
print(res.body); print(res.body);
var m = json.decode(res.body) as Map; var m = json.decode(res.body) as Map;
@ -114,20 +107,20 @@ void main() {
}); });
test('hasBody', () async { test('hasBody', () async {
var res = await client.get('/body'); var res = await client.get(Uri.parse('/body'));
expect(res, hasBody()); expect(res, hasBody());
expect(res, hasBody('OK')); expect(res, hasBody('OK'));
}); });
test('hasHeader', () async { test('hasHeader', () async {
var res = await client.get('/hello'); var res = await client.get(Uri.parse('/hello'));
expect(res, hasHeader('server')); expect(res, hasHeader('server'));
expect(res, hasHeader('server', 'angel')); expect(res, hasHeader('server', 'Angel3'));
expect(res, hasHeader('server', ['angel'])); expect(res, hasHeader('server', ['Angel3']));
}); });
test('hasValidBody+hasContentType', () async { test('hasValidBody+hasContentType', () async {
var res = await client.get('/valid'); var res = await client.get(Uri.parse('/valid'));
print('Body: ${res.body}'); print('Body: ${res.body}');
expect(res, hasContentType('application/json')); expect(res, hasContentType('application/json'));
expect(res, hasContentType(ContentType('application', 'json'))); expect(res, hasContentType(ContentType('application', 'json')));
@ -143,7 +136,7 @@ void main() {
}); });
test('gzip decode', () async { test('gzip decode', () async {
var res = await client.get('/gzip'); var res = await client.get(Uri.parse('/gzip'));
print('Body: ${res.body}'); print('Body: ${res.body}');
expect(res, hasHeader('content-encoding', 'gzip')); expect(res, hasHeader('content-encoding', 'gzip'));
expect(res, hasBody('Poop')); expect(res, hasBody('Poop'));
@ -174,5 +167,4 @@ void main() {
equals(<String, dynamic>{'foo': 'bar'})); equals(<String, dynamic>{'foo': 'bar'}));
}); });
}); });
*/
} }

View file

@ -2,7 +2,7 @@
## 8.0.1 ## 8.0.1
* Fixed null check throwing exception * Fixed missing mismatch errors
## 8.0.0 ## 8.0.0

View file

@ -63,6 +63,9 @@ class Validator extends Matcher {
/// Fields that must be present for data to be considered valid. /// Fields that must be present for data to be considered valid.
final List<String> requiredFields = []; final List<String> requiredFields = [];
/// Validation error messages.
final List<String> errorMessages = [];
void _importSchema(Map<String, dynamic> schema) { void _importSchema(Map<String, dynamic> schema) {
for (var keys in schema.keys) { for (var keys in schema.keys) {
for (var key in keys.split(',').map((s) => s.trim())) { for (var key in keys.split(',').map((s) => s.trim())) {
@ -205,7 +208,12 @@ class Validator extends Matcher {
} }
} }
} catch (e) { } catch (e) {
errors.add(e.toString()); if (e is ValidationException) {
errors.add(e.errors.first);
} else {
errors.add(e.toString());
}
valid = false; valid = false;
break; break;
} }
@ -401,7 +409,7 @@ class ValidationException extends AngelHttpException {
statusCode: 400, statusCode: 400,
errors: (errors).toSet().toList(), errors: (errors).toSet().toList(),
stackTrace: StackTrace.current) { stackTrace: StackTrace.current) {
this.errors.addAll(errors.toSet()); //this.errors.addAll(errors.toSet());
} }
@override @override