diff --git a/packages/test/CHANGELOG.md b/packages/test/CHANGELOG.md index 462cbed6..978a8865 100644 --- a/packages/test/CHANGELOG.md +++ b/packages/test/CHANGELOG.md @@ -1,5 +1,13 @@ # 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 * Require Dart >= 3.0 diff --git a/packages/test/lib/src/matchers.dart b/packages/test/lib/src/matchers.dart index 7334caab..875618a0 100644 --- a/packages/test/lib/src/matchers.dart +++ b/packages/test/lib/src/matchers.dart @@ -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. Matcher hasValidBody(Validator validator) => _HasValidBody(validator); +String notHttpResponse = "expected http.Response but got none\n"; + class _IsJson extends Matcher { dynamic value; @@ -97,19 +99,35 @@ class _HasContentType extends Matcher { @override bool matches(item, Map matchState) { 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) { - var compare = ContentType.parse(item.headers['content-type']!); + var compare = ContentType.parse(headerContentType); return equals(contentType.mimeType) .matches(compare.mimeType, matchState); } else { 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()) .matches(item.headers.keys, matchState); } 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]; - return v - .map((x) => x.toString()) - .every(item.headers[key.toLowerCase()]!.split(',').contains); + return v.map((x) => x.toString()).every(headerKey.split(',').contains); } } else { 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 { @@ -161,11 +189,24 @@ class _HasStatus extends Matcher { bool matches(item, Map matchState) => item is http.Response && 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 { final Validator validator; + final _errors = []; + _HasValidBody(this.validator); @override @@ -176,11 +217,32 @@ class _HasValidBody extends Matcher { bool matches(item, Map matchState) { if (item is http.Response) { final jsons = json.decode(item.body); - if (jsons is! Map) return false; - return validator.matches(jsons, matchState); - } else { - return false; + if (jsons is Map) { + try { + return validator.matches(jsons, matchState); + } 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; } } diff --git a/packages/test/pubspec.yaml b/packages/test/pubspec.yaml index fcc3ae92..dd39ac50 100644 --- a/packages/test/pubspec.yaml +++ b/packages/test/pubspec.yaml @@ -1,5 +1,5 @@ name: angel3_test -version: 8.0.0 +version: 8.0.1 description: Testing utility library for the Angel3 framework. Use with package:test. homepage: https://angel3-framework.web.app/ repository: https://github.com/dukefirehawk/angel/tree/master/packages/test @@ -19,9 +19,9 @@ dependencies: dev_dependencies: test: ^1.24.0 lints: ^2.1.0 -# dependency_overrides: -# angel3_container: -# path: ../container/angel_container +dependency_overrides: + angel3_validate: + path: ../validate # angel3_framework: # path: ../framework # angel3_http_exception: diff --git a/packages/test/test/simple_test.dart b/packages/test/test/simple_test.dart index 431da3a4..822364d2 100644 --- a/packages/test/test/simple_test.dart +++ b/packages/test/test/simple_test.dart @@ -1,7 +1,9 @@ +import 'dart:convert'; import 'dart:io'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_container/mirrors.dart'; import 'package:angel3_test/angel3_test.dart'; +import 'package:angel3_validate/angel3_validate.dart'; import 'package:angel3_websocket/server.dart'; import 'package:test/test.dart'; @@ -12,7 +14,7 @@ void main() { setUp(() async { app = Angel(reflector: MirrorsReflector()) ..get('/hello', (req, res) => 'Hello') - ..get('/user_info', (req, res) => {'u': req.uri!.userInfo}) + ..get('/user_info', (req, res) => {'u': req.uri?.userInfo}) ..get( '/error', (req, res) => throw AngelHttpException.forbidden(message: 'Test') @@ -66,32 +68,23 @@ void main() { final response = await client.get(Uri.parse('/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 { - 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'}))); }); }); test('isAngelHttpException', () async { - var res = await client.get('/error'); + var res = await client.get(Uri.parse('/error')); print(res.body); expect(res, isAngelHttpException()); expect( res, isAngelHttpException( statusCode: 403, message: 'Test', errors: ['foo', 'bar'])); - }); + }, skip: 'This is a bug to be fixed, skip for now'); test('userInfo from Uri', () async { var url = Uri(userInfo: 'foo:bar', path: '/user_info'); @@ -106,7 +99,7 @@ void main() { var url = Uri(path: '/user_info'); print('URL: $url'); 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); var m = json.decode(res.body) as Map; @@ -114,20 +107,20 @@ void main() { }); test('hasBody', () async { - var res = await client.get('/body'); + var res = await client.get(Uri.parse('/body')); expect(res, hasBody()); expect(res, hasBody('OK')); }); 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', 'angel')); - expect(res, hasHeader('server', ['angel'])); + expect(res, hasHeader('server', 'Angel3')); + expect(res, hasHeader('server', ['Angel3'])); }); test('hasValidBody+hasContentType', () async { - var res = await client.get('/valid'); + var res = await client.get(Uri.parse('/valid')); print('Body: ${res.body}'); expect(res, hasContentType('application/json')); expect(res, hasContentType(ContentType('application', 'json'))); @@ -143,7 +136,7 @@ void main() { }); test('gzip decode', () async { - var res = await client.get('/gzip'); + var res = await client.get(Uri.parse('/gzip')); print('Body: ${res.body}'); expect(res, hasHeader('content-encoding', 'gzip')); expect(res, hasBody('Poop')); @@ -174,5 +167,4 @@ void main() { equals({'foo': 'bar'})); }); }); - */ } diff --git a/packages/validate/CHANGELOG.md b/packages/validate/CHANGELOG.md index b4d8b308..c7d69333 100644 --- a/packages/validate/CHANGELOG.md +++ b/packages/validate/CHANGELOG.md @@ -2,7 +2,7 @@ ## 8.0.1 -* Fixed null check throwing exception +* Fixed missing mismatch errors ## 8.0.0 diff --git a/packages/validate/lib/src/validator.dart b/packages/validate/lib/src/validator.dart index c7092e7f..f39470db 100644 --- a/packages/validate/lib/src/validator.dart +++ b/packages/validate/lib/src/validator.dart @@ -63,6 +63,9 @@ class Validator extends Matcher { /// Fields that must be present for data to be considered valid. final List requiredFields = []; + /// Validation error messages. + final List errorMessages = []; + void _importSchema(Map schema) { for (var keys in schema.keys) { for (var key in keys.split(',').map((s) => s.trim())) { @@ -205,7 +208,12 @@ class Validator extends Matcher { } } } catch (e) { - errors.add(e.toString()); + if (e is ValidationException) { + errors.add(e.errors.first); + } else { + errors.add(e.toString()); + } + valid = false; break; } @@ -401,7 +409,7 @@ class ValidationException extends AngelHttpException { statusCode: 400, errors: (errors).toSet().toList(), stackTrace: StackTrace.current) { - this.errors.addAll(errors.toSet()); + //this.errors.addAll(errors.toSet()); } @override