diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 166b41b2a..5d62f8661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: name: Build IOS uses: ./.github/workflows/build-native.yml with: - runner: macos-latest + runner: macos-12 binary: ios build: '["ios-device", "ios-simulator", "ios-catalyst"]' @@ -231,7 +231,7 @@ jobs: secrets: inherit with: os: macos - runner: macos-13 # workaround to: https://github.com/flutter/flutter/issues/118469 latest is still macos-12 ¯\_(ツ)_/¯ + runner: macos-latest differentiator: fm${{ github.run_id }}${{ github.run_attempt }} cleanup-cluster-flutter-macos: @@ -281,7 +281,7 @@ jobs: differentiator: fi${{ github.run_id }}${{ github.run_attempt }} flutter-tests-ios: - runs-on: macos-latest + runs-on: macos-12 name: Flutter Tests iOS timeout-minutes: 45 needs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9601710..704010640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,20 @@ ### Fixed * Avoid: Attempt to execute code removed by Dart AOT compiler (TFA). (Issue [#1647](https://github.com/realm/realm-dart/issues/1647)) * Fixed nullability annotations for the experimental API `App.baseUrl` and `App.updateBaseUrl`. The former is guaranteed not to be `null`, while the latter will now accept a `null` argument, in which case the base url will be restored to its default value. (Issue [#1523](https://github.com/realm/realm-dart/issues/1523)) +* `App.users` included logged out users only if they were logged out while the App instance existed. It now always includes all logged out users. (Core 14.6.0) +* Fixed several issues around encrypted file portability (copying a "bundled" encrypted Realm from one device to another): (Core 14.6.0) + * Fixed `Assertion failed: new_size % (1ULL << m_page_shift) == 0` when opening an encrypted Realm less than 64Mb that was generated on a platform with a different page size than the current platform. + * Fixed a `DecryptionFailed` exception thrown when opening a small (<4k of data) Realm generated on a device with a page size of 4k if it was bundled and opened on a device with a larger page size. + * Fixed an issue during a subsequent open of an encrypted Realm for some rare allocation patterns when the top ref was within ~50 bytes of the end of a page. This could manifest as a DecryptionFailed exception or as an assertion: `encrypted_file_mapping.hpp:183: Assertion failed: local_ndx < m_page_state.size()`. +* Schema initialization could hit an assertion failure if the sync client applied a downloaded changeset while the Realm file was in the process of being opened. (Core 14.6.0) +* Improve perfomance of "chained OR equality" queries for UUID/ObjectId types and RQL parsed "IN" queries on string/int/uuid/objectid types. (Core 14.6.0) +* Fixed a bug when running a IN query (or a query of the pattern `x == 1 OR x == 2 OR x == 3`) when evaluating on a string property with an empty string in the search condition. Matches with an empty string would have been evaluated as if searching for a null string instead. (Core 14.6.2) ### Compatibility * Realm Studio: 15.0.0 or later. ### Internal -* Using Core x.y.z. +* Using Core 14.6.2. * Flutter: ^3.19.0 * Dart: ^3.3.0 diff --git a/packages/realm_dart/ffigen.yaml b/packages/realm_dart/ffigen.yaml index 31f708fa9..905257949 100644 --- a/packages/realm_dart/ffigen.yaml +++ b/packages/realm_dart/ffigen.yaml @@ -27,6 +27,7 @@ comments: compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' + - '-DREALM_APP_SERVICES' - '-I src/realm-core/src' - '-I src/dart-dl' sort: true diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/native/realm_bindings.dart index 607c45033..a7fdd06fe 100644 --- a/packages/realm_dart/lib/src/native/realm_bindings.dart +++ b/packages/realm_dart/lib/src/native/realm_bindings.dart @@ -175,6 +175,25 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t)>(); + ffi.Pointer + realm_app_config_get_sync_client_config( + ffi.Pointer arg0, + ) { + return _realm_app_config_get_sync_client_config( + arg0, + ); + } + + late final _realm_app_config_get_sync_client_configPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>( + 'realm_app_config_get_sync_client_config'); + late final _realm_app_config_get_sync_client_config = + _realm_app_config_get_sync_client_configPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer)>(); + /// Create a new app configuration. /// /// @param app_id The MongoDB Realm app id. @@ -197,6 +216,25 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer, ffi.Pointer)>(); + void realm_app_config_set_base_file_path( + ffi.Pointer arg0, + ffi.Pointer arg1, + ) { + return _realm_app_config_set_base_file_path( + arg0, + arg1, + ); + } + + late final _realm_app_config_set_base_file_pathPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, + ffi.Pointer)>>('realm_app_config_set_base_file_path'); + late final _realm_app_config_set_base_file_path = + _realm_app_config_set_base_file_pathPtr.asFunction< + void Function( + ffi.Pointer, ffi.Pointer)>(); + void realm_app_config_set_base_url( ffi.Pointer arg0, ffi.Pointer arg1, @@ -330,6 +368,44 @@ class RealmLibrary { void Function( ffi.Pointer, ffi.Pointer)>(); + void realm_app_config_set_metadata_encryption_key( + ffi.Pointer arg0, + ffi.Pointer arg1, + ) { + return _realm_app_config_set_metadata_encryption_key( + arg0, + arg1, + ); + } + + late final _realm_app_config_set_metadata_encryption_keyPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>>( + 'realm_app_config_set_metadata_encryption_key'); + late final _realm_app_config_set_metadata_encryption_key = + _realm_app_config_set_metadata_encryption_keyPtr.asFunction< + void Function( + ffi.Pointer, ffi.Pointer)>(); + + void realm_app_config_set_metadata_mode( + ffi.Pointer arg0, + int arg1, + ) { + return _realm_app_config_set_metadata_mode( + arg0, + arg1, + ); + } + + late final _realm_app_config_set_metadata_modePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, + ffi.Int32)>>('realm_app_config_set_metadata_mode'); + late final _realm_app_config_set_metadata_mode = + _realm_app_config_set_metadata_modePtr + .asFunction, int)>(); + void realm_app_config_set_platform_version( ffi.Pointer arg0, ffi.Pointer arg1, @@ -387,48 +463,61 @@ class RealmLibrary { void Function( ffi.Pointer, ffi.Pointer)>(); - /// Create realm_app_t* instance given a valid realm configuration and sync client configuration. + void realm_app_config_set_security_access_group( + ffi.Pointer arg0, + ffi.Pointer arg1, + ) { + return _realm_app_config_set_security_access_group( + arg0, + arg1, + ); + } + + late final _realm_app_config_set_security_access_groupPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>>( + 'realm_app_config_set_security_access_group'); + late final _realm_app_config_set_security_access_group = + _realm_app_config_set_security_access_groupPtr.asFunction< + void Function( + ffi.Pointer, ffi.Pointer)>(); + + /// Create realm_app_t* instance given a valid realm app configuration. /// /// @return A non-null pointer if no error occurred. ffi.Pointer realm_app_create( ffi.Pointer arg0, - ffi.Pointer arg1, ) { return _realm_app_create( arg0, - arg1, ); } late final _realm_app_createPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, - ffi.Pointer)>>('realm_app_create'); + ffi.Pointer Function( + ffi.Pointer)>>('realm_app_create'); late final _realm_app_create = _realm_app_createPtr.asFunction< - ffi.Pointer Function(ffi.Pointer, - ffi.Pointer)>(); + ffi.Pointer Function(ffi.Pointer)>(); - /// Create cached realm_app_t* instance given a valid realm configuration and sync client configuration. + /// Create cached realm_app_t* instance given a valid realm app configuration. /// /// @return A non-null pointer if no error occurred. ffi.Pointer realm_app_create_cached( ffi.Pointer arg0, - ffi.Pointer arg1, ) { return _realm_app_create_cached( arg0, - arg1, ); } late final _realm_app_create_cachedPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, - ffi.Pointer)>>( - 'realm_app_create_cached'); + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('realm_app_create_cached'); late final _realm_app_create_cached = _realm_app_create_cachedPtr.asFunction< - ffi.Pointer Function(ffi.Pointer, - ffi.Pointer)>(); + ffi.Pointer Function(ffi.Pointer)>(); ffi.Pointer realm_app_credentials_new_anonymous( bool reuse_credentials, @@ -1413,31 +1502,24 @@ class RealmLibrary { /// Switches the active user with the specified one. The user must exist in the list of all users who have logged into /// this application. /// @param app ptr to realm_app - /// @param user ptr to current user - /// @param new_user ptr to the new user to switch + /// @param user ptr to user to set as current. /// @return True if no error has been recorded, False otherwise bool realm_app_switch_user( ffi.Pointer app, ffi.Pointer user, - ffi.Pointer> new_user, ) { return _realm_app_switch_user( app, user, - new_user, ); } late final _realm_app_switch_userPtr = _lookup< - ffi.NativeFunction< - ffi.Bool Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer>)>>( - 'realm_app_switch_user'); + ffi.NativeFunction< + ffi.Bool Function(ffi.Pointer, + ffi.Pointer)>>('realm_app_switch_user'); late final _realm_app_switch_user = _realm_app_switch_userPtr.asFunction< - bool Function(ffi.Pointer, ffi.Pointer, - ffi.Pointer>)>(); + bool Function(ffi.Pointer, ffi.Pointer)>(); /// Get the default realm file path based on the user and partition value in the config. /// @@ -9280,26 +9362,6 @@ class RealmLibrary { late final _realm_sync_client_config_new = _realm_sync_client_config_newPtr .asFunction Function()>(); - void realm_sync_client_config_set_base_file_path( - ffi.Pointer arg0, - ffi.Pointer arg1, - ) { - return _realm_sync_client_config_set_base_file_path( - arg0, - arg1, - ); - } - - late final _realm_sync_client_config_set_base_file_pathPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - ffi.Pointer)>>( - 'realm_sync_client_config_set_base_file_path'); - late final _realm_sync_client_config_set_base_file_path = - _realm_sync_client_config_set_base_file_pathPtr.asFunction< - void Function(ffi.Pointer, - ffi.Pointer)>(); - void realm_sync_client_config_set_connect_timeout( ffi.Pointer arg0, int arg1, @@ -9416,44 +9478,6 @@ class RealmLibrary { _realm_sync_client_config_set_max_resumption_delay_intervalPtr.asFunction< void Function(ffi.Pointer, int)>(); - void realm_sync_client_config_set_metadata_encryption_key( - ffi.Pointer arg0, - ffi.Pointer arg1, - ) { - return _realm_sync_client_config_set_metadata_encryption_key( - arg0, - arg1, - ); - } - - late final _realm_sync_client_config_set_metadata_encryption_keyPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - ffi.Pointer)>>( - 'realm_sync_client_config_set_metadata_encryption_key'); - late final _realm_sync_client_config_set_metadata_encryption_key = - _realm_sync_client_config_set_metadata_encryption_keyPtr.asFunction< - void Function(ffi.Pointer, - ffi.Pointer)>(); - - void realm_sync_client_config_set_metadata_mode( - ffi.Pointer arg0, - int arg1, - ) { - return _realm_sync_client_config_set_metadata_mode( - arg0, - arg1, - ); - } - - late final _realm_sync_client_config_set_metadata_modePtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - ffi.Int32)>>('realm_sync_client_config_set_metadata_mode'); - late final _realm_sync_client_config_set_metadata_mode = - _realm_sync_client_config_set_metadata_modePtr.asFunction< - void Function(ffi.Pointer, int)>(); - void realm_sync_client_config_set_multiplex_sessions( ffi.Pointer arg0, bool arg1, @@ -11318,7 +11342,7 @@ class RealmLibrary { ffi.Pointer)>(); /// @return a notification token object. Dispose it to stop receiving notifications. - ffi.Pointer + ffi.Pointer realm_sync_user_on_state_change_register_callback( ffi.Pointer arg0, realm_sync_on_user_state_changed_t arg1, @@ -11335,7 +11359,7 @@ class RealmLibrary { late final _realm_sync_user_on_state_change_register_callbackPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( + ffi.Pointer Function( ffi.Pointer, realm_sync_on_user_state_changed_t, ffi.Pointer, @@ -11343,7 +11367,7 @@ class RealmLibrary { 'realm_sync_user_on_state_change_register_callback'); late final _realm_sync_user_on_state_change_register_callback = _realm_sync_user_on_state_change_register_callbackPtr.asFunction< - ffi.Pointer Function( + ffi.Pointer Function( ffi.Pointer, realm_sync_on_user_state_changed_t, ffi.Pointer, @@ -11543,7 +11567,7 @@ class RealmLibrary { /// Return the identiy for the user passed as argument /// @param user ptr to the user for which the identiy has to be retrieved - /// @return a ptr to the identity string + /// @return a ptr to the identity string. This must be manually released with realm_free(). ffi.Pointer realm_user_get_identity( ffi.Pointer user, ) { @@ -11953,8 +11977,6 @@ final class realm_app_error extends ffi.Struct { /// Pointers to this struct and its pointer members are only valid inside the scope /// of the callback they were passed to. typedef realm_app_error_t = realm_app_error; - -/// App typedef realm_app_t = realm_app; final class realm_app_user_apikey extends ffi.Struct { @@ -11987,6 +12009,10 @@ typedef Dartrealm_app_user_completion_func_tFunction = void Function( ffi.Pointer user, ffi.Pointer error); +final class realm_app_user_subscription_token extends ffi.Opaque {} + +typedef realm_app_user_subscription_token_t = realm_app_user_subscription_token; + /// Generic completion callback for asynchronous Realm App operations. /// /// @param error Pointer to an error object if the operation failed, otherwise null if it completed successfully. @@ -12265,9 +12291,9 @@ abstract class realm_errno { static const int RLM_ERR_CUSTOM_ERROR = 4000; static const int RLM_ERR_CLIENT_USER_NOT_FOUND = 4100; static const int RLM_ERR_CLIENT_USER_NOT_LOGGED_IN = 4101; - static const int RLM_ERR_CLIENT_APP_DEALLOCATED = 4102; static const int RLM_ERR_CLIENT_REDIRECT_ERROR = 4103; static const int RLM_ERR_CLIENT_TOO_MANY_REDIRECTS = 4104; + static const int RLM_ERR_CLIENT_USER_ALREADY_NAMED = 4105; static const int RLM_ERR_BAD_TOKEN = 4200; static const int RLM_ERR_MALFORMED_JSON = 4201; static const int RLM_ERR_MISSING_JSON_KEY = 4202; @@ -12894,13 +12920,13 @@ final class realm_sync_client_config extends ffi.Opaque {} typedef realm_sync_client_config_t = realm_sync_client_config; -/// Sync abstract class realm_sync_client_metadata_mode { static const int RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT = 0; static const int RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED = 1; static const int RLM_SYNC_CLIENT_METADATA_MODE_DISABLED = 2; } +/// Sync abstract class realm_sync_client_reconnect_mode { static const int RLM_SYNC_CLIENT_RECONNECT_MODE_NORMAL = 0; static const int RLM_SYNC_CLIENT_RECONNECT_MODE_TESTING = 1; @@ -13007,6 +13033,14 @@ final class realm_sync_error_user_info extends ffi.Struct { } typedef realm_sync_error_user_info_t = realm_sync_error_user_info; + +abstract class realm_sync_file_action { + static const int RLM_SYNC_FILE_ACTION_DELETE_REALM = 0; + static const int RLM_SYNC_FILE_ACTION_BACK_UP_THEN_DELETE_REALM = 1; +} + +final class realm_sync_manager extends ffi.Opaque {} + typedef realm_sync_on_subscription_state_changed_t = ffi.Pointer< ffi.NativeFunction>; typedef realm_sync_on_subscription_state_changed_tFunction = ffi.Void Function( @@ -13212,11 +13246,6 @@ typedef Dartrealm_sync_ssl_verify_func_tFunction = bool Function( int preverify_ok, int depth); -final class realm_sync_user_subscription_token extends ffi.Opaque {} - -typedef realm_sync_user_subscription_token_t - = realm_sync_user_subscription_token; - /// Callback function invoked by the sync session once it has uploaded or download /// all available changesets. See @a realm_sync_session_wait_for_upload and /// @a realm_sync_session_wait_for_download. @@ -13265,6 +13294,7 @@ abstract class realm_user_state { static const int RLM_USER_STATE_REMOVED = 2; } +/// App typedef realm_user_t = realm_user; final class realm_uuid extends ffi.Struct { diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/native/realm_core.dart index 88d3bdb0a..7f4ccafbd 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/native/realm_core.dart @@ -1903,6 +1903,13 @@ class _RealmCore { _realmLib.realm_app_config_set_bundle_id(handle._pointer, getBundleId().toCharPtr(arena)); + _realmLib.realm_app_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toCharPtr(arena)); + _realmLib.realm_app_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); + _realmLib.realm_app_config_set_default_request_timeout(handle._pointer, configuration.defaultRequestTimeout.inMilliseconds); + if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { + _realmLib.realm_app_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); + } + return handle; }); } @@ -2109,20 +2116,6 @@ class _RealmCore { }); } - SyncClientConfigHandle _createSyncClientConfig(AppConfiguration configuration) { - return using((arena) { - final handle = SyncClientConfigHandle._(_realmLib.realm_sync_client_config_new()); - - _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toCharPtr(arena)); - _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - _realmLib.realm_sync_client_config_set_connect_timeout(handle._pointer, configuration.maxConnectionTimeout.inMilliseconds); - if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { - _realmLib.realm_sync_client_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); - } - return handle; - }); - } - // TODO: // We need a pure Dart equivalent of: // `ServiceBinding.rootIsolateToken != null` @@ -2139,8 +2132,8 @@ class _RealmCore { } final httpTransportHandle = _createHttpTransport(configuration.httpClient); final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); - final syncClientConfigHandle = _createSyncClientConfig(configuration); - final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_create_cached(appConfigHandle._pointer, syncClientConfigHandle._pointer)); + final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_create_cached(appConfigHandle._pointer)); + return AppHandle._(realmAppPtr); } @@ -2408,7 +2401,6 @@ class _RealmCore { () => _realmLib.realm_app_switch_user( application.handle._pointer, user.handle._pointer, - nullptr, ), "Switch user failed"); }); @@ -3325,8 +3317,8 @@ class RealmNotificationTokenHandle extends RootedHandleBase pointer, RealmHandle root) : super(root, pointer, 32); } -class UserNotificationTokenHandle extends HandleBase { - UserNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); +class UserNotificationTokenHandle extends HandleBase { + UserNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); } class RealmSyncSessionConnectionStateNotificationTokenHandle extends HandleBase { diff --git a/packages/realm_dart/src/realm-core b/packages/realm_dart/src/realm-core index 4d815c6e6..9cf7ef4ad 160000 --- a/packages/realm_dart/src/realm-core +++ b/packages/realm_dart/src/realm-core @@ -1 +1 @@ -Subproject commit 4d815c6e6883bfdcb67cf755d0f0f29a4b399ea7 +Subproject commit 9cf7ef4ad8e2f4c7a519c9a395ca3d253bb87aa8 diff --git a/packages/realm_dart/test/app_test.dart b/packages/realm_dart/test/app_test.dart index e1c3ac232..f2b9e0afb 100644 --- a/packages/realm_dart/test/app_test.dart +++ b/packages/realm_dart/test/app_test.dart @@ -168,7 +168,7 @@ void main() { final user = await app.logIn(Credentials.anonymous()); final user1 = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); - expect(app.users, [user1, user]); + expect(app.users, {user1, user}); }); baasTest('App delete user', (configuration) async { diff --git a/packages/realm_dart/test/realm_test.dart b/packages/realm_dart/test/realm_test.dart index 3d69c108e..6d5a6e6d6 100644 --- a/packages/realm_dart/test/realm_test.dart +++ b/packages/realm_dart/test/realm_test.dart @@ -1328,17 +1328,18 @@ void main() { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, getSyncSchema()); + int count = 0; double progress = -1; - final completer = Completer(); + var syncedRealm = await getRealmAsync(configuration, onProgressCallback: (syncProgress) { + count++; progress = syncProgress.progressEstimate; - if (syncProgress.progressEstimate == 1.0) { - completer.complete(); - } }); - completer.future.timeout(Duration(milliseconds: 300), onTimeout: () => throw Exception("onProgressCallback did not happen.")); + expect(syncedRealm.isClosed, false); - expect(progress, greaterThan(-1)); + // Semantics of onProgressCallback changed with https://github.com/realm/realm-core/issues/7452 + expect(count, 0); + expect(progress, -1); }); baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { diff --git a/packages/realm_dart/test/session_test.dart b/packages/realm_dart/test/session_test.dart index 138179311..040f15429 100644 --- a/packages/realm_dart/test/session_test.dart +++ b/packages/realm_dart/test/session_test.dart @@ -201,12 +201,10 @@ void main() { } final uploadData = subscribeToProgress(realmA, ProgressDirection.upload, ProgressMode.forCurrentlyOutstandingWork); + final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); await realmA.syncSession.waitForUpload(); - // Subscribe immediately after the upload to ensure we get the entire upload message as progress notifications - final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); - await validateData(uploadData, expectDone: true); await realmB.syncSession.waitForDownload();