From 2b6bb6121a412bb482cda41a0a92ccd404687739 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 29 Dec 2022 10:02:15 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improve=20tests=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove redundant `#test` with namings. - `assert\((.*)\, (.*)\)\;` -> `expect($1, $2);`. - Add a 1-second delay for each `httpbin.org` test. --- dio/test/basic_test.dart | 45 ++--- dio/test/dio_mixin_test.dart | 2 +- dio/test/download_test.dart | 16 +- dio/test/encoding_test.dart | 16 +- dio/test/exception_test.dart | 25 +-- dio/test/formdata_test.dart | 4 +- dio/test/interceptor_test.dart | 37 ++-- dio/test/mimetype_test.dart | 10 +- dio/test/mock/adapters.dart | 5 +- dio/test/options_test.dart | 107 +++++------ dio/test/pinning_test.dart | 14 +- dio/test/readtimeout_test.dart | 6 +- dio/test/request_test.dart | 38 ++-- dio/test/upload_stream_test.dart | 14 +- .../http2_adapter/lib/src/http2_adapter.dart | 2 +- plugins/http2_adapter/test/http2_test.dart | 166 ---------------- plugins/http2_adapter/test/pinning_test.dart | 178 ++++++++++++++++++ scripts/prepare_pinning_certs.sh | 3 + 18 files changed, 357 insertions(+), 331 deletions(-) create mode 100644 plugins/http2_adapter/test/pinning_test.dart diff --git a/dio/test/basic_test.dart b/dio/test/basic_test.dart index 5bfee09ae..fa7a5d264 100644 --- a/dio/test/basic_test.dart +++ b/dio/test/basic_test.dart @@ -1,53 +1,54 @@ +@TestOn('vm') import 'dart:async'; import 'dart:io'; import 'package:diox/diox.dart'; import 'package:test/test.dart'; +import 'mock/adapters.dart'; + void main() { - test('#test headers', () { + test('test headers', () { final headers = Headers.fromMap({ 'set-cookie': ['k=v', 'k1=v1'], 'content-length': ['200'], 'test': ['1', '2'], }); headers.add('SET-COOKIE', 'k2=v2'); - assert(headers.value('content-length') == '200'); + expect(headers.value('content-length'), '200'); expect(Future(() => headers.value('test')), throwsException); - assert(headers['set-cookie']?.length == 3); + expect(headers['set-cookie']?.length, 3); headers.remove('set-cookie', 'k=v'); - assert(headers['set-cookie']?.length == 2); + expect(headers['set-cookie']?.length, 2); headers.removeAll('set-cookie'); - assert(headers['set-cookie'] == null); + expect(headers['set-cookie'], isNull); final ls = []; - headers.forEach((k, list) { - ls.addAll(list); - }); - assert(ls.length == 3); - assert(headers.toString() == 'content-length: 200\ntest: 1\ntest: 2\n'); + headers.forEach((k, list) => ls.addAll(list)); + expect(ls.length, 3); + expect(headers.toString(), 'content-length: 200\ntest: 1\ntest: 2\n'); headers.set('content-length', '300'); - assert(headers.value('content-length') == '300'); + expect(headers.value('content-length'), '300'); headers.set('content-length', ['400']); - assert(headers.value('content-length') == '400'); + expect(headers.value('content-length'), '400'); final headers1 = Headers(); headers1.set('xx', 'v'); - assert(headers1.value('xx') == 'v'); + expect(headers1.value('xx'), 'v'); headers1.clear(); - assert(headers1.map.isEmpty == true); + expect(headers1.map.isEmpty, isTrue); }); - test('#send with an invalid URL', () async { + test('send with an invalid URL', () async { await expectLater( Dio().get('http://http.invalid'), throwsA((e) => e is DioError && e.error is SocketException), ); }, testOn: "vm"); - test('#cancellation', () async { + test('cancellation', () async { final dio = Dio(); final token = CancelToken(); - Timer(Duration(milliseconds: 10), () { + Future.delayed(const Duration(milliseconds: 10), () { token.cancel('cancelled'); dio.httpClientAdapter.close(force: true); }); @@ -59,17 +60,19 @@ void main() { ); }); - test('#status error', () async { - final dio = Dio()..options.baseUrl = 'https://httpbin.org/status/'; + test('status error', () async { + final dio = Dio() + ..options.baseUrl = EchoAdapter.mockBase + ..httpClientAdapter = EchoAdapter(); await expectLater( - dio.get('401'), + dio.get('/401'), throwsA((e) => e is DioError && e.type == DioErrorType.badResponse && e.response!.statusCode == 401), ); final r = await dio.get( - '401', + '/401', options: Options(validateStatus: (status) => true), ); expect(r.statusCode, 401); diff --git a/dio/test/dio_mixin_test.dart b/dio/test/dio_mixin_test.dart index c1eaee9a3..dc78c52a7 100644 --- a/dio/test/dio_mixin_test.dart +++ b/dio/test/dio_mixin_test.dart @@ -2,7 +2,7 @@ import 'package:diox/diox.dart'; import 'package:test/test.dart'; void main() { - test('#assureResponse', () { + test('assureResponse', () { final untypedResponse = Response( requestOptions: RequestOptions(path: ''), data: null, diff --git a/dio/test/download_test.dart b/dio/test/download_test.dart index e43b2e037..6cb5abf42 100644 --- a/dio/test/download_test.dart +++ b/dio/test/download_test.dart @@ -10,7 +10,7 @@ import 'utils.dart'; void main() { setUp(startServer); tearDown(stopServer); - test('#test download1', () async { + test('download1', () async { const savePath = 'test/_download_test.md'; final dio = Dio(); dio.options.baseUrl = serverUrl.toString(); @@ -26,7 +26,7 @@ void main() { f.deleteSync(recursive: false); }); - test('#test download2', () async { + test('download2', () async { const savePath = 'test/_download_test.md'; final dio = Dio(); dio.options.baseUrl = serverUrl.toString(); @@ -40,14 +40,14 @@ void main() { f.deleteSync(recursive: false); }); - test('#test download error', () async { + test('download error', () async { const savePath = 'test/_download_test.md'; final dio = Dio(); dio.options.baseUrl = serverUrl.toString(); Response response = await dio .download('/error', savePath) .catchError((e) => (e as DioError).response!); - assert(response.data == 'error'); + expect(response.data, 'error'); response = await dio .download( '/error', @@ -55,10 +55,10 @@ void main() { options: Options(receiveDataWhenStatusError: false), ) .catchError((e) => (e as DioError).response!); - assert(response.data == null); + expect(response.data, null); }); - test('#test download timeout', () async { + test('download timeout', () async { const savePath = 'test/_download_test.md'; final dio = Dio(BaseOptions( receiveTimeout: Duration(milliseconds: 1), @@ -72,7 +72,7 @@ void main() { //print(r); }); - test('#test download cancellation', () async { + test('download cancellation', () async { const savePath = 'test/_download_test.md'; final cancelToken = CancelToken(); Future.delayed(Duration(milliseconds: 100), () { @@ -91,7 +91,7 @@ void main() { //print(r); }); - test('Test `savePath` types', () async { + test('`savePath` types', () async { Object? error; final dio = Dio() ..options.baseUrl = EchoAdapter.mockBase diff --git a/dio/test/encoding_test.dart b/dio/test/encoding_test.dart index 678c8d054..27b362812 100644 --- a/dio/test/encoding_test.dart +++ b/dio/test/encoding_test.dart @@ -13,45 +13,45 @@ void main() { } } }; - test('#url encode default ', () { + test('default ', () { // a=你好&b=5&b=6&c[d]=8&c[e][a]=5&c[e][b]=66&c[e][b]=8 final result = 'a=%E4%BD%A0%E5%A5%BD&b=5&b=6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66&c%5Be%5D%5Bb%5D=8'; expect(Transformer.urlEncodeMap(data), result); }); - test('#url encode csv', () { + test('csv', () { // a=你好&b=5,6&c[d]=8&c[e][a]=5&c[e][b]=66,8 final result = 'a=%E4%BD%A0%E5%A5%BD&b=5%2C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%2C8'; expect(Transformer.urlEncodeMap(data, ListFormat.csv), result); }); - test('#url encode ssv', () { + test('ssv', () { // a=你好&b=5+6&c[d]=8&c[e][a]=5&c[e][b]=66+8 final result = 'a=%E4%BD%A0%E5%A5%BD&b=5+6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66+8'; expect(Transformer.urlEncodeMap(data, ListFormat.ssv), result); }); - test('#url encode tsv', () { + test('tsv', () { // a=你好&b=5\t6&c[d]=8&c[e][a]=5&c[e][b]=66\t8 final result = 'a=%E4%BD%A0%E5%A5%BD&b=5%5Ct6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%5Ct8'; expect(Transformer.urlEncodeMap(data, ListFormat.tsv), result); }); - test('#url encode pipe', () { + test('pipe', () { //a=你好&b=5|6&c[d]=8&c[e][a]=5&c[e][b]=66|8 final result = 'a=%E4%BD%A0%E5%A5%BD&b=5%7C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%7C8'; expect(Transformer.urlEncodeMap(data, ListFormat.pipes), result); }); - test('#url encode multi', () { + test('multi', () { //a=你好&b[]=5&b[]=6&c[d]=8&c[e][a]=5&c[e][b][]=66&c[e][b][]=8 final result = 'a=%E4%BD%A0%E5%A5%BD&b%5B%5D=5&b%5B%5D=6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D%5B%5D=66&c%5Be%5D%5Bb%5D%5B%5D=8'; expect(Transformer.urlEncodeMap(data, ListFormat.multiCompatible), result); }); - test('#url encode multi2', () { + test('multi2', () { final data = { 'a': 'string', 'b': 'another_string', @@ -62,7 +62,7 @@ void main() { expect(Transformer.urlEncodeMap(data, ListFormat.multiCompatible), result); }); - test('#url encode custom', () { + test('custom', () { //a=你好&b=5|6&c[d]=8&c[e][a]=5&c[e][b]=foo,bar&c[e][c]=foo+bar&c[e][d][]=foo&c[e][d][]=bar&c[e][e]=foo\tbar final result = 'a=%E4%BD%A0%E5%A5%BD&b=5%7C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=foo%2Cbar&c%5Be%5D%5Bc%5D=foo+bar&c%5Be%5D%5Bd%5D%5B%5D=foo&c%5Be%5D%5Bd%5D%5B%5D=bar&c%5Be%5D%5Be%5D=foo%5Ctbar'; diff --git a/dio/test/exception_test.dart b/dio/test/exception_test.dart index 514f416a1..6327ff829 100644 --- a/dio/test/exception_test.dart +++ b/dio/test/exception_test.dart @@ -1,39 +1,35 @@ +@TestOn('vm') +import 'dart:io'; + import 'package:diox/diox.dart'; import 'package:diox/io.dart'; import 'package:test/test.dart'; void main() { test('catch DioError', () async { - dynamic error; - + DioError? error; try { await Dio().get('https://does.not.exist'); fail('did not throw'); } on DioError catch (e) { error = e; } - expect(error, isNotNull); - expect(error is DioError, isTrue); }); test('catch DioError as Exception', () async { - dynamic error; - + DioError? error; try { await Dio().get('https://does.not.exist'); fail('did not throw'); } on DioError catch (e) { error = e; } - expect(error, isNotNull); - expect(error is DioError, isTrue); }); - test('catch sslerror: hostname mismatch', () async { - dynamic error; - + test('catch DioError: hostname mismatch', () async { + DioError? error; try { await Dio().get('https://wrong.host.badssl.com/'); fail('did not throw'); @@ -41,7 +37,12 @@ void main() { error = e; } expect(error, isNotNull); - expect(error is DioError, isTrue); + expect(error.error, isA()); + expect((error.error as HandshakeException).osError, isNotNull); + expect( + ((error.error as HandshakeException).osError as OSError).message, + contains('Hostname mismatch'), + ); }); test('allow badssl', () async { diff --git a/dio/test/formdata_test.dart b/dio/test/formdata_test.dart index c6e0638ce..c349b343a 100644 --- a/dio/test/formdata_test.dart +++ b/dio/test/formdata_test.dart @@ -6,7 +6,7 @@ import 'package:diox/diox.dart'; import 'package:test/test.dart'; void main() async { - test('#test FormData', () async { + test('FormData', () async { final fm = FormData.fromMap({ 'name': 'wendux', 'age': 25, @@ -67,6 +67,6 @@ void main() async { 'test': ['c'] }), )); - assert(fmStr.length == fm1.length); + expect(fmStr.length, fm1.length); }); } diff --git a/dio/test/interceptor_test.dart b/dio/test/interceptor_test.dart index 5f4c8cbad..d4d6d5019 100644 --- a/dio/test/interceptor_test.dart +++ b/dio/test/interceptor_test.dart @@ -16,11 +16,9 @@ class MyInterceptor extends Interceptor { } void main() { - group('#test Request Interceptor', () { - Dio dio; - - test('#test interceptor chain', () async { - dio = Dio(); + group('Request Interceptor', () { + test('interceptor chain', () async { + final dio = Dio(); dio.options.baseUrl = EchoAdapter.mockBase; dio.httpClientAdapter = EchoAdapter(); dio.interceptors @@ -153,19 +151,19 @@ void main() { }, )); Response response = await dio.get('/resolve'); - assert(response.data == 1); + expect(response.data, 1); response = await dio.get('/resolve-next'); - assert(response.data == 3); + expect(response.data, 3); response = await dio.get('/resolve-next/always'); - assert(response.data == 4); + expect(response.data, 4); response = await dio.post('/post', data: 'xxx'); - assert(response.data == 'xxx'); + expect(response.data, 'xxx'); response = await dio.get('/reject-next-response'); - assert(response.data == 100); + expect(response.data, 100); expect( dio.get('/reject').catchError((e) => throw e.error as num), @@ -226,8 +224,8 @@ void main() { ); }); - test('#test request interceptor', () async { - dio = Dio(); + test('request interceptor', () async { + final dio = Dio(); dio.options.baseUrl = MockAdapter.mockBase; dio.httpClientAdapter = MockAdapter(); dio.interceptors.add(InterceptorsWrapper(onRequest: ( @@ -304,9 +302,9 @@ void main() { }); }); - group('#test response interceptor', () { + group('response interceptor', () { Dio dio; - test('#test Response Interceptor', () async { + test('Response Interceptor', () async { const urlNotFound = '/404/'; const urlNotFound1 = '${urlNotFound}1'; const urlNotFound2 = '${urlNotFound}2'; @@ -397,8 +395,9 @@ void main() { expect(resp.data['extra_2'], 'extra'); }); }); - group('# test queued interceptors', () { - test('test queued interceptor for requests ', () async { + + group('QueuedInterceptor', () { + test('requests ', () async { String? csrfToken; final dio = Dio(); int tokenRequestCounts = 0; @@ -438,13 +437,13 @@ void main() { ]); expect(tokenRequestCounts, 1); expect(result, 3); - assert(myInter.requestCount > 0); + expect(myInter.requestCount, predicate((int e) => e > 0)); dio.interceptors[0] = myInter; dio.interceptors.clear(); - assert(dio.interceptors.isEmpty == true); + expect(dio.interceptors.isEmpty, true); }); - test('test queued interceptors for error', () async { + test('error', () async { String? csrfToken; final dio = Dio(); int tokenRequestCounts = 0; diff --git a/dio/test/mimetype_test.dart b/dio/test/mimetype_test.dart index b0de5d7f8..72b6b108c 100644 --- a/dio/test/mimetype_test.dart +++ b/dio/test/mimetype_test.dart @@ -2,16 +2,18 @@ import 'package:diox/diox.dart'; import 'package:test/test.dart'; void main() { - test('JSON MimeType "application/json" ', () { + test('application/json', () { expect(Transformer.isJsonMimeType("application/json"), isTrue); }); - test('JSON MimeType "text/json" ', () { + test('text/json', () { expect(Transformer.isJsonMimeType("text/json"), isTrue); }); - test('JSON MimeType "application/vnd.example.com+json" ', () { + test('application/vnd.example.com+json', () { expect( - Transformer.isJsonMimeType("application/vnd.example.com+json"), isTrue); + Transformer.isJsonMimeType("application/vnd.example.com+json"), + isTrue, + ); }); } diff --git a/dio/test/mock/adapters.dart b/dio/test/mock/adapters.dart index a60f8a6f1..b83b0bfc1 100644 --- a/dio/test/mock/adapters.dart +++ b/dio/test/mock/adapters.dart @@ -116,10 +116,11 @@ class EchoAdapter implements HttpClientAdapter { ) async { final Uri uri = options.uri; if (uri.host == mockHost) { + final statusCode = int.tryParse(uri.path.replaceFirst('/', '')) ?? 200; if (requestStream != null) { - return ResponseBody(requestStream, 200); + return ResponseBody(requestStream, statusCode); } else { - return ResponseBody.fromString(uri.path, 200); + return ResponseBody.fromString(uri.path, statusCode); } } return _adapter.fetch(options, requestStream, cancelFuture); diff --git a/dio/test/options_test.dart b/dio/test/options_test.dart index 3c63fddc6..c0bc18dc8 100644 --- a/dio/test/options_test.dart +++ b/dio/test/options_test.dart @@ -7,7 +7,7 @@ import 'package:test/test.dart'; import 'mock/adapters.dart'; void main() { - test('#test options', () { + test('options', () { final map = {'a': '5'}; final mapOverride = {'b': '6'}; final baseOptions = BaseOptions( @@ -31,16 +31,16 @@ void main() { headers: mapOverride, contentType: 'text/html', ); - assert(opt1.method == 'post'); - assert(opt1.receiveTimeout == Duration(seconds: 3)); - assert(opt1.connectTimeout == Duration(seconds: 2)); - assert(opt1.followRedirects == false); - assert(opt1.persistentConnection == false); - assert(opt1.baseUrl == 'https://pub.dev'); - assert(opt1.headers['b'] == '6'); - assert(opt1.extra['b'] == '6'); - assert(opt1.queryParameters['b'] == null); - assert(opt1.contentType == 'text/html'); + expect(opt1.method, 'post'); + expect(opt1.receiveTimeout, Duration(seconds: 3)); + expect(opt1.connectTimeout, Duration(seconds: 2)); + expect(opt1.followRedirects, false); + expect(opt1.persistentConnection, false); + expect(opt1.baseUrl, 'https://pub.dev'); + expect(opt1.headers['b'], '6'); + expect(opt1.extra['b'], '6'); + expect(opt1.queryParameters['b'], null); + expect(opt1.contentType, 'text/html'); final opt2 = Options( method: 'get', @@ -62,13 +62,13 @@ void main() { contentType: 'text/html', ); - assert(opt3.method == 'post'); - assert(opt3.receiveTimeout == Duration(seconds: 3)); - assert(opt3.followRedirects == false); - assert(opt3.persistentConnection == false); - assert(opt3.headers!['b'] == '6'); - assert(opt3.extra!['b'] == '6'); - assert(opt3.contentType == 'text/html'); + expect(opt3.method, 'post'); + expect(opt3.receiveTimeout, Duration(seconds: 3)); + expect(opt3.followRedirects, false); + expect(opt3.persistentConnection, false); + expect(opt3.headers!['b'], '6'); + expect(opt3.extra!['b'], '6'); + expect(opt3.contentType, 'text/html'); final opt4 = RequestOptions( path: '/xxx', @@ -86,22 +86,22 @@ void main() { path: '/', contentType: 'text/html', ); - assert(opt5.method == 'post'); - assert(opt5.receiveTimeout == Duration(seconds: 3)); - assert(opt5.followRedirects == false); - assert(opt5.persistentConnection == false); - assert(opt5.contentType == 'text/html'); - assert(opt5.headers['b'] == '6'); - assert(opt5.extra['b'] == '6'); - assert(opt5.data == 'xx=5'); - assert(opt5.path == '/'); + expect(opt5.method, 'post'); + expect(opt5.receiveTimeout, Duration(seconds: 3)); + expect(opt5.followRedirects, false); + expect(opt5.persistentConnection, false); + expect(opt5.contentType, 'text/html'); + expect(opt5.headers['b'], '6'); + expect(opt5.extra['b'], '6'); + expect(opt5.data, 'xx=5'); + expect(opt5.path, '/'); // Keys of header are case-insensitive expect(opt5.headers['B'], '6'); opt5.headers['B'] = 9; - assert(opt5.headers['b'] == 9); + expect(opt5.headers['b'], 9); }); - test('#test options content-type', () { + test('options content-type', () { const contentType = 'text/html'; const contentTypeJson = 'appliction/json'; final headers = {'content-type': contentType}; @@ -109,7 +109,7 @@ void main() { try { BaseOptions(contentType: contentType, headers: headers); - assert(false, 'baseOptions1'); + expect(false, 'baseOptions1'); } catch (e) { // } @@ -118,20 +118,20 @@ void main() { final bo2 = BaseOptions(headers: headers); final bo3 = BaseOptions(); - assert(bo1.headers['content-type'] == contentType); - assert(bo2.headers['content-type'] == contentType); - assert(bo3.headers['content-type'] == null); + expect(bo1.headers['content-type'], contentType); + expect(bo2.headers['content-type'], contentType); + expect(bo3.headers['content-type'], null); try { bo1.copyWith(headers: headers); - assert(false, 'baseOptions copyWith 1'); + expect(false, 'baseOptions copyWith 1'); } catch (e) { // } try { bo2.copyWith(contentType: contentType); - assert(false, 'baseOptions copyWith 2'); + expect(false, 'baseOptions copyWith 2'); } catch (e) { // } @@ -141,7 +141,7 @@ void main() { /// options try { Options(contentType: contentType, headers: headers); - assert(false, 'Options1'); + expect(false, 'Options1'); } catch (e) { // } @@ -151,14 +151,14 @@ void main() { try { o1.copyWith(headers: headers); - assert(false, 'Options copyWith 1'); + expect(false, 'Options copyWith 1'); } catch (e) { // } try { o2.copyWith(contentType: contentType); - assert(false, 'Options copyWith 2'); + expect(false, 'Options copyWith 2'); } catch (e) { // } @@ -191,7 +191,7 @@ void main() { /// RequestOptions try { RequestOptions(path: '', contentType: contentType, headers: headers); - assert(false, 'Options1'); + expect(false, 'Options1'); } catch (e) { // } @@ -201,14 +201,14 @@ void main() { try { ro1.copyWith(headers: headers); - assert(false, 'RequestOptions copyWith 1'); + expect(false, 'RequestOptions copyWith 1'); } catch (e) { // } try { ro2.copyWith(contentType: contentType); - assert(false, 'RequestOptions copyWith 2'); + expect(false, 'RequestOptions copyWith 2'); } catch (e) { // } @@ -217,7 +217,7 @@ void main() { ro3.copyWith(); }); - test('#test default content-type', () async { + test('default content-type', () async { final dio = Dio(); dio.options.baseUrl = EchoAdapter.mockBase; dio.httpClientAdapter = EchoAdapter(); @@ -310,7 +310,7 @@ void main() { ); }); - test('#test default content-type 2', () async { + test('default content-type 2', () async { final dio = Dio(); dio.options.setRequestContentTypeWhenNoPayload = true; dio.options.baseUrl = 'https://www.example.com'; @@ -318,15 +318,17 @@ void main() { final r1 = Options(method: 'GET').compose(dio.options, '/test').copyWith( headers: {Headers.contentTypeHeader: Headers.textPlainContentType}, ); - assert( - r1.headers[Headers.contentTypeHeader] == Headers.textPlainContentType, + expect( + r1.headers[Headers.contentTypeHeader], + Headers.textPlainContentType, ); final r2 = Options(method: 'GET') .compose(dio.options, '/test') .copyWith(contentType: Headers.textPlainContentType); - assert( - r2.headers[Headers.contentTypeHeader] == Headers.textPlainContentType, + expect( + r2.headers[Headers.contentTypeHeader], + Headers.textPlainContentType, ); try { @@ -334,16 +336,15 @@ void main() { headers: {Headers.contentTypeHeader: Headers.textPlainContentType}, contentType: Headers.formUrlEncodedContentType, ); - assert(false); } catch (_) {} dio.options.setRequestContentTypeWhenNoPayload = false; final r3 = Options(method: 'GET').compose(dio.options, '/test'); - assert(r3.uri.toString() == 'https://www.example.com/test'); - assert(r3.headers[Headers.contentTypeHeader] == null); + expect(r3.uri.toString(), 'https://www.example.com/test'); + expect(r3.headers[Headers.contentTypeHeader], null); }); - test("#test responseDecoder return null", () async { + test("responseDecoder return null", () async { final dio = Dio(); dio.options.responseDecoder = (_, __, ___) => null; dio.options.baseUrl = EchoAdapter.mockBase; @@ -354,7 +355,7 @@ void main() { expect(response.data, null); }); - test('#test forceConvert responseType', () async { + test('forceConvert responseType', () async { final dio = Dio(BaseOptions( baseUrl: MockAdapter.mockBase, )) // @@ -371,7 +372,7 @@ void main() { expect(textResponse2.data, json.encode(expectedResponseData)); }); - test('#test option invalid base url', () { + test('option invalid base url', () { final opt1 = 'blob:http://localhost/xyz123'; final opt2 = 'https://pub.dev'; final opt3 = 'https://'; diff --git a/dio/test/pinning_test.dart b/dio/test/pinning_test.dart index aca92ece6..b3e1537d2 100644 --- a/dio/test/pinning_test.dart +++ b/dio/test/pinning_test.dart @@ -23,7 +23,7 @@ void main() { }); test('pinning: untrusted host rejected with no approver', () async { - dynamic error; + DioError? error; try { final dio = Dio(); await dio.get(untrustedCertUrl); @@ -32,12 +32,10 @@ void main() { error = e; } expect(error, isNotNull); - expect(error is DioError, isTrue); }); test('pinning: every certificate tested and rejected', () async { - dynamic error; - + DioError? error; try { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter() @@ -48,7 +46,6 @@ void main() { error = e; } expect(error, isNotNull); - expect(error is DioError, isTrue); }); test('pinning: trusted certificate tested and allowed', () async { @@ -82,7 +79,7 @@ void main() { test('pinning: untrusted certificate rejected before validateCertificate', () async { - dynamic error; + DioError? error; try { final dio = Dio(); @@ -99,12 +96,10 @@ void main() { error = e; } expect(error, isNotNull); - expect(error is DioError, isTrue); }); test('bad pinning: badCertCallback does not use leaf certificate', () async { - dynamic error; - + DioError? error; try { final dio = Dio(); dio.httpClientAdapter = IOHttpClientAdapter() @@ -127,7 +122,6 @@ void main() { error = e; } expect(error, isNotNull); - expect(error is DioError, isTrue); }); test('pinning: 2 requests == 2 approvals', () async { diff --git a/dio/test/readtimeout_test.dart b/dio/test/readtimeout_test.dart index 5119820ce..5513b0f62 100644 --- a/dio/test/readtimeout_test.dart +++ b/dio/test/readtimeout_test.dart @@ -55,7 +55,7 @@ void main() { tearDown(stopServer); test( - '#read_timeout - catch DioError when receiveTimeout < $_sleepDurationAfterConnectionEstablished', + 'catch DioError when receiveTimeout < $_sleepDurationAfterConnectionEstablished', () async { final dio = Dio(); @@ -79,7 +79,7 @@ void main() { }); test( - '#read_timeout - no DioError when receiveTimeout > $_sleepDurationAfterConnectionEstablished', + 'no DioError when receiveTimeout > $_sleepDurationAfterConnectionEstablished', () async { final dio = Dio(); @@ -100,7 +100,7 @@ void main() { expect(error, isNull); }); - test('#read_timeout - change connectTimeout in run time ', () async { + test('change connectTimeout in run time ', () async { final dio = Dio(); final adapter = IOHttpClientAdapter(); final http = HttpClient(); diff --git a/dio/test/request_test.dart b/dio/test/request_test.dart index 5bc25aeca..0d3d34b58 100644 --- a/dio/test/request_test.dart +++ b/dio/test/request_test.dart @@ -11,7 +11,7 @@ void main() { tearDown(stopServer); - group('#test requests', () { + group('requests', () { late Dio dio; setUp(() { dio = Dio(); @@ -28,7 +28,7 @@ void main() { }, )); }); - test('#test restful APIs', () async { + test('restful APIs', () async { Response response; // test get response = await dio.get( @@ -79,14 +79,14 @@ void main() { // ignore progress }, ); - assert(response.isRedirect == true); - assert(response.redirects.length == 1); + expect(response.isRedirect, true); + expect(response.redirects.length, 1); final ri = response.redirects.first; - assert(ri.statusCode == 302); - assert(ri.method == 'GET'); + expect(ri.statusCode, 302); + expect(ri.method, 'GET'); }); - test('#test multi value headers', () async { + test('multi value headers', () async { final Response response = await dio.get( '/multi-value-header', options: Options( @@ -102,7 +102,7 @@ void main() { ); }); - test('#test request with URI', () async { + test('request with URI', () async { Response response; // test get @@ -137,35 +137,35 @@ void main() { expect(response.data['path'], '/test'); }); - test('#test redirect', () async { + test('redirect', () async { Response response; response = await dio.get('/redirect'); - assert(response.isRedirect == true); - assert(response.redirects.length == 1); + expect(response.isRedirect, true); + expect(response.redirects.length, 1); final ri = response.redirects.first; - assert(ri.statusCode == 302); - assert(ri.method == 'GET'); - assert(ri.location.path == '/'); + expect(ri.statusCode, 302); + expect(ri.method, 'GET'); + expect(ri.location.path, '/'); }); - test('#test generic parameters', () async { + test('generic parameters', () async { Response response; // default is "Map" response = await dio.get('/test'); - assert(response.data is Map); + expect(response.data, isA()); // get response as `string` response = await dio.get('/test'); - assert(response.data is String); + expect(response.data, isA()); // get response as `Map` response = await dio.get('/test'); - assert(response.data is Map); + expect(response.data, isA()); // get response as `List` response = await dio.get('/list'); - assert(response.data is List); + expect(response.data, isA()); expect(response.data[0], 1); }); }); diff --git a/dio/test/upload_stream_test.dart b/dio/test/upload_stream_test.dart index fde1ca0ff..2dfc298fb 100644 --- a/dio/test/upload_stream_test.dart +++ b/dio/test/upload_stream_test.dart @@ -5,8 +5,18 @@ import 'package:diox/diox.dart'; import 'package:test/test.dart'; void main() { - final dio = Dio(); - dio.options.baseUrl = 'https://httpbin.org/'; + final dio = Dio() + ..options.baseUrl = 'https://httpbin.org/' + ..interceptors.add( + QueuedInterceptorsWrapper( + onRequest: (options, handler) async { + // Delay 1 second before requests to avoid request too frequently. + await Future.delayed(const Duration(seconds: 1)); + handler.next(options); + }, + ), + ); + test('stream', () async { Response r; const str = 'hello 😌'; diff --git a/plugins/http2_adapter/lib/src/http2_adapter.dart b/plugins/http2_adapter/lib/src/http2_adapter.dart index 46025a0f4..6be6f3f76 100644 --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart @@ -131,7 +131,7 @@ class Http2Adapter implements HttpClientAdapter { } }, onDone: () => sc.close(), - onError: (dynamic error, StackTrace stackTrace) { + onError: (Object error, StackTrace stackTrace) { // If connection is being forcefully terminated, remove the connection if (error is TransportConnectionException) { _connectionMgr.removeConnection(transport); diff --git a/plugins/http2_adapter/test/http2_test.dart b/plugins/http2_adapter/test/http2_test.dart index 66dd6a4ca..d5fdb4dd5 100644 --- a/plugins/http2_adapter/test/http2_test.dart +++ b/plugins/http2_adapter/test/http2_test.dart @@ -1,20 +1,8 @@ -import 'dart:io'; - -import 'package:crypto/crypto.dart'; import 'package:diox/diox.dart'; import 'package:diox_http2_adapter/diox_http2_adapter.dart'; import 'package:test/test.dart'; void main() { - // NOTE: Run test.sh to download the currrent certs to the file below. - // - // OpenSSL output like: SHA256 Fingerprint=EE:5C:E1:DF:A7:A4... - // All badssl.com hosts have the same cert, they just have TLS - // setting or other differences (like host name) that make them bad. - final lines = File('test/_pinning_http2.txt').readAsLinesSync(); - final fingerprint = - lines.first.split('=').last.toLowerCase().replaceAll(':', ''); - test('adds one to input values', () async { final dio = Dio() ..options.baseUrl = 'https://pub.dev/' @@ -46,158 +34,4 @@ void main() { final res = await dio.post('post', data: 'TEST'); expect(res.data.toString(), contains('TEST')); }); - - test('pinning: trusted host allowed with no approver', () async { - final dio = Dio() - ..httpClientAdapter = Http2Adapter(ConnectionManager( - idleTimeout: Duration(seconds: 10), - )); - - final res = await dio.get('https://httpbin.org/get'); - expect(res, isNotNull); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - }); - - test('pinning: untrusted host rejected with no approver', () async { - dynamic error; - - try { - final dio = Dio() - ..httpClientAdapter = Http2Adapter(ConnectionManager( - idleTimeout: Duration(seconds: 10), - onClientCreate: (url, config) { - // Consider all hosts untrusted - config.context = SecurityContext(withTrustedRoots: false); - })); - - await dio.get('https://httpbin.org/get'); - fail('did not throw'); - } on DioError catch (e) { - error = e; - } - expect(error, isNotNull); - expect(error is DioError, isTrue); - }); - - test('pinning: trusted certificate tested and allowed', () async { - bool approved = false; - final dio = Dio() - ..httpClientAdapter = Http2Adapter(ConnectionManager( - idleTimeout: Duration(seconds: 10), - onClientCreate: (url, config) { - config.validateCertificate = (certificate, host, port) { - approved = true; - return true; - }; - }, - )); - - final res = await dio.get('https://httpbin.org/get'); - expect(approved, true); - expect(res, isNotNull); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - }); - - test('pinning: untrusted certificate tested and allowed', () async { - bool badCert = false; - bool approved = false; - final dio = Dio() - ..httpClientAdapter = Http2Adapter(ConnectionManager( - idleTimeout: Duration(seconds: 10), - onClientCreate: (url, config) { - config.context = SecurityContext(withTrustedRoots: false); - config.onBadCertificate = (certificate) { - badCert = true; - return true; - }; - config.validateCertificate = (certificate, host, port) { - approved = true; - return true; - }; - }, - )); - - final res = await dio.get('https://httpbin.org/get'); - expect(badCert, true); - expect(approved, true); - expect(res, isNotNull); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - }); - - test('pinning: untrusted certificate tested and allowed', () async { - bool badCert = false; - bool approved = false; - String? badCertSubject; - String? approverSubject; - String? badCertSha256; - String? approverSha256; - - final dio = Dio() - ..httpClientAdapter = Http2Adapter(ConnectionManager( - idleTimeout: Duration(seconds: 10), - onClientCreate: (url, config) { - config.context = SecurityContext(withTrustedRoots: false); - config.onBadCertificate = (certificate) { - badCert = true; - badCertSubject = certificate.subject.toString(); - badCertSha256 = sha256.convert(certificate.der).toString(); - return true; - }; - config.validateCertificate = (certificate, host, port) { - if (certificate == null) fail('must include a certificate'); - approved = true; - approverSubject = certificate.subject.toString(); - approverSha256 = sha256.convert(certificate.der).toString(); - return true; - }; - }, - )); - - final res = await dio.get('https://httpbin.org/get'); - expect(badCert, true); - expect(approved, true); - expect(badCertSubject, isNotNull); - expect(badCertSubject, isNot(contains('httpbin.org'))); - expect(badCertSha256, isNot(fingerprint)); - expect(approverSubject, isNotNull); - expect(approverSubject, contains('httpbin.org')); - expect(approverSha256, fingerprint); - expect(approverSubject, isNot(badCertSubject)); - expect(approverSha256, isNot(badCertSha256)); - expect(res, isNotNull); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - }); - - test('pinning: 2 requests == 1 approval', () async { - int approvalCount = 0; - final dio = Dio() - ..options.baseUrl = 'https://httpbin.org/' - ..httpClientAdapter = Http2Adapter(ConnectionManager( - // allow connection reuse - idleTimeout: Duration(seconds: 20), - onClientCreate: (url, config) { - config.validateCertificate = (certificate, host, port) { - approvalCount++; - return true; - }; - }, - )); - - Response res = await dio.get('get'); - final firstTime = res.headers['date']; - expect(approvalCount, 1); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - await Future.delayed(Duration(seconds: 1)); - res = await dio.get('get'); - final secondTime = res.headers['date']; - expect(approvalCount, 1); - expect(firstTime, isNot(secondTime)); - expect(res.data, isNotNull); - expect(res.data.toString(), contains('Host: httpbin.org')); - }); } diff --git a/plugins/http2_adapter/test/pinning_test.dart b/plugins/http2_adapter/test/pinning_test.dart new file mode 100644 index 000000000..c0751fb3b --- /dev/null +++ b/plugins/http2_adapter/test/pinning_test.dart @@ -0,0 +1,178 @@ +@TestOn('vm') +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:diox/diox.dart'; +import 'package:diox_http2_adapter/diox_http2_adapter.dart'; +import 'package:test/test.dart'; + +void main() { + // NOTE: Run test.sh to download the current certs to the file below. + // + // OpenSSL output like: SHA256 Fingerprint=EE:5C:E1:DF:A7:A4... + // All badssl.com hosts have the same cert, they just have TLS + // setting or other differences (like host name) that make them bad. + final lines = File('test/_pinning_http2.txt').readAsLinesSync(); + final fingerprint = + lines.first.split('=').last.toLowerCase().replaceAll(':', ''); + + group('SSL pinning', () { + final Dio dio = Dio() + ..options.baseUrl = 'https://httpbin.org/' + ..interceptors.add( + QueuedInterceptorsWrapper( + onRequest: (options, handler) async { + // Delay 1 second before requests to avoid request too frequently. + await Future.delayed(const Duration(seconds: 1)); + handler.next(options); + }, + ), + ); + final baseHost = 'httpbin.org'; + final expectedHostString = 'Host: $baseHost'; + + test('trusted host allowed with no approver', () async { + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + idleTimeout: Duration(seconds: 10), + )); + + final res = await dio.get('get'); + expect(res, isNotNull); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + }); + + test('untrusted host rejected with no approver', () async { + DioError? error; + try { + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + idleTimeout: Duration(seconds: 10), + onClientCreate: (url, config) { + // Consider all hosts untrusted + config.context = SecurityContext(withTrustedRoots: false); + }, + )); + await dio.get('get'); + fail('did not throw'); + } on DioError catch (e) { + error = e; + } + expect(error, isNotNull); + }); + + test('trusted certificate tested and allowed', () async { + bool approved = false; + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + idleTimeout: Duration(seconds: 10), + onClientCreate: (url, config) { + config.validateCertificate = (certificate, host, port) { + approved = true; + return true; + }; + }, + )); + final res = await dio.get('get'); + expect(approved, true); + expect(res, isNotNull); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + }); + + test('untrusted certificate tested and allowed', () async { + bool badCert = false; + bool approved = false; + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + idleTimeout: Duration(seconds: 10), + onClientCreate: (url, config) { + config.context = SecurityContext(withTrustedRoots: false); + config.onBadCertificate = (certificate) { + badCert = true; + return true; + }; + config.validateCertificate = (certificate, host, port) { + approved = true; + return true; + }; + }, + )); + + final res = await dio.get('get'); + expect(badCert, true); + expect(approved, true); + expect(res, isNotNull); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + }); + + test('untrusted certificate tested and allowed', () async { + bool badCert = false; + bool approved = false; + String? badCertSubject; + String? approverSubject; + String? badCertSha256; + String? approverSha256; + + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + idleTimeout: Duration(seconds: 10), + onClientCreate: (url, config) { + config.context = SecurityContext(withTrustedRoots: false); + config.onBadCertificate = (certificate) { + badCert = true; + badCertSubject = certificate.subject.toString(); + badCertSha256 = sha256.convert(certificate.der).toString(); + return true; + }; + config.validateCertificate = (certificate, host, port) { + if (certificate == null) fail('must include a certificate'); + approved = true; + approverSubject = certificate.subject.toString(); + approverSha256 = sha256.convert(certificate.der).toString(); + return true; + }; + }, + )); + + final res = await dio.get('get'); + expect(badCert, true); + expect(approved, true); + expect(badCertSubject, isNotNull); + expect(badCertSubject, isNot(contains(baseHost))); + expect(badCertSha256, isNot(fingerprint)); + expect(approverSubject, isNotNull); + expect(approverSubject, contains(baseHost)); + expect(approverSha256, fingerprint); + expect(approverSubject, isNot(badCertSubject)); + expect(approverSha256, isNot(badCertSha256)); + expect(res, isNotNull); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + }); + + test('2 requests == 1 approval', () async { + int approvalCount = 0; + dio.httpClientAdapter = Http2Adapter(ConnectionManager( + // allow connection reuse + idleTimeout: Duration(seconds: 20), + onClientCreate: (url, config) { + config.validateCertificate = (certificate, host, port) { + approvalCount++; + return true; + }; + }, + )); + + Response res = await dio.get('get'); + final firstTime = res.headers['date']; + expect(approvalCount, 1); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + await Future.delayed(Duration(seconds: 1)); + res = await dio.get('get'); + final secondTime = res.headers['date']; + expect(approvalCount, 1); + expect(firstTime, isNot(secondTime)); + expect(res.data, isNotNull); + expect(res.data.toString(), contains(expectedHostString)); + }); + }); +} diff --git a/scripts/prepare_pinning_certs.sh b/scripts/prepare_pinning_certs.sh index e105cc316..fc6afe46e 100644 --- a/scripts/prepare_pinning_certs.sh +++ b/scripts/prepare_pinning_certs.sh @@ -1,7 +1,10 @@ +# For diox pinning tests openssl s_client \ -servername badssl.com \ -connect badssl.com:443 < /dev/null 2>/dev/null \ | openssl x509 -noout -fingerprint -sha256 > dio/test/_pinning.txt 2>/dev/null + +# For http2_adapter pinning tests openssl s_client \ -servername httpbin.org \ -connect httpbin.org:443 < /dev/null 2>/dev/null \