From 73272658a00b40e609e35a37ed11c7f0b8783822 Mon Sep 17 00:00:00 2001 From: Sominemo Date: Thu, 4 Mar 2021 00:41:13 +0200 Subject: [PATCH] Sound null safety --- CHANGELOG.md | 5 + README.md | 2 +- lib/src/api.dart | 58 +++---- lib/src/mcc/extensions/emoji.dart | 9 +- lib/src/mcc/extensions/visuals.dart | 4 +- lib/src/money.dart | 22 +-- lib/src/mono.dart | 61 ++++++-- pubspec.yaml | 10 +- test/api_test.dart | 232 +++++++++++++++++----------- 9 files changed, 252 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2cf2b0..6d5e147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ +## 1.4.0-nullsafety.1 +- Null safety +- Bugs fixed + ## 1.3.0 - Breaking change: /mcc/mcc.dart removed - Docs fixes + ## 1.2.2 - Fixed CurrencyInfo integer parsing error diff --git a/README.md b/README.md index 7e656d5..4f3a3ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Monobank API SDK for Dart -Author is not related to monobank team. +This package is unofficial ## Usage diff --git a/lib/src/api.dart b/lib/src/api.dart index 037840c..5fce685 100644 --- a/lib/src/api.dart +++ b/lib/src/api.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:math' as math; import 'package:http/http.dart' as http; /// Possible error types that happen to API @@ -136,18 +135,18 @@ class APIRequest { this.useAuth = false, /// JSON body to send - Map data, + Map data = const {}, /// Headers to send /// /// ``` /// Key: Value /// ``` - Map headers, + Map headers = const {}, this.httpMethod = APIHttpMethod.GET, }) : _completer = Completer(), - data = data ?? {}, - headers = headers ?? {}, + data = Map.from(data), + headers = Map.from(headers), _originalData = data, _originalHeaders = headers; @@ -169,7 +168,7 @@ class APIRequest { /// ``` /// APIRequestA.methodId == APIRequestB.methodId /// ``` - final String methodId; + final String? methodId; /// API Request Flags /// @@ -240,7 +239,7 @@ class APIResponse { /// HTTP Response headers final Map headers; - APIResponse._(this.body, this.statusCode, this.headers); + const APIResponse._(this.body, this.statusCode, this.headers); } /// Requests sender and throttling container @@ -261,10 +260,10 @@ class API { this.domain, { this.globalTimeout = Duration.zero, this.token, - Map requestTimeouts, - Uri noAuthDomain, + Map requestTimeouts = const {}, + Uri? noAuthDomain, }) : noAuthDomain = noAuthDomain ?? domain { - this.requestTimeouts = requestTimeouts ?? {}; + this.requestTimeouts = requestTimeouts; } /// Root path @@ -284,7 +283,7 @@ class API { /// /// Is being appended in [API.authAttacher] when /// [APIRequest.useAuth] == `true`. - final String token; + final String? token; /// Minimal timeout between all requests /// @@ -337,7 +336,7 @@ class API { /// Returns last time request of class was sent if specified /// /// Returns `DateTime.fromMillisecondsSinceEpoch(0)` if never was sent - DateTime lastRequest({String methodId}) => methodId == null + DateTime lastRequest({String? methodId}) => methodId == null ? _lastRequest : (_lastRequests[methodId] ?? DateTime.fromMillisecondsSinceEpoch(0)); @@ -347,7 +346,7 @@ class API { /// /// Must be checked again before sending the request, because it returns /// **minimum** required time, not the actual one - Duration willFreeIn({String methodId}) { + Duration willFreeIn({String? methodId}) { // Time since last request final timePassed = DateTime.now().difference(_lastRequest); @@ -388,7 +387,7 @@ class API { /// /// Can be also calculated by comparasion of API.isRequestImmediate /// to [Duration.zero] - bool isRequestImmediate(String methodId) => + bool isRequestImmediate(String? methodId) => willFreeIn(methodId: methodId) == Duration.zero; /// Evaluate the request @@ -450,7 +449,7 @@ class API { Duration globalWait; - Duration minDelay; + Duration? minDelay; for (var request in _cart.toList()) { try { @@ -462,7 +461,7 @@ class API { (request.settings & APIFlags.skipGlobal != 0))) { minDelay = (minDelay == null ? methodWait - : math.min(minDelay, methodWait)); + : (minDelay > methodWait ? methodWait : minDelay)); continue; } } @@ -501,17 +500,23 @@ class API { final url = request.useAuth ? domain : noAuthDomain; final beforeSentMethodTime = lastRequest(methodId: request.methodId); final beforeSentTime = lastRequest(); + final methodId = request.methodId; + try { request._isProcessingNeeded = false; var inLine = (request.settings & APIFlags.skip == 0) && (request.settings & APIFlags.skipGlobal == 0); _lastRequest = DateTime.now(); - _lastRequests[request.methodId] = DateTime.now(); - if (inLine) { - _methodBusy[request.methodId] = true; + + if (methodId != null) { + _lastRequests[methodId] = DateTime.now(); + if (inLine) { + _methodBusy[methodId] = true; + } } - final requestUrl = '$url${request.method}'; + final requestUrl = url.replace( + pathSegments: [...url.pathSegments, ...request.method.split('/')]); preprocessRequest(request); if (request.useAuth) authAttacher(request); @@ -536,15 +541,15 @@ class API { final stamp = DateTime.now(); _lastRequest = stamp; - _lastRequests[request.methodId] = stamp; + if (methodId != null) _lastRequests[methodId] = stamp; if (inLine) { - _methodBusy[request.methodId] = false; + if (methodId != null) _methodBusy[methodId] = false; } request._completer.complete(resolver); } on APIError catch (e) { _lastRequest = beforeSentTime; - _lastRequests[request.methodId] = beforeSentMethodTime; + if (methodId != null) _lastRequests[methodId] = beforeSentMethodTime; if ((e.isFloodError && request.settings & APIFlags.resendOnFlood > 0) || request.settings & APIFlags.resend > 0) { @@ -554,18 +559,19 @@ class API { } } catch (e) { _lastRequest = beforeSentTime; - _lastRequests[request.methodId] = beforeSentMethodTime; + if (methodId != null) _lastRequests[methodId] = beforeSentMethodTime; request._completer.completeError(e); rethrow; } - _methodBusy[request.methodId] = false; + if (methodId != null) _methodBusy[methodId] = false; } /// Attaches credentials to given request void authAttacher(APIRequest request) { - request.headers['X-Token'] = token; + final localToken = token; + if (localToken != null) request.headers['X-Token'] = localToken; } /// Preprocesses given request diff --git a/lib/src/mcc/extensions/emoji.dart b/lib/src/mcc/extensions/emoji.dart index 3d3aebe..218ff65 100644 --- a/lib/src/mcc/extensions/emoji.dart +++ b/lib/src/mcc/extensions/emoji.dart @@ -11,7 +11,10 @@ extension EmojiMCC on MCC { static String fallbackEmoji = '💳'; /// Related emoji - String get emoji => - MCCEmojiDataset.keys.firstWhere((e) => MCCEmojiDataset[e].contains(code), - orElse: () => fallbackEmoji); + String get emoji => MCCEmojiDataset.keys.firstWhere((e) { + final emoji = MCCEmojiDataset[e]; + + if (emoji == null) return false; + return emoji.contains(code); + }, orElse: () => fallbackEmoji); } diff --git a/lib/src/mcc/extensions/visuals.dart b/lib/src/mcc/extensions/visuals.dart index 31a9b99..c2fccf5 100644 --- a/lib/src/mcc/extensions/visuals.dart +++ b/lib/src/mcc/extensions/visuals.dart @@ -17,8 +17,8 @@ extension VisualsMCC on MCC { Map get visuals => MCCVisualsDataset[emoji] ?? fallbackVisual; /// Get possible color for use - String get color => visuals['color']; + String get color => visuals['color'] ?? 'crefit_card'; /// Get Material icon name: https://material.io/icons - String get icon => visuals['icon']; + String get icon => visuals['icon'] ?? '#607d8b'; } diff --git a/lib/src/money.dart b/lib/src/money.dart index 3167060..1d73926 100644 --- a/lib/src/money.dart +++ b/lib/src/money.dart @@ -49,11 +49,11 @@ class Currency { /// XXX code to look for String code, ) { - var upperCode = code.toUpperCase(); - var info = Iso4217.firstWhere((currency) => currency['code'] == upperCode, - orElse: () => null); + final upperCode = code.toUpperCase(); + final info = Iso4217.firstWhere((currency) => currency['code'] == upperCode, + orElse: () => {}); - if (info == null) return UnknownCurrency(code); + if (!info.containsKey('code')) return UnknownCurrency(code); return Currency(info['code'], info['number'], info['digits']); } @@ -65,10 +65,10 @@ class Currency { /// ISO-4217 number to look for int number, ) { - var info = Iso4217.firstWhere((currency) => currency['number'] == number, - orElse: () => null); + final info = Iso4217.firstWhere((currency) => currency['number'] == number, + orElse: () => {}); - if (info == null) return UnknownCurrency(number.toString()); + if (!info.containsKey('code')) return UnknownCurrency(number.toString()); return Currency(info['code'], info['number'], info['digits']); } @@ -196,7 +196,7 @@ class Money implements Comparable { /// Accepts main and fractional part of the amount as two integers factory Money.separated(int integer, int fraction, Currency currency) => - Money(integer * pow(10, currency.digits) + fraction, currency); + Money(integer * pow(10, currency.digits).floor() + fraction, currency); /// Constant for zero amount of a [Currency.dummy] currency static const Money zero = Money(0, Currency.dummy); @@ -216,7 +216,7 @@ class Money implements Comparable { @override int compareTo(other) { if (other is! Money) throw Exception('Money can be compared only to Money'); - Money second = other; + final second = other; if (this > second) return 1; if (this < second) return -1; @@ -446,7 +446,7 @@ class CurrencyInfo { /// Shortcut to create conversion objects with same sell rate and currency rate factory CurrencyInfo.cross( Currency currencyA, Currency currencyB, double rateCross, - {MoneyRounding rounding}) => + {MoneyRounding? rounding}) => CurrencyInfo(currencyA, currencyB, rateCross, rateCross, - rounding: rounding); + rounding: rounding!); } diff --git a/lib/src/mono.dart b/lib/src/mono.dart index 144c535..3cfb43b 100644 --- a/lib/src/mono.dart +++ b/lib/src/mono.dart @@ -80,27 +80,21 @@ class BankCard { switch (type) { case 'black': return CardType.black; - break; case 'white': return CardType.white; - break; case 'platinum': return CardType.platinum; - break; case 'iron': return CardType.iron; - break; case 'yellow': return CardType.yellow; - break; case 'fop': return CardType.fop; - break; default: return CardType.other; @@ -118,6 +112,12 @@ class Client { var list = accs.map((e) => Account._fromJson(e, this)).toList(); if (sortAccounts) { + const uahOrder = [ + CardType.black, + CardType.white, + CardType.yellow, + CardType.fop + ]; const curOrder = ['UAH', 'USD', 'EUR', 'PLN']; list.sort((a, b) => a.id.compareTo(b.id)); @@ -140,15 +140,42 @@ class Client { }); uah.sort((a, b) { - if (a.type == CardType.white && b.type != CardType.white) { + final indA = uahOrder.indexOf(a.type); + final indB = uahOrder.indexOf(b.type); + + if (indA == -1 && indB == -1) { + return 0; + } + if (indA == -1 && indB != -1) { + return 1; + } + if (indA != -1 && indB == -1) { + return -1; + } + + if (indA < indB) { + return -1; + } + if (indA > indB) { return 1; } return 0; }); known.sort((a, b) { - var indA = curOrder.indexOf(a.balance.currency.code); - var indB = curOrder.indexOf(b.balance.currency.code); + final indA = curOrder.indexOf(a.balance.currency.code); + final indB = curOrder.indexOf(b.balance.currency.code); + + if (indA == -1 && indB == -1) { + return 0; + } + if (indA == -1 && indB != -1) { + return 1; + } + if (indA != -1 && indB == -1) { + return -1; + } + if (indA < indB) { return -1; } @@ -276,7 +303,10 @@ class StatementItem { account.balance.currency, ), comment = data['comment'] ?? '', - hold = data['hold']; + hold = data['hold'], + receiptId = data['receiptId'], + counterEdrpou = data['counterEdrpou'], + counterIban = data['counterIban']; /// Parent account final Account account; @@ -314,6 +344,15 @@ class StatementItem { /// Authorization hold final bool hold; + // Check number on check.gov.ua + final String? receiptId; + + // Counteragent Edrpou number, is available only for accounts with `fop` type + final String? counterEdrpou; + + // Counteragent Iban number, is available only for accounts with `fop` type + final String? counterIban; + /// Returns true if operation is outgoing bool get isOut => amount.isNegative; @@ -342,7 +381,7 @@ class Statement { /// End on final DateTime to; - static const Duration _maxRange = Duration(days: 31); + static const Duration _maxRange = Duration(days: 7); /// Begins stream of statement /// diff --git a/pubspec.yaml b/pubspec.yaml index d95b1fd..af8ad6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,15 +4,15 @@ description: > Supports getting currencies and grabbing statement -version: 1.3.0 +version: 1.4.0-nullsafety.1 homepage: https://github.com/Sominemo/monobank_api environment: - sdk: ">=2.8.1 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" dependencies: - http: ^0.12.2 + http: ^0.13.0 dev_dependencies: - pedantic: ^1.9.0 - test: ^1.14.4 + pedantic: ^1.11.0 + test: ^1.16.5 diff --git a/test/api_test.dart b/test/api_test.dart index 1ff84dc..eb28d63 100644 --- a/test/api_test.dart +++ b/test/api_test.dart @@ -4,8 +4,22 @@ import 'dart:io'; import 'package:monobank_api/monobank_api.dart'; import 'package:test/test.dart'; +class EmptyApiResponse implements APIResponse { + EmptyApiResponse() + : body = {}, + statusCode = 0, + headers = {}; + + @override + final dynamic body; + @override + final int statusCode; + @override + final Map headers; +} + void main() { - API api; + var api = API(Uri.parse('http://example.com/')); const threshold = 100; const timeouts = { 'test-class1': Duration(seconds: 1), @@ -62,7 +76,7 @@ void main() { try { await api.call(APIRequest('test', useAuth: true)); fail('Invalid call didn\'t throw anything'); - } catch (e) { + } on APIError catch (e) { expect(e.isAccessError, equals(true)); } }); @@ -82,15 +96,19 @@ void main() { }); group('With Server', () { - HttpServer server; - Uri url; - API api; - DateTime time; + HttpServer? server; + Uri? url; + var api = API(Uri.parse('https://example.com')); + DateTime? time; setUp(() async { - server = await HttpServer.bind('localhost', 0); - url = Uri.parse('http://${server.address.host}:${server.port}/'); - api = API(url, globalTimeout: Duration(seconds: 3), requestTimeouts: { + final s = await HttpServer.bind('localhost', 0); + server = s; + + final u = Uri.parse('http://${s.address.host}:${s.port}/'); + url = u; + + api = API(u, globalTimeout: Duration(seconds: 3), requestTimeouts: { 'test-class1': Duration(seconds: 5), 'test-class2': Duration(seconds: 2), 'test-class3': Duration(seconds: 0), @@ -99,16 +117,18 @@ void main() { }); tearDown(() async { - await server.close(force: true); + final s = server; + if (s == null) return; + + await s.close(force: true); server = null; url = null; - api = null; time = null; }); test('Method is being passed correctly', () { api.call(APIRequest('test-method')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); @@ -118,20 +138,20 @@ void main() { test('Initial request is being sent immediatelly', () { api.call(APIRequest('test-method')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { var received = DateTime.now(); request.response.write('{}'); await request.response.close(); - expect(received.difference(time), + expect(received.difference(time ?? DateTime(0)), lessThan(Duration(milliseconds: threshold))); })); }); test('Methods can be busy', () { api.call(APIRequest('test-method', methodId: 'test-class1')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { var business = api.isMethodBusy('test-class1'); request.response.write('{}'); @@ -145,7 +165,7 @@ void main() { var originalTime = api.lastRequest(); api.call(APIRequest('test-method')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); @@ -156,7 +176,7 @@ void main() { test('Request time is being recorded correctly', () { api.call(APIRequest('test-method')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); @@ -169,7 +189,7 @@ void main() { var originalTime = api.lastRequest(methodId: 'test-class1'); api.call(APIRequest('test-method', methodId: 'test-class1')); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); @@ -179,13 +199,13 @@ void main() { }); group('Clonnig and Delivery', () { - API api; + var api = API(Uri()); APIRequest originalAPIRequest, cloneAPIRequest; - HttpRequest originalRequest, cloneRequest; + HttpRequest? originalRequest, cloneRequest; var oDec, cDec; setUp(() async { - api = API(url, token: 'my-test-token'); + api = API(url ?? Uri(), token: 'my-test-token'); originalAPIRequest = APIRequest('test-method', data: {'test-field': 'test-value'}, @@ -196,13 +216,13 @@ void main() { cloneAPIRequest = APIRequest.clone(originalAPIRequest); - server.listen((request) async { + server?.listen((request) async { if (originalRequest == null) { originalRequest = request; - oDec = jsonDecode(await utf8.decodeStream(originalRequest)); + oDec = jsonDecode(await utf8.decodeStream(originalRequest!)); } else { cloneRequest = request; - cDec = jsonDecode(await utf8.decodeStream(cloneRequest)); + cDec = jsonDecode(await utf8.decodeStream(cloneRequest!)); } request.response.write('{}'); @@ -214,21 +234,21 @@ void main() { }); test('Method', () { - expect(originalRequest.uri, equals(cloneRequest.uri)); + expect(originalRequest?.uri, equals(cloneRequest?.uri)); }); test('HTTP Method', () { - expect(originalRequest.method, equals(cloneRequest.method)); + expect(originalRequest?.method, equals(cloneRequest?.method)); }); test('Header', () { - expect(originalRequest.headers.value('X-Test-Header'), - equals(cloneRequest.headers.value('X-Test-Header'))); + expect(originalRequest?.headers.value('X-Test-Header'), + equals(cloneRequest?.headers.value('X-Test-Header'))); }); test('Token', () { - expect(originalRequest.headers.value('X-Token'), - equals(cloneRequest.headers.value('X-Token'))); + expect(originalRequest?.headers.value('X-Token'), + equals(cloneRequest?.headers.value('X-Token'))); }); test('Body value', () { @@ -243,10 +263,10 @@ void main() { 'test-class2': Duration(seconds: 1), 'test-class3': Duration(seconds: 0), }; - API api; + var api = API(Uri()); setUp(() { - api = API(url, + api = API(url ?? Uri(), globalTimeout: Duration(seconds: 2), requestTimeouts: timeouts); }); @@ -255,19 +275,19 @@ void main() { var request = APIRequest('test-method', settings: APIFlags.waiting, methodId: 'test-class1'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime?.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -280,20 +300,20 @@ void main() { var request = APIRequest('test-method', settings: APIFlags.waiting, methodId: 'test-class2'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime?.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { expect( - DateTime.now().difference(lastTime).inMilliseconds, + DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(api.globalTimeout.inMilliseconds, api.globalTimeout.inMilliseconds + threshold)); } @@ -306,36 +326,46 @@ void main() { test('skip: Throws without agreeing for waiting', () { var request = APIRequest('test-method', settings: APIFlags.skip); - server.listen(expectAsync1((request) async { + server?.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); // Request must not be delivered }, count: 0)); - api.call(request).catchError(expectAsync1((error) { - expect(error.isIllegalRequestError, equals(true)); - })); + + final cb = expectAsync1((error) { + if (error is APIError) { + expect(error.isIllegalRequestError, equals(true)); + } + + return error; + }); + + api.call(request).catchError((o) { + cb(o); + return EmptyApiResponse(); + }); }); test('skip | waiting: Works on no-throttling', () { - api = API(url); + api = API(url!); var wait = Duration.zero.inMilliseconds; var request = APIRequest('test-method', settings: APIFlags.skip | APIFlags.waiting, methodId: 'test-class1'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -345,25 +375,25 @@ void main() { }, timeout: Timeout(Duration(seconds: 1))); test('skipGlobal | waiting: Works on no-throttling', () { - api = API(url); + api = API(url!); var wait = Duration.zero.inMilliseconds; var request = APIRequest('test-method', settings: APIFlags.skipGlobal | APIFlags.waiting, methodId: 'test-class2'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -376,22 +406,31 @@ void main() { var request = APIRequest('test-method', settings: APIFlags.skipGlobal); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); fail('Request must not be delivered'); }, count: 0)); - api.call(request).catchError(expectAsync1((error) { - expect(error.isIllegalRequestError, equals(true)); - })); + final cb = expectAsync1((error) { + if (error is APIError) { + expect(error.isIllegalRequestError, equals(true)); + } + + return error; + }); + + api.call(request).catchError((o) { + cb(o); + return EmptyApiResponse(); + }); }); test('resend', () { var count = 0; var request = APIRequest('test-method', settings: APIFlags.resend); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { if (count == 0) { request.response.statusCode = HttpStatus.forbidden; count++; @@ -409,7 +448,7 @@ void main() { settings: APIFlags.resendOnFlood | APIFlags.waiting); var request2 = APIRequest('test-method2'); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { count++; if (count == 1) { request.response.statusCode = HttpStatus.tooManyRequests; @@ -440,19 +479,19 @@ void main() { settings: APIFlags.skip | APIFlags.waiting, methodId: 'test-class1'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -467,19 +506,19 @@ void main() { settings: APIFlags.skip | APIFlags.waiting, methodId: 'test-class2'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -494,19 +533,19 @@ void main() { settings: APIFlags.skipGlobal | APIFlags.waiting, methodId: 'test-class1'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -522,19 +561,19 @@ void main() { settings: APIFlags.skipGlobal | APIFlags.waiting, methodId: 'test-class2'); - DateTime lastTime; + DateTime? lastTime; - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); if (lastTime == null) { lastTime = DateTime.now(); - expect(lastTime.difference(time), + expect(lastTime!.difference(time!), lessThan(Duration(milliseconds: threshold))); } else { - expect(DateTime.now().difference(lastTime).inMilliseconds, + expect(DateTime.now().difference(lastTime!).inMilliseconds, inClosedOpenRange(wait, wait + threshold)); } }, count: 2)); @@ -547,29 +586,38 @@ void main() { var request = APIRequest('test-method', settings: APIFlags.skip | APIFlags.skipGlobal | APIFlags.waiting); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { request.response.write('{}'); await request.response.close(); fail('Request must not be delivered'); }, count: 0)); - api.call(request).catchError(expectAsync1((error) { - expect(error.isIllegalRequestError, equals(true)); - })); + final cb = expectAsync1((error) { + if (error is APIError) { + expect(error.isIllegalRequestError, equals(true)); + } + + return error; + }); + + api.call(request).catchError((o) { + cb(o); + return EmptyApiResponse(); + }); }); test('resendOnFlood | waiting: Correct wait times for global', () async { - DateTime last; + DateTime? last; var request1 = APIRequest('test-method', settings: APIFlags.resendOnFlood | APIFlags.waiting); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { if (last == null) { last = DateTime.now(); request.response.statusCode = HttpStatus.tooManyRequests; } else { - expect(DateTime.now().difference(last).inMilliseconds, + expect(DateTime.now().difference(last!).inMilliseconds, closeTo(0, threshold)); } request.response.write('{}'); @@ -581,17 +629,17 @@ void main() { test('resendOnFlood | waiting: Correct wait times for method', () async { - DateTime last; + DateTime? last; var request1 = APIRequest('test-method', settings: APIFlags.resendOnFlood | APIFlags.waiting, methodId: 'test-class1'); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { if (last == null) { last = DateTime.now(); request.response.statusCode = HttpStatus.tooManyRequests; } else { - expect(DateTime.now().difference(last).inMilliseconds, + expect(DateTime.now().difference(last!).inMilliseconds, closeTo(0, threshold)); } request.response.write('{}'); @@ -602,16 +650,16 @@ void main() { }); test('resend | waiting: Correct wait times for global', () async { - DateTime last; + DateTime? last; var request1 = APIRequest('test-method', settings: APIFlags.resend | APIFlags.waiting); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { if (last == null) { last = DateTime.now(); request.response.statusCode = HttpStatus.notFound; } else { - expect(DateTime.now().difference(last).inMilliseconds, + expect(DateTime.now().difference(last!).inMilliseconds, closeTo(0, threshold)); } request.response.write('{}'); @@ -622,17 +670,17 @@ void main() { }); test('resend | waiting: Correct wait times for method', () async { - DateTime last; + DateTime? last; var request1 = APIRequest('test-method', settings: APIFlags.resend | APIFlags.waiting, methodId: 'test-class1'); - server.listen(expectAsync1((request) async { + server!.listen(expectAsync1((request) async { if (last == null) { last = DateTime.now(); request.response.statusCode = HttpStatus.tooManyRequests; } else { - expect(DateTime.now().difference(last).inMilliseconds, + expect(DateTime.now().difference(last!).inMilliseconds, closeTo(0, threshold)); } request.response.write('{}');