From 045e5fc1fe447a6568b0f10d411da1636b61e920 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 2 Nov 2023 13:02:38 +0100 Subject: [PATCH] chore: enable discarded_futures lint --- analysis_options.yaml | 2 + lib/encryption/encryption.dart | 14 ++-- lib/encryption/key_manager.dart | 8 ++- lib/encryption/olm_manager.dart | 70 +++++++++++-------- lib/encryption/ssss.dart | 7 +- lib/encryption/utils/base64_unpadded.dart | 2 +- lib/src/client.dart | 4 +- lib/src/event.dart | 2 + lib/src/room.dart | 1 + lib/src/timeline.dart | 3 + lib/src/utils/device_keys_list.dart | 4 +- lib/src/utils/native_implementations.dart | 5 +- lib/src/utils/run_in_root.dart | 8 +-- lib/src/utils/uia_request.dart | 1 + .../native_implementations_web_worker.dart | 12 ++-- lib/src/voip/call.dart | 10 +-- lib/src/voip/conn_tester.dart | 15 ++-- lib/src/voip/group_call.dart | 5 +- lib/src/voip/voip.dart | 3 +- test/client_test.dart | 4 +- test/database_api_test.dart | 2 +- test/device_keys_list_test.dart | 2 +- test/fake_matrix_api.dart | 1 + test/room_archived_test.dart | 2 +- test/timeline_context_test.dart | 9 +-- test/timeline_test.dart | 9 +-- 26 files changed, 115 insertions(+), 90 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 0ce1c52c7..58719aedc 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -6,6 +6,8 @@ linter: always_use_package_imports: true avoid_bool_literals_in_conditional_expressions: true avoid_print: true + cancel_subscriptions: true + discarded_futures: true non_constant_identifier_names: false # seems to wrongly diagnose static const variables prefer_final_in_for_each: true prefer_final_locals: true diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 7403da9dd..8a5d1635c 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -100,11 +100,12 @@ class Encryption { void handleDeviceOneTimeKeysCount( Map? countJson, List? unusedFallbackKeyTypes) { - runInRoot(() => olmManager.handleDeviceOneTimeKeysCount( + runInRoot(() async => olmManager.handleDeviceOneTimeKeysCount( countJson, unusedFallbackKeyTypes)); } void onSync() { + // ignore: discarded_futures keyVerificationManager.cleanup(); } @@ -118,30 +119,25 @@ class Encryption { .contains(event.type)) { // "just" room key request things. We don't need these asap, so we handle // them in the background - // ignore: unawaited_futures runInRoot(() => keyManager.handleToDeviceEvent(event)); } if (event.type == EventTypes.Dummy) { // the previous device just had to create a new olm session, due to olm session // corruption. We want to try to send it the last message we just sent it, if possible - // ignore: unawaited_futures runInRoot(() => olmManager.handleToDeviceEvent(event)); } if (event.type.startsWith('m.key.verification.')) { // some key verification event. No need to handle it now, we can easily // do this in the background - // ignore: unawaited_futures runInRoot(() => keyVerificationManager.handleToDeviceEvent(event)); } if (event.type.startsWith('m.secret.')) { // some ssss thing. We can do this in the background - // ignore: unawaited_futures runInRoot(() => ssss.handleToDeviceEvent(event)); } if (event.sender == client.userID) { // maybe we need to re-try SSSS secrets - // ignore: unawaited_futures runInRoot(() => ssss.periodicallyRequestMissingCache()); } } @@ -157,14 +153,11 @@ class Encryption { update.content['content']['msgtype'] .startsWith('m.key.verification.'))) { // "just" key verification, no need to do this in sync - - // ignore: unawaited_futures runInRoot(() => keyVerificationManager.handleEventUpdate(update)); } if (update.content['sender'] == client.userID && update.content['unsigned']?['transaction_id'] == null) { // maybe we need to re-try SSSS secrets - // ignore: unawaited_futures runInRoot(() => ssss.periodicallyRequestMissingCache()); } } @@ -239,6 +232,7 @@ class Encryption { // the entry should always exist. In the case it doesn't, the following // line *could* throw an error. As that is a future, though, and we call // it un-awaited here, nothing happens, which is exactly the result we want + // ignore: discarded_futures client.database?.updateInboundGroupSessionIndexes( json.encode(inboundGroupSession.indexes), roomId, sessionId); } @@ -252,7 +246,7 @@ class Encryption { ?.session_id() ?? '') == content.sessionId) { - runInRoot(() => + runInRoot(() async => keyManager.clearOrUseOutboundGroupSession(roomId, wipe: true)); } if (canRequestSession) { diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index d8cb59fb9..6c70f870d 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -244,7 +244,8 @@ class KeyManager { !client.isUnknownSession) { // do e2ee recovery _requestedSessionIds.add(requestIdent); - runInRoot(() => request( + + runInRoot(() async => request( room, sessionId, senderKey, @@ -775,8 +776,8 @@ class KeyManager { Future? _uploadingFuture; void startAutoUploadKeys() { - _uploadKeysOnSync = encryption.client.onSync.stream - .listen((_) => uploadInboundGroupSessions(skipIfInProgress: true)); + _uploadKeysOnSync = encryption.client.onSync.stream.listen( + (_) async => uploadInboundGroupSessions(skipIfInProgress: true)); } /// This task should be performed after sync processing but should not block @@ -1064,6 +1065,7 @@ class KeyManager { StreamSubscription? _uploadKeysOnSync; void dispose() { + // ignore: discarded_futures _uploadKeysOnSync?.cancel(); for (final sess in _outboundGroupSessions.values) { sess.dispose(); diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index ce60c29bd..fca70d752 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -396,20 +396,24 @@ class OlmManager { final device = client.userDeviceKeys[event.sender]?.deviceKeys.values .firstWhereOrNull((d) => d.curve25519Key == senderKey); final existingSessions = olmSessions[senderKey]; - Future updateSessionUsage([OlmSession? session]) => - runInRoot(() async { - if (session != null) { - session.lastReceived = DateTime.now(); - await storeOlmSession(session); - } - if (device != null) { - device.lastActive = DateTime.now(); - await encryption.olmDatabase?.setLastActiveUserDeviceKey( - device.lastActive.millisecondsSinceEpoch, - device.userId, - device.deviceId!); - } - }); + Future updateSessionUsage([OlmSession? session]) async { + try { + if (session != null) { + session.lastReceived = DateTime.now(); + await storeOlmSession(session); + } + if (device != null) { + device.lastActive = DateTime.now(); + await encryption.olmDatabase?.setLastActiveUserDeviceKey( + device.lastActive.millisecondsSinceEpoch, + device.userId, + device.deviceId!); + } + } catch (e, s) { + Logs().e('Error while updating olm session timestamp', e, s); + } + } + if (existingSessions != null) { for (final session in existingSessions) { if (session.session == null) { @@ -446,14 +450,16 @@ class OlmManager { newSession.create_inbound_from(_olmAccount!, senderKey, body); _olmAccount!.remove_one_time_keys(newSession); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); + plaintext = newSession.decrypt(type, body); - await runInRoot(() => storeOlmSession(OlmSession( - key: client.userID!, - identityKey: senderKey, - sessionId: newSession.session_id(), - session: newSession, - lastReceived: DateTime.now(), - ))); + + await storeOlmSession(OlmSession( + key: client.userID!, + identityKey: senderKey, + sessionId: newSession.session_id(), + session: newSession, + lastReceived: DateTime.now(), + )); await updateSessionUsage(); } catch (e) { newSession.free(); @@ -570,8 +576,6 @@ class OlmManager { return _decryptToDeviceEvent(event); } catch (_) { // okay, the thing errored while decrypting. It is safe to assume that the olm session is corrupt and we should generate a new one - - // ignore: unawaited_futures runInRoot(() => restoreOlmSession(event.senderId, senderKey)); rethrow; @@ -658,14 +662,18 @@ class OlmManager { final encryptResult = sess.first.session!.encrypt(json.encode(fullPayload)); await storeOlmSession(sess.first); if (encryption.olmDatabase != null) { - await runInRoot( - () async => encryption.olmDatabase?.setLastSentMessageUserDeviceKey( - json.encode({ - 'type': type, - 'content': payload, - }), - device.userId, - device.deviceId!)); + try { + await encryption.olmDatabase?.setLastSentMessageUserDeviceKey( + json.encode({ + 'type': type, + 'content': payload, + }), + device.userId, + device.deviceId!); + } catch (e, s) { + // we can ignore this error, since it would just make us use a different olm session possibly + Logs().w('Error while updating olm usage timestamp', e, s); + } } final encryptedBody = { 'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2, diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index e6dab3870..b24854e3a 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -31,7 +31,6 @@ import 'package:matrix/encryption/utils/ssss_cache.dart'; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/crypto/crypto.dart' as uc; -import 'package:matrix/src/utils/run_in_root.dart'; const cacheTypes = { EventTypes.CrossSigningSelfSigning, @@ -722,7 +721,11 @@ class OpenSSSS { throw InvalidPassphraseException('Inalid key'); } if (postUnlock) { - await runInRoot(() => _postUnlock()); + try { + await _postUnlock(); + } catch (e, s) { + Logs().e('Error during post unlock', e, s); + } } } diff --git a/lib/encryption/utils/base64_unpadded.dart b/lib/encryption/utils/base64_unpadded.dart index 160a8d113..43f4c7784 100644 --- a/lib/encryption/utils/base64_unpadded.dart +++ b/lib/encryption/utils/base64_unpadded.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; /// decodes base64 /// -/// Dart's native [base64.decode] requires a padded base64 input String. +/// Dart's native [base64.decode()] requires a padded base64 input String. /// This function allows unpadded base64 too. /// /// See: https://github.com/dart-lang/sdk/issues/39510 diff --git a/lib/src/client.dart b/lib/src/client.dart index 8910a6ed7..59d665ed9 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1635,7 +1635,7 @@ class Client extends MatrixApi { set backgroundSync(bool enabled) { _backgroundSync = enabled; if (_backgroundSync) { - _sync(); + runInRoot(() async => _sync()); } } @@ -2228,7 +2228,7 @@ class Client extends MatrixApi { requestHistoryOnLimitedTimeline) { Logs().v( 'Limited timeline for ${rooms[roomIndex].id} request history now'); - unawaited(runInRoot(rooms[roomIndex].requestHistory)); + runInRoot(rooms[roomIndex].requestHistory); } } return room; diff --git a/lib/src/event.dart b/lib/src/event.dart index f352143b5..b5549d1d9 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -128,6 +128,7 @@ class Event extends MatrixEvent { final json = toJson(); json['unsigned'] ??= {}; json['unsigned'][messageSendingStatusKey] = EventStatus.error.intValue; + // ignore: discarded_futures room.client.handleSync( SyncUpdate( nextBatch: '', @@ -154,6 +155,7 @@ class Event extends MatrixEvent { MessageTypes.File, }.contains(messageType) && !room.sendingFilePlaceholders.containsKey(eventId)) { + // ignore: discarded_futures remove(); } } diff --git a/lib/src/room.dart b/lib/src/room.dart index bd92d5872..7694dba03 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1626,6 +1626,7 @@ class Room { return user.asUser; } else { if (mxID.isValidMatrixId) { + // ignore: discarded_futures requestUser( mxID, ignoreErrors: true, diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index c7cc26b7b..c6baf6ba1 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -300,8 +300,11 @@ class Timeline { /// Don't forget to call this before you dismiss this object! void cancelSubscriptions() { + // ignore: discarded_futures sub?.cancel(); + // ignore: discarded_futures roomSub?.cancel(); + // ignore: discarded_futures sessionIdReceivedSub?.cancel(); } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index edbb27ef0..e3daa2e54 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -504,7 +504,7 @@ class DeviceKeys extends SignableKey { lastActive = DateTime.fromMillisecondsSinceEpoch(0); } - KeyVerification startVerification() { + Future startVerification() async { if (!isValid) { throw Exception('setVerification called on invalid key'); } @@ -516,7 +516,7 @@ class DeviceKeys extends SignableKey { final request = KeyVerification( encryption: encryption, userId: userId, deviceId: deviceId!); - request.start(); + await request.start(); encryption.keyVerificationManager.addRequest(request); return request; } diff --git a/lib/src/utils/native_implementations.dart b/lib/src/utils/native_implementations.dart index 9faf431c8..48b840693 100644 --- a/lib/src/utils/native_implementations.dart +++ b/lib/src/utils/native_implementations.dart @@ -50,10 +50,9 @@ abstract class NativeImplementations { bool retryInDummy = false, }); - @override - /// this implementation will catch any non-implemented method - dynamic noSuchMethod(Invocation invocation) { + @override + dynamic noSuchMethod(Invocation invocation) async { final dynamic argument = invocation.positionalArguments.single; final memberName = invocation.memberName.toString().split('"')[1]; diff --git a/lib/src/utils/run_in_root.dart b/lib/src/utils/run_in_root.dart index 614a0db0a..c19eb6695 100644 --- a/lib/src/utils/run_in_root.dart +++ b/lib/src/utils/run_in_root.dart @@ -20,13 +20,13 @@ import 'dart:async'; import 'package:matrix/matrix.dart'; -Future runInRoot(FutureOr Function() fn) async { - return await Zone.root.run(() async { +void runInRoot(FutureOr Function() fn) { + // ignore: discarded_futures + Zone.root.run(() async { try { - return await fn(); + await fn(); } catch (e, s) { Logs().e('Error thrown in root zone', e, s); } - return null; }); } diff --git a/lib/src/utils/uia_request.dart b/lib/src/utils/uia_request.dart index 7f1f0dffb..9cad2e634 100644 --- a/lib/src/utils/uia_request.dart +++ b/lib/src/utils/uia_request.dart @@ -52,6 +52,7 @@ class UiaRequest { } UiaRequest({this.onUpdate, required this.request}) { + // ignore: discarded_futures _run(); } diff --git a/lib/src/utils/web_worker/native_implementations_web_worker.dart b/lib/src/utils/web_worker/native_implementations_web_worker.dart index 24a26fc2f..8672d43dc 100644 --- a/lib/src/utils/web_worker/native_implementations_web_worker.dart +++ b/lib/src/utils/web_worker/native_implementations_web_worker.dart @@ -37,7 +37,7 @@ class NativeImplementationsWebWorker extends NativeImplementations { return completer.future.timeout(timeout); } - void _handleIncomingMessage(MessageEvent event) { + Future _handleIncomingMessage(MessageEvent event) async { final data = event.data; // don't forget handling errors of our second thread... if (data['label'] == 'stacktrace') { @@ -46,12 +46,10 @@ class NativeImplementationsWebWorker extends NativeImplementations { final error = event.data['error']!; - Future.value( - onStackTrace.call(event.data['stacktrace'] as String), - ).then( - (stackTrace) => completer?.completeError( - WebWorkerError(error: error, stackTrace: stackTrace), - ), + final stackTrace = + await onStackTrace.call(event.data['stacktrace'] as String); + completer?.completeError( + WebWorkerError(error: error, stackTrace: stackTrace), ); } else { final response = WebWorkerData.fromJson(event.data); diff --git a/lib/src/voip/call.dart b/lib/src/voip/call.dart index 581c6a329..31e51684c 100644 --- a/lib/src/voip/call.dart +++ b/lib/src/voip/call.dart @@ -522,17 +522,18 @@ class CallSession { await gotCallFeedsForAnswer(callFeeds); } - void replacedBy(CallSession newCall) { + Future replacedBy(CallSession newCall) async { if (state == CallState.kWaitLocalMedia) { Logs().v('Telling new call to wait for local media'); newCall.waitForLocalAVStream = true; } else if (state == CallState.kCreateOffer || state == CallState.kInviteSent) { Logs().v('Handing local stream to new call'); - newCall.gotCallFeedsForAnswer(getLocalStreams); + await newCall.gotCallFeedsForAnswer(getLocalStreams); } successor = newCall; onCallReplaced.add(newCall); + // ignore: unawaited_futures hangup(CallErrorCode.Replaced, true); } @@ -1508,8 +1509,9 @@ class CallSession { return pc; } - void createDataChannel(String label, RTCDataChannelInit dataChannelDict) { - pc?.createDataChannel(label, dataChannelDict); + Future createDataChannel( + String label, RTCDataChannelInit dataChannelDict) async { + await pc?.createDataChannel(label, dataChannelDict); } Future tryRemoveStopedStreams() async { diff --git a/lib/src/voip/conn_tester.dart b/lib/src/voip/conn_tester.dart index 2e00eb13d..889334dc5 100644 --- a/lib/src/voip/conn_tester.dart +++ b/lib/src/voip/conn_tester.dart @@ -45,11 +45,15 @@ class ConnectionTester { await pc1!.setRemoteDescription(answer); - void dispose() { - pc1!.close(); - pc1!.dispose(); - pc2!.close(); - pc2!.dispose(); + Future dispose() async { + await Future.wait([ + pc1!.close(), + pc2!.close(), + ]); + await Future.wait([ + pc1!.dispose(), + pc2!.dispose(), + ]); } bool connected = false; @@ -69,6 +73,7 @@ class ConnectionTester { .e('[VOIP] ConnectionTester Error while testing TURN server: ', e, s); } + // ignore: unawaited_futures dispose(); return connected; } diff --git a/lib/src/voip/group_call.dart b/lib/src/voip/group_call.dart index b61fbd957..b6fe62ace 100644 --- a/lib/src/voip/group_call.dart +++ b/lib/src/voip/group_call.dart @@ -232,11 +232,11 @@ class GroupCall { this.groupCallId = groupCallId ?? genCallID(); } - GroupCall create() { + Future create() async { voip.groupCalls[groupCallId] = this; voip.groupCalls[room.id] = this; - client.setRoomStateWithKey( + await client.setRoomStateWithKey( room.id, EventTypes.GroupCallPrefix, groupCallId, @@ -413,6 +413,7 @@ class GroupCall { if (localUserMediaStream != null) { final oldStream = localUserMediaStream!.stream; localUserMediaStream!.setNewStream(stream.stream!); + // ignore: discarded_futures stopMediaStream(oldStream); } } diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index 2d6d93463..22a6a1650 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -53,6 +53,7 @@ class VoIP { for (final room in client.rooms) { if (room.activeGroupCallEvents.isNotEmpty) { for (final groupCall in room.activeGroupCallEvents) { + // ignore: discarded_futures createGroupCallFromRoomStateEvent(groupCall, emitHandleNewGroupCall: false); } @@ -589,7 +590,7 @@ class VoIP { return null; } final groupId = genCallID(); - final groupCall = GroupCall( + final groupCall = await GroupCall( groupCallId: groupId, client: client, voip: this, diff --git a/test/client_test.dart b/test/client_test.dart index 694aa3496..602268b3e 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -1049,8 +1049,8 @@ void main() { reason: '!5345234235:example.com not found as archived room'); }); - tearDown(() { - matrix.dispose(closeDatabase: true); + tearDown(() async { + await matrix.dispose(closeDatabase: true); }); }); } diff --git a/test/database_api_test.dart b/test/database_api_test.dart index ecdec026d..df84d4b67 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -27,7 +27,7 @@ import 'package:matrix/matrix.dart'; import 'fake_database.dart'; void main() { - group('HiveCollections Database Test', () { + group('HiveCollections Database Test', () async { testDatabase( getHiveCollectionsDatabase(null), ); diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index 36b2ad8e0..69a2498e2 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -249,7 +249,7 @@ void main() { test('start verification', () async { if (!olmEnabled) return; - var req = client + var req = await client .userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'] ?.startVerification(); expect(req != null, true); diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index f92fa3579..de6f5d142 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -260,6 +260,7 @@ class FakeMatrixApi extends BaseClient { } } // and generate a fake sync + // ignore: discarded_futures _client!.handleSync(sdk.SyncUpdate(nextBatch: '')); } return {}; diff --git a/test/room_archived_test.dart b/test/room_archived_test.dart index c99af5abe..6cfa5bd93 100644 --- a/test/room_archived_test.dart +++ b/test/room_archived_test.dart @@ -50,7 +50,7 @@ void main() { insertList.clear(); }); - tearDown(() => client.dispose().onError((e, s) {})); + tearDown(() async => client.dispose().onError((e, s) {})); test('archive room not loaded', () async { final archiveRoom = diff --git a/test/timeline_context_test.dart b/test/timeline_context_test.dart index f62b45c83..e1d07ca67 100644 --- a/test/timeline_context_test.dart +++ b/test/timeline_context_test.dart @@ -38,7 +38,7 @@ void main() { var olmEnabled = true; final countStream = StreamController.broadcast(); - Future waitForCount(int count) { + Future waitForCount(int count) async { if (updateCount == count) { return Future.value(updateCount); } @@ -46,9 +46,9 @@ void main() { final completer = Completer(); StreamSubscription? sub; - sub = countStream.stream.listen((newCount) { + sub = countStream.stream.listen((newCount) async { if (newCount == count) { - sub?.cancel(); + await sub?.cancel(); completer.complete(count); } }); @@ -100,7 +100,8 @@ void main() { testTimeStamp = DateTime.now().millisecondsSinceEpoch; }); - tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {})); + tearDown( + () async => client.dispose(closeDatabase: true).onError((e, s) {})); test('Request future', () async { timeline.events.clear(); diff --git a/test/timeline_test.dart b/test/timeline_test.dart index 97f738975..e7e7c2307 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -39,7 +39,7 @@ void main() { var currentPoison = 0; final countStream = StreamController.broadcast(); - Future waitForCount(int count) { + Future waitForCount(int count) async { if (updateCount == count) { return Future.value(updateCount); } @@ -47,9 +47,9 @@ void main() { final completer = Completer(); StreamSubscription? sub; - sub = countStream.stream.listen((newCount) { + sub = countStream.stream.listen((newCount) async { if (newCount == count) { - sub?.cancel(); + await sub?.cancel(); completer.complete(count); } }); @@ -109,7 +109,8 @@ void main() { testTimeStamp = DateTime.now().millisecondsSinceEpoch; }); - tearDown(() => client.dispose(closeDatabase: true).onError((e, s) {})); + tearDown( + () async => client.dispose(closeDatabase: true).onError((e, s) {})); test('Create', () async { client.onEvent.add(EventUpdate(