diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4728791..cf8a88432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,35 @@ ### Enhancements * Suppressing rules for a *.g.dart files ([#1413](https://github.com/realm/realm-dart/pull/1413)) +* Full text search supports searching for prefix only. Eg. "description TEXT 'alex*'" (Core upgrade) +* Unknown protocol errors received from the baas server will no longer cause the application to crash if a valid error action is also received. (Core upgrade) +* Added support for server log messages that are enabled by sync protocol version 10. AppServices request id will be provided in a server log message in a future server release. (Core upgrade) +* Simplified sync errors. The following sync errors and error codes are deprecated ([#1387](https://github.com/realm/realm-dart/pull/1387)): + * `SyncClientError`, `SyncConnectionError`, `SyncSessionError`, `SyncWebSocketError`, `GeneralSyncError` - replaced by `SyncError`. + * `SyncClientErrorCode`, `SyncConnectionErrorCode`, `SyncSessionErrorCode`, `SyncWebSocketErrorCode`, `GeneralSyncErrorCode, SyncErrorCategory` - replaced by `SyncErrorCode`. +* Throw an exception if `File::unlock` has failed, in order to inform the SDK that we are likely hitting some limitation on the OS filesystem, instead of crashing the application and use the same file locking logic for all the platforms. (Core upgrade) ### Fixed * Fixed iteration after `skip` bug ([#1409](https://github.com/realm/realm-dart/issues/1409)) +* Crash when querying the size of a Object property through a link chain (Core upgrade, since v13.17.2) +* Deprecated `App.localAppName` and `App.localAppVersion`. They were not used by the server and were not needed to set them. ([#1387](https://github.com/realm/realm-dart/pull/1387)) +* Fixed crash in slab allocator (`Assertion failed: ref + size <= next->first`). (Core upgrade, since 13.0.0) +* Sending empty UPLOAD messages may lead to 'Bad server version' errors and client reset. (Core upgrade, since v11.8.0) +* If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state. (Core 13.21.0) +* Receiving a `write_not_allowed` error from the server would have led to a crash. (Core 13.22.0) +* Fix interprocess locking for concurrent realm file access resulting in a interprocess deadlock on FAT32/exFAT filesystems. (Core 13.23.0) * Fixed RealmObject not overriding `hashCode`, which would lead to sets of RealmObjects potentially containing duplicates. ([#1418](https://github.com/realm/realm-dart/issues/1418)) ### Compatibility * Realm Studio: 13.0.0 or later. ### Internal -* Using Core x.y.z. +* Made binding a `sync::Session` exception safe so if a `MultipleSyncAgents` exception is thrown, the sync client can be torn down safely. (Core upgrade, since 13.4.1) +* Add information about the reason a synchronization session is used for to flexible sync client BIND message. (Core upgrade) +* Sync protocol version bumped to 10. (Core upgrade) +* Handle `badChangeset` error when printing changeset contents in debug. (Core upgrade) + +* Using Core 13.20.1. ## 1.5.0 (2023-09-18) diff --git a/lib/src/app.dart b/lib/src/app.dart index aa4969772..298234efd 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -62,10 +62,12 @@ class AppConfiguration { /// These can be the same conceptual app developed for different platforms, or /// significantly different client side applications that operate on the same data - e.g. an event managing /// service that has different clients apps for organizers and attendees. + @Deprecated("localAppName is not used.") final String? localAppName; /// The [localAppVersion] can be specified, if you wish to distinguish different client versions of the /// same application. + @Deprecated("localAppVersion is not used.") final String? localAppVersion; /// Enumeration that specifies how and if logged-in User objects are persisted across application launches. diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index cd63401d3..c63242407 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -22,7 +22,6 @@ import 'dart:io'; // ignore: no_leading_underscores_for_library_prefixes import 'package:path/path.dart' as _path; - import 'native/realm_core.dart'; import 'realm_class.dart'; import 'init.dart'; @@ -603,6 +602,7 @@ class ClientResetError extends SyncError { final App? _app; /// If true the received error is fatal. + @Deprecated("This will be removed in the future.") final bool isFatal = true; /// The path to the original copy of the realm when the client reset was triggered. @@ -612,34 +612,38 @@ class ClientResetError extends SyncError { /// The path where the backup copy of the realm will be placed once the client reset process is complete. final String? backupFilePath; - /// The [ClientResetError] has error code of [SyncClientErrorCode.autoClientResetFailure] - /// when a client reset fails and `onManualResetFallback` occurs. Otherwise, it is [SyncClientErrorCode.unknown] - SyncClientErrorCode get code => SyncClientErrorCode.fromInt(codeValue); - /// The [SyncSessionErrorCode] value indicating the type of the sync error. /// This property will be [SyncSessionErrorCode.unknown] if `onManualResetFallback` occurs on client reset. - SyncSessionErrorCode get sessionErrorCode => SyncSessionErrorCode.fromInt(codeValue); + @Deprecated("This will be removed in the future.") + SyncSessionErrorCode get sessionErrorCode => SyncSessionErrorCode.unknown; @Deprecated("ClientResetError constructor is deprecated and will be removed in the future") ClientResetError( - String message, { - App? app, + String message, + this._app, { SyncErrorCategory category = SyncErrorCategory.client, int? errorCodeValue, this.backupFilePath, this.originalFilePath, String? detailedMessage, - }) : _app = app, - super( + }) : super( message, category, errorCodeValue ?? SyncClientErrorCode.autoClientResetFailure.code, detailedMessage: detailedMessage, ); + ClientResetError._( + String message, + SyncErrorCode code, + this._app, { + this.backupFilePath, + this.originalFilePath, + }) : super._(message, code); + @override String toString() { - return "ClientResetError message: $message category: $category code: $code isFatal: $isFatal"; + return "ClientResetError message: $message"; } /// Initiates the client reset process. @@ -649,70 +653,128 @@ class ClientResetError extends SyncError { if (_app == null) { throw RealmException("This `ClientResetError` does not have an `Application` instance."); } + if (originalFilePath == null) { throw RealmException("Missing `originalFilePath`"); } + return realmCore.immediatelyRunFileActions(_app!, originalFilePath!); } } /// Thrown when an error occurs during synchronization +/// This error or its subclasses will be returned to users through [FlexibleSyncConfiguration.syncErrorHandler] +/// and the exact reason must be found in the `message`. /// {@category Sync} class SyncError extends RealmError { + /// The code that describes this error. + final SyncErrorCode code; + + SyncError._(String message, this.code) : super(message); + /// The numeric code value indicating the type of the sync error. - final int codeValue; + @Deprecated("Errors of SyncError subclasses will be created base on the error code. Error codes won't be returned anymore.") + int get codeValue => code.code; /// The category of the sync error - final SyncErrorCategory category; + @Deprecated("SyncErrorCategory enum is deprecated.") + late SyncErrorCategory category = SyncErrorCategory.system; /// Detailed error message. /// In case of server error, it contains the link to the server log. - final String? detailedMessage; + @Deprecated("Detailed message is empty. Use `message` property.") + late String? detailedMessage; @Deprecated("SyncError constructor is deprecated and will be removed in the future") - SyncError(String message, this.category, this.codeValue, {this.detailedMessage}) : super(message); + SyncError(String message, this.category, int codeValue, {this.detailedMessage}) + : code = SyncErrorCode.fromInt(codeValue), + super(message); /// Creates a specific type of [SyncError] instance based on the [category] and the [code] supplied. @Deprecated("This method is deprecated and will be removed in the future") static SyncError create(String message, SyncErrorCategory category, int code, {bool isFatal = false}) { - switch (category) { - case SyncErrorCategory.client: - final SyncClientErrorCode errorCode = SyncClientErrorCode.fromInt(code); - if (errorCode == SyncClientErrorCode.autoClientResetFailure) { - return ClientResetError(message); - } - return SyncClientError(message, category, errorCode, isFatal: isFatal); - case SyncErrorCategory.connection: - return SyncConnectionError(message, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal); - case SyncErrorCategory.session: - return SyncSessionError(message, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal); - case SyncErrorCategory.webSocket: - return SyncWebSocketError(message, category, SyncWebSocketErrorCode.fromInt(code)); - case SyncErrorCategory.system: - case SyncErrorCategory.unknown: - default: - return GeneralSyncError(message, category, code); - } + return SyncError._(message, SyncErrorCode.fromInt(code)); } - /// As a specific [SyncError] type. - T as() => this as T; + @override + String toString() { + return "Sync Error: $message"; + } +} + +/// Contains the details for a compensating write performed by the server. +/// {@category Sync} +class CompensatingWriteInfo { + /// The type of the object which was affected by the compensating write. + final String objectType; + + /// The reason for the server to perform a compensating write. + final String reason; + + /// The primary key of the object which was affected by the compensating write. + final RealmValue primaryKey; + + const CompensatingWriteInfo(this.objectType, this.reason, this.primaryKey); + + @override + String toString() { + return "CompensatingWriteInfo: objectType: '$objectType' reason: '$reason' primaryKey: '$primaryKey'"; + } +} + +/// An error type that describes a compensating write error, +/// which indicates that one more object changes have been reverted +/// by the server. +/// {@category Sync} +final class CompensatingWriteError extends SyncError { + /// The list of the compensating writes performed by the server. + late final List? compensatingWrites; + + CompensatingWriteError._( + String message, { + this.compensatingWrites, + }) : super._(message, SyncErrorCode.compensatingWrite); @override String toString() { - return "SyncError message: $message category: $category code: $codeValue"; + return "CompensatingWriteError: $message. ${compensatingWrites ?? ''}"; + } +} + +/// @nodoc +extension SyncErrorInternal on SyncError { + static SyncError createSyncError(SyncErrorDetails error, {App? app}) { + //Client reset can be requested with isClientResetRequested disregarding the ErrorCode + SyncErrorCode errorCode = SyncErrorCode.fromInt(error.code); + + return switch (errorCode) { + SyncErrorCode.autoClientResetFailed => ClientResetError._( + error.message, + errorCode, + app, + originalFilePath: error.originalFilePath, + backupFilePath: error.backupFilePath, + ), + SyncErrorCode.clientReset => + ClientResetError._(error.message, errorCode, app, originalFilePath: error.originalFilePath, backupFilePath: error.backupFilePath), + SyncErrorCode.compensatingWrite => CompensatingWriteError._( + error.message, + compensatingWrites: error.compensatingWrites, + ), + _ => SyncError._(error.message, errorCode), + }; } } +// Deprecated errors - to be removed in 2.0 + /// An error type that describes a session-level error condition. /// {@category Sync} +@Deprecated("Use SyncError.") class SyncClientError extends SyncError { /// If true the received error is fatal. final bool isFatal; - /// The [SyncClientErrorCode] value indicating the type of the sync error. - SyncClientErrorCode get code => SyncClientErrorCode.fromInt(codeValue); - @Deprecated("SyncClientError constructor is deprecated and will be removed in the future") SyncClientError( String message, @@ -730,13 +792,11 @@ class SyncClientError extends SyncError { /// An error type that describes a connection-level error condition. /// {@category Sync} +@Deprecated("Use SyncError.") class SyncConnectionError extends SyncError { /// If true the received error is fatal. final bool isFatal; - /// The [SyncConnectionErrorCode] value indicating the type of the sync error. - SyncConnectionErrorCode get code => SyncConnectionErrorCode.fromInt(codeValue); - @Deprecated("SyncConnectionError constructor is deprecated and will be removed in the future") SyncConnectionError( String message, @@ -754,13 +814,11 @@ class SyncConnectionError extends SyncError { /// An error type that describes a session-level error condition. /// {@category Sync} +@Deprecated("Use SyncError.") class SyncSessionError extends SyncError { /// If true the received error is fatal. final bool isFatal; - /// The [SyncSessionErrorCode] value indicating the type of the sync error. - SyncSessionErrorCode get code => SyncSessionErrorCode.fromInt(codeValue); - @Deprecated("SyncSessionError constructor is deprecated and will be removed in the future") SyncSessionError( String message, @@ -780,11 +838,8 @@ class SyncSessionError extends SyncError { /// /// This class is deprecated and it will be removed. The sync errors caused by network resolution problems /// will be received as [SyncWebSocketError]. -@Deprecated("Use SyncWebSocketError instead") +@Deprecated("Use SyncError.") class SyncResolveError extends SyncError { - /// The numeric value indicating the type of the network resolution sync error. - SyncResolveErrorCode get code => SyncResolveErrorCode.fromInt(codeValue); - SyncResolveError( String message, SyncErrorCategory category, @@ -798,10 +853,8 @@ class SyncResolveError extends SyncError { } /// Web socket error +@Deprecated("Use SyncError.") class SyncWebSocketError extends SyncError { - /// The numeric value indicating the type of the web socket error. - SyncWebSocketErrorCode get code => SyncWebSocketErrorCode.fromInt(codeValue); - @Deprecated("SyncWebSocketError constructor is deprecated and will be removed in the future") SyncWebSocketError( String message, @@ -817,10 +870,8 @@ class SyncWebSocketError extends SyncError { } /// A general or unknown sync error +@Deprecated("Use SyncError.") class GeneralSyncError extends SyncError { - /// The numeric value indicating the type of the general sync error. - int get code => codeValue; - @Deprecated("GeneralSyncError constructor is deprecated and will be removed in the future") GeneralSyncError( String message, @@ -836,6 +887,7 @@ class GeneralSyncError extends SyncError { } /// General sync error codes +@Deprecated("Use SyncError.") enum GeneralSyncErrorCode { /// Unknown Sync error code unknown(9999); @@ -849,84 +901,3 @@ enum GeneralSyncErrorCode { final int code; const GeneralSyncErrorCode(this.code); } - -/// Contains the details for a compensating write performed by the server. -/// {@category Sync} -class CompensatingWriteInfo { - /// The type of the object which was affected by the compensating write. - final String objectType; - - /// The reason for the server to perform a compensating write. - final String reason; - - /// The primary key of the object which was affected by the compensating write. - final RealmValue primaryKey; - - const CompensatingWriteInfo(this.objectType, this.reason, this.primaryKey); - - @override - String toString() { - return "CompensatingWriteInfo: objectType: '$objectType' reason: '$reason' primaryKey: '$primaryKey'"; - } -} - -/// An error type that describes a compensating write error, -/// which indicates that one more object changes have been reverted -/// by the server. -/// {@category Sync} -class CompensatingWriteError extends SyncError { - /// The [CompensatingWriteError] has error code of [SyncSessionErrorCode.compensatingWrite] - SyncSessionErrorCode get code => SyncSessionErrorCode.compensatingWrite; - - /// The list of the compensating writes performed by the server. - late final List? compensatingWrites; - - CompensatingWriteError._( - String message, { - String? detailedMessage, - this.compensatingWrites, - }) : super(message, SyncErrorCategory.session, SyncSessionErrorCode.compensatingWrite.code, detailedMessage: detailedMessage); - - @override - String toString() { - return "CompensatingWriteError message: $message category: $category code: $code. ${compensatingWrites ?? ''}"; - } -} - -/// @nodoc -extension SyncErrorInternal on SyncError { - static SyncError createSyncError(SyncErrorDetails error, {App? app}) { - if (error.isClientResetRequested) { - //Client reset can be requested with isClientResetRequested disregarding the SyncClientErrorCode and SyncSessionErrorCode values - return ClientResetError(error.message, - app: app, - category: error.category, - errorCodeValue: error.code, - originalFilePath: error.originalFilePath, - backupFilePath: error.backupFilePath, - detailedMessage: error.detailedMessage); - } - - switch (error.category) { - case SyncErrorCategory.client: - final errorCode = SyncClientErrorCode.fromInt(error.code); - return SyncClientError(error.message, error.category, errorCode, detailedMessage: error.detailedMessage, isFatal: error.isFatal); - case SyncErrorCategory.connection: - final errorCode = SyncConnectionErrorCode.fromInt(error.code); - return SyncConnectionError(error.message, error.category, errorCode, detailedMessage: error.detailedMessage, isFatal: error.isFatal); - case SyncErrorCategory.session: - final errorCode = SyncSessionErrorCode.fromInt(error.code); - if (errorCode == SyncSessionErrorCode.compensatingWrite) { - return CompensatingWriteError._(error.message, detailedMessage: error.detailedMessage, compensatingWrites: error.compensatingWrites); - } - return SyncSessionError(error.message, error.category, errorCode, detailedMessage: error.detailedMessage, isFatal: error.isFatal); - case SyncErrorCategory.webSocket: - final errorCode = SyncWebSocketErrorCode.fromInt(error.code); - return SyncWebSocketError(error.message, error.category, errorCode, detailedMessage: error.detailedMessage); - case SyncErrorCategory.system: - case SyncErrorCategory.unknown: - default: - return GeneralSyncError(error.message, error.category, error.code, detailedMessage: error.detailedMessage); - } - } -} diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index d0a0a61ad..8a5ef8b25 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -326,45 +326,6 @@ class RealmLibrary { void Function( ffi.Pointer, ffi.Pointer)>(); - void realm_app_config_set_local_app_name( - ffi.Pointer arg0, - ffi.Pointer arg1, - ) { - return _realm_app_config_set_local_app_name( - arg0, - arg1, - ); - } - - late final _realm_app_config_set_local_app_namePtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - ffi.Pointer)>>('realm_app_config_set_local_app_name'); - late final _realm_app_config_set_local_app_name = - _realm_app_config_set_local_app_namePtr.asFunction< - void Function( - ffi.Pointer, ffi.Pointer)>(); - - void realm_app_config_set_local_app_version( - ffi.Pointer arg0, - ffi.Pointer arg1, - ) { - return _realm_app_config_set_local_app_version( - arg0, - arg1, - ); - } - - late final _realm_app_config_set_local_app_versionPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, ffi.Pointer)>>( - 'realm_app_config_set_local_app_version'); - late final _realm_app_config_set_local_app_version = - _realm_app_config_set_local_app_versionPtr.asFunction< - void Function( - ffi.Pointer, ffi.Pointer)>(); - void realm_app_config_set_platform_version( ffi.Pointer arg0, ffi.Pointer arg1, @@ -3834,7 +3795,7 @@ class RealmLibrary { void realm_dart_sync_wait_for_completion_callback( ffi.Pointer userdata, - ffi.Pointer error, + ffi.Pointer error, ) { return _realm_dart_sync_wait_for_completion_callback( userdata, @@ -3844,13 +3805,12 @@ class RealmLibrary { late final _realm_dart_sync_wait_for_completion_callbackPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - ffi.Pointer)>>( + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>>( 'realm_dart_sync_wait_for_completion_callback'); late final _realm_dart_sync_wait_for_completion_callback = _realm_dart_sync_wait_for_completion_callbackPtr.asFunction< - void Function( - ffi.Pointer, ffi.Pointer)>(); + void Function(ffi.Pointer, ffi.Pointer)>(); void realm_dart_userdata_async_free( ffi.Pointer userdata, @@ -4768,8 +4728,9 @@ class RealmLibrary { /// /// @param err A pointer to a `realm_error_t` struct that will be populated with /// information about the error. May not be NULL. + /// @return A bool indicating whether or not an error is available to be returned /// @see realm_get_last_error() - void realm_get_async_error( + bool realm_get_async_error( ffi.Pointer err, ffi.Pointer out_err, ) { @@ -4781,10 +4742,10 @@ class RealmLibrary { late final _realm_get_async_errorPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, + ffi.Bool Function(ffi.Pointer, ffi.Pointer)>>('realm_get_async_error'); late final _realm_get_async_error = _realm_get_async_errorPtr.asFunction< - void Function( + bool Function( ffi.Pointer, ffi.Pointer)>(); /// Fetch the backlinks for the object passed as argument. @@ -9687,22 +9648,19 @@ class RealmLibrary { /// Wrapper for SyncSession::OnlyForTesting::handle_error. This routine should be used only for testing. /// @param session ptr to a valid sync session - /// @param error_code error code to simulate - /// @param category category of the error to simulate - /// @param error_message string representing the error + /// @param error_code realm_errno_e representing the error to simulate + /// @param error_str error message to be included with Status /// @param is_fatal boolean to signal if the error is fatal or not void realm_sync_session_handle_error_for_testing( ffi.Pointer session, int error_code, - int category, - ffi.Pointer error_message, + ffi.Pointer error_str, bool is_fatal, ) { return _realm_sync_session_handle_error_for_testing( session, error_code, - category, - error_message, + error_str, is_fatal, ); } @@ -9711,13 +9669,12 @@ class RealmLibrary { ffi.NativeFunction< ffi.Void Function( ffi.Pointer, - ffi.Int, - ffi.Int, + ffi.Int32, ffi.Pointer, ffi.Bool)>>('realm_sync_session_handle_error_for_testing'); late final _realm_sync_session_handle_error_for_testing = _realm_sync_session_handle_error_for_testingPtr.asFunction< - void Function(ffi.Pointer, int, int, + void Function(ffi.Pointer, int, ffi.Pointer, bool)>(); /// Ask the session to pause synchronization. @@ -9901,29 +9858,24 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t)>(); - void realm_sync_socket_callback_complete( - ffi.Pointer realm_callback, - int status, - ffi.Pointer reason, - ) { - return _realm_sync_socket_callback_complete( - realm_callback, - status, - reason, - ); - } - - late final _realm_sync_socket_callback_completePtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Int32, - ffi.Pointer)>>('realm_sync_socket_callback_complete'); - late final _realm_sync_socket_callback_complete = - _realm_sync_socket_callback_completePtr.asFunction< - void Function(ffi.Pointer, int, - ffi.Pointer)>(); - + /// Creates a new sync socket instance for the Sync Client that handles the operations for a custom + /// websocket and event loop implementation. + /// @param userdata CAPI implementation specific pointer containing custom context data that is provided to + /// each of the provided functions. + /// @param userdata_free function that will be called when the sync socket is destroyed to delete userdata. This + /// is required if userdata is not null. + /// @param post_func function that will be called to post a callback handler onto the event loop - use the + /// realm_sync_socket_post_complete() function when the callback handler is scheduled to run. + /// @param create_timer_func function that will be called to create a new timer resource with the callback + /// handler that will be run when the timer expires or an erorr occurs - use the + /// realm_sync_socket_timer_canceled() function if the timer is canceled or the + /// realm_sync_socket_timer_complete() function if the timer expires or an error occurs. + /// @param cancel_timer_func function that will be called when the timer has been canceled by the sync client. + /// @param free_timer_func function that will be called when the timer resource has been destroyed by the sync client. + /// @param websocket_connect_func function that will be called when the sync client creates a websocket. + /// @param websocket_write_func function that will be called when the sync client sends data over the websocket. + /// @param websocket_free_func function that will be called when the sync client closes the websocket conneciton. + /// @return a realm_sync_socket_t pointer suitable for passing to realm_sync_client_config_set_sync_socket() ffi.Pointer realm_sync_socket_new( ffi.Pointer userdata, realm_free_userdata_func_t userdata_free, @@ -9973,32 +9925,132 @@ class RealmLibrary { realm_sync_socket_websocket_async_write_func_t, realm_sync_socket_websocket_free_func_t)>(); - void realm_sync_socket_websocket_closed( + /// To be called to execute the callback function provided to the post_func when the event loop executes + /// that post'ed operation. The post_handler resource will automatically be destroyed during this + /// operation. + /// @param post_handler the post callback handler that was originally provided to the post_func + /// @param result the error code for the error that occurred or RLM_ERR_SYNC_SOCKET_SUCCESS if the + /// callback handler should be executed normally. + /// @param reason a string describing details about the error that occurred or empty string if no error. + /// NOTE: This function must be called by the event loop execution thread. + void realm_sync_socket_post_complete( + ffi.Pointer post_handler, + int result, + ffi.Pointer reason, + ) { + return _realm_sync_socket_post_complete( + post_handler, + result, + reason, + ); + } + + late final _realm_sync_socket_post_completePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Int32, + ffi.Pointer)>>('realm_sync_socket_post_complete'); + late final _realm_sync_socket_post_complete = + _realm_sync_socket_post_completePtr.asFunction< + void Function(ffi.Pointer, int, + ffi.Pointer)>(); + + /// To be called to execute the callback handler provided to the create_timer_func when the timer has been + /// canceled. + /// @param timer_handler the timer callback handler that was provided when the timer was created. + /// NOTE: This function must be called by the event loop execution thread. + void realm_sync_socket_timer_canceled( + ffi.Pointer timer_handler, + ) { + return _realm_sync_socket_timer_canceled( + timer_handler, + ); + } + + late final _realm_sync_socket_timer_canceledPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer)>>( + 'realm_sync_socket_timer_canceled'); + late final _realm_sync_socket_timer_canceled = + _realm_sync_socket_timer_canceledPtr.asFunction< + void Function(ffi.Pointer)>(); + + /// To be called to execute the callback handler provided to the create_timer_func when the timer is + /// complete or an error occurs while processing the timer. + /// @param timer_handler the timer callback handler that was provided when the timer was created. + /// @param result the error code for the error that occurred or RLM_ERR_SYNC_SOCKET_SUCCESS if the timer + /// expired normally. + /// @param reason a string describing details about the error that occurred or empty string if no error. + /// NOTE: This function must be called by the event loop execution thread. + void realm_sync_socket_timer_complete( + ffi.Pointer timer_handler, + int result, + ffi.Pointer reason, + ) { + return _realm_sync_socket_timer_complete( + timer_handler, + result, + reason, + ); + } + + late final _realm_sync_socket_timer_completePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Int32, + ffi.Pointer)>>('realm_sync_socket_timer_complete'); + late final _realm_sync_socket_timer_complete = + _realm_sync_socket_timer_completePtr.asFunction< + void Function(ffi.Pointer, int, + ffi.Pointer)>(); + + /// To be called when the websocket has been closed, either due to an error or a normal close operation. + /// @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + /// @param was_clean boolean value that indicates whether this is a normal close situation (true), the + /// close code was provided by the server via a close message (true), or if the close code was + /// generated by the local websocket as a result of some other error (false) (e.g. host + /// unreachable, etc.) + /// @param code the websocket close code (per the WebSocket spec) that describes why the websocket was closed. + /// @param reason a string describing details about the error that occurred or empty string if no error. + /// @return bool designates whether the WebSocket object has been destroyed during the execution of this + /// function. The normal return value is True to indicate the WebSocket object is no longer valid. If + /// False is returned, the WebSocket object will be destroyed at some point in the future. + /// NOTE: This function must be called by the event loop execution thread and should not be called + /// after the websocket_free_func has been called to release the websocket resources. + bool realm_sync_socket_websocket_closed( ffi.Pointer realm_websocket_observer, bool was_clean, - int status, + int code, ffi.Pointer reason, ) { return _realm_sync_socket_websocket_closed( realm_websocket_observer, was_clean, - status, + code, reason, ); } late final _realm_sync_socket_websocket_closedPtr = _lookup< ffi.NativeFunction< - ffi.Void Function( + ffi.Bool Function( ffi.Pointer, ffi.Bool, ffi.Int32, ffi.Pointer)>>('realm_sync_socket_websocket_closed'); late final _realm_sync_socket_websocket_closed = _realm_sync_socket_websocket_closedPtr.asFunction< - void Function(ffi.Pointer, bool, int, + bool Function(ffi.Pointer, bool, int, ffi.Pointer)>(); + /// To be called when the websocket successfully connects to the server. + /// @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + /// @param protocol the value of the Sec-WebSocket-Protocol header in the connect response from the server. + /// NOTE: This function must be called by the event loop execution thread and should not be called + /// after the websocket_free_func has been called to release the websocket resources. void realm_sync_socket_websocket_connected( ffi.Pointer realm_websocket_observer, ffi.Pointer protocol, @@ -10018,6 +10070,12 @@ class RealmLibrary { void Function(ffi.Pointer, ffi.Pointer)>(); + /// To be called when an error occurs - the actual error value will be provided when the websocket_closed + /// function is called. This function informs that the socket object is in an error state and no further + /// TX operations should be performed. + /// @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + /// NOTE: This function must be called by the event loop execution thread and should not be called + /// after the websocket_free_func has been called to release the websocket resources. void realm_sync_socket_websocket_error( ffi.Pointer realm_websocket_observer, ) { @@ -10034,7 +10092,17 @@ class RealmLibrary { _realm_sync_socket_websocket_errorPtr .asFunction)>(); - void realm_sync_socket_websocket_message( + /// To be called to provide the received data to the Sync Client when a write operation has completed. + /// The data buffer can be safely discarded after this function has completed. + /// @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + /// @param data a pointer to the buffer that contains the data received over the websocket + /// @param data_size the number of bytes in the data buffer + /// @return bool designates whether the WebSocket object should continue processing messages. The normal return + /// value is true. False must be returned if the websocket object has been destroyed during execution of + /// the function. + /// NOTE: This function must be called by the event loop execution thread and should not be called + /// after the websocket_free_func has been called to release the websocket resources. + bool realm_sync_socket_websocket_message( ffi.Pointer realm_websocket_observer, ffi.Pointer data, int data_size, @@ -10048,15 +10116,46 @@ class RealmLibrary { late final _realm_sync_socket_websocket_messagePtr = _lookup< ffi.NativeFunction< - ffi.Void Function( + ffi.Bool Function( ffi.Pointer, ffi.Pointer, ffi.Size)>>('realm_sync_socket_websocket_message'); late final _realm_sync_socket_websocket_message = _realm_sync_socket_websocket_messagePtr.asFunction< - void Function(ffi.Pointer, + bool Function(ffi.Pointer, ffi.Pointer, int)>(); + /// To be called to execute the callback function provided to the websocket_write_func when the write + /// operation is complete. The write_handler resource will automatically be destroyed during this + /// operation. + /// @param write_handler the write callback handler that was originally provided to the websocket_write_func + /// @param result the error code for the error that occurred or RLM_ERR_SYNC_SOCKET_SUCCESS if write completed + /// successfully + /// @param reason a string describing details about the error that occurred or empty string if no error. + /// NOTE: This function must be called by the event loop execution thread. + void realm_sync_socket_write_complete( + ffi.Pointer write_handler, + int result, + ffi.Pointer reason, + ) { + return _realm_sync_socket_write_complete( + write_handler, + result, + reason, + ); + } + + late final _realm_sync_socket_write_completePtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Int32, + ffi.Pointer)>>('realm_sync_socket_write_complete'); + late final _realm_sync_socket_write_complete = + _realm_sync_socket_write_completePtr.asFunction< + void Function(ffi.Pointer, int, + ffi.Pointer)>(); + /// Access the subscription at index. /// @return the subscription or nullptr if the index is not valid ffi.Pointer realm_sync_subscription_at( @@ -10220,6 +10319,32 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); + /// Remove all subscriptions for a given class type. If operation completes successfully set the bool out param. + /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error). + bool realm_sync_subscription_set_erase_by_class_name( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer erased, + ) { + return _realm_sync_subscription_set_erase_by_class_name( + arg0, + arg1, + erased, + ); + } + + late final _realm_sync_subscription_set_erase_by_class_namePtr = _lookup< + ffi.NativeFunction< + ffi.Bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>( + 'realm_sync_subscription_set_erase_by_class_name'); + late final _realm_sync_subscription_set_erase_by_class_name = + _realm_sync_subscription_set_erase_by_class_namePtr.asFunction< + bool Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>(); + /// Erase from subscription set by id. If operation completes successfully set the bool out param. /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error). bool realm_sync_subscription_set_erase_by_id( @@ -10671,20 +10796,6 @@ class RealmLibrary { late final _realm_user_get_app = _realm_user_get_appPtr.asFunction< ffi.Pointer Function(ffi.Pointer)>(); - int realm_user_get_auth_provider( - ffi.Pointer arg0, - ) { - return _realm_user_get_auth_provider( - arg0, - ); - } - - late final _realm_user_get_auth_providerPtr = _lookup< - ffi.NativeFunction)>>( - 'realm_user_get_auth_provider'); - late final _realm_user_get_auth_provider = _realm_user_get_auth_providerPtr - .asFunction)>(); - /// Get the custom user data from the user's access token. /// /// Returned value must be manually released with realm_free(). @@ -10739,21 +10850,6 @@ class RealmLibrary { late final _realm_user_get_identity = _realm_user_get_identityPtr .asFunction Function(ffi.Pointer)>(); - ffi.Pointer realm_user_get_local_identity( - ffi.Pointer arg0, - ) { - return _realm_user_get_local_identity( - arg0, - ); - } - - late final _realm_user_get_local_identityPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer)>>('realm_user_get_local_identity'); - late final _realm_user_get_local_identity = _realm_user_get_local_identityPtr - .asFunction Function(ffi.Pointer)>(); - /// Get the user profile associated with this user. /// /// Returned value must be manually released with realm_free(). @@ -11028,7 +11124,7 @@ class _SymbolAddresses { ffi.Pointer< ffi.NativeFunction< ffi.Void Function( - ffi.Pointer, ffi.Pointer)>> + ffi.Pointer, ffi.Pointer)>> get realm_dart_sync_wait_for_completion_callback => _library._realm_dart_sync_wait_for_completion_callbackPtr; ffi.Pointer)>> @@ -11329,6 +11425,23 @@ abstract class realm_errno { static const int RLM_ERR_SCHEMA_VERSION_MISMATCH = 1025; static const int RLM_ERR_NO_SUBSCRIPTION_FOR_WRITE = 1026; static const int RLM_ERR_OPERATION_ABORTED = 1027; + static const int RLM_ERR_AUTO_CLIENT_RESET_FAILED = 1028; + static const int RLM_ERR_BAD_SYNC_PARTITION_VALUE = 1029; + static const int RLM_ERR_CONNECTION_CLOSED = 1030; + static const int RLM_ERR_INVALID_SUBSCRIPTION_QUERY = 1031; + static const int RLM_ERR_SYNC_CLIENT_RESET_REQUIRED = 1032; + static const int RLM_ERR_SYNC_COMPENSATING_WRITE = 1033; + static const int RLM_ERR_SYNC_CONNECT_FAILED = 1034; + static const int RLM_ERR_SYNC_CONNECT_TIMEOUT = 1035; + static const int RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE = 1036; + static const int RLM_ERR_SYNC_PERMISSION_DENIED = 1037; + static const int RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED = 1038; + static const int RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED = 1039; + static const int RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED = 1040; + static const int RLM_ERR_SYNC_USER_MISMATCH = 1041; + static const int RLM_ERR_TLS_HANDSHAKE_FAILED = 1042; + static const int RLM_ERR_WRONG_SYNC_TYPE = 1043; + static const int RLM_ERR_SYNC_WRITE_NOT_ALLOWED = 1044; static const int RLM_ERR_SYSTEM_ERROR = 1999; static const int RLM_ERR_LOGIC = 2000; static const int RLM_ERR_NOT_SUPPORTED = 2001; @@ -11400,7 +11513,7 @@ abstract class realm_errno { static const int RLM_ERR_MONGODB_ERROR = 4311; static const int RLM_ERR_ARGUMENTS_NOT_ALLOWED = 4312; static const int RLM_ERR_FUNCTION_EXECUTION_ERROR = 4313; - static const int RLM_ERR_NO_MATCHING_RULE = 4314; + static const int RLM_ERR_NO_MATCHING_RULE_FOUND = 4314; static const int RLM_ERR_INTERNAL_SERVER_ERROR = 4315; static const int RLM_ERR_AUTH_PROVIDER_NOT_FOUND = 4316; static const int RLM_ERR_AUTH_PROVIDER_ALREADY_EXISTS = 4317; @@ -11442,9 +11555,6 @@ abstract class realm_errno { static const int RLM_ERR_USERPASS_TOKEN_INVALID = 4353; static const int RLM_ERR_INVALID_SERVER_RESPONSE = 4354; static const int RLM_ERR_APP_SERVER_ERROR = 4355; - static const int RLM_ERR_WEBSOCKET_RESOLVE_FAILED_ERROR = 4400; - static const int RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_CLIENT_ERROR = 4401; - static const int RLM_ERR_WEBSOCKET_CONNECTION_CLOSED_SERVER_ERROR = 4402; /// < A user-provided callback failed. static const int RLM_ERR_CALLBACK = 1000000; @@ -11944,9 +12054,7 @@ typedef realm_sync_connection_state_changed_func_t = ffi.Pointer< ffi.Int32 new_state)>>; final class realm_sync_error extends ffi.Struct { - external realm_sync_error_code_t error_code; - - external ffi.Pointer detailed_message; + external realm_error_t status; external ffi.Pointer c_original_file_path_key; @@ -11989,34 +12097,6 @@ abstract class realm_sync_error_action { static const int RLM_SYNC_ERROR_ACTION_REVERT_TO_PBS = 9; } -/// Possible error categories realm_sync_error_code_t can fall in. -abstract class realm_sync_error_category { - static const int RLM_SYNC_ERROR_CATEGORY_CLIENT = 0; - static const int RLM_SYNC_ERROR_CATEGORY_CONNECTION = 1; - static const int RLM_SYNC_ERROR_CATEGORY_SESSION = 2; - static const int RLM_SYNC_ERROR_CATEGORY_WEBSOCKET = 3; - - /// System error - POSIX errno, Win32 HRESULT, etc. - static const int RLM_SYNC_ERROR_CATEGORY_SYSTEM = 4; - - /// Unknown source of error. - static const int RLM_SYNC_ERROR_CATEGORY_UNKNOWN = 5; -} - -final class realm_sync_error_code extends ffi.Struct { - @ffi.Int32() - external int category; - - @ffi.Int() - external int value; - - external ffi.Pointer message; - - external ffi.Pointer category_name; -} - -typedef realm_sync_error_code_t = realm_sync_error_code; - final class realm_sync_error_compensating_write_info extends ffi.Struct { external ffi.Pointer reason; @@ -12089,24 +12169,36 @@ final class realm_sync_socket extends ffi.Opaque {} final class realm_sync_socket_callback extends ffi.Opaque {} -typedef realm_sync_socket_callback_t = realm_sync_socket_callback; +abstract class realm_sync_socket_callback_result { + static const int RLM_ERR_SYNC_SOCKET_SUCCESS = 0; + static const int RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED = 1027; + static const int RLM_ERR_SYNC_SOCKET_RUNTIME = 1000; + static const int RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY = 1003; + static const int RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED = 1005; + static const int RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED = 1030; + static const int RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED = 2001; + static const int RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT = 3000; +} + typedef realm_sync_socket_connect_func_t = ffi.Pointer< ffi.NativeFunction< realm_sync_socket_websocket_t Function( ffi.Pointer userdata, realm_websocket_endpoint_t endpoint, - ffi.Pointer realm_websocket_observer)>>; + ffi.Pointer websocket_observer)>>; typedef realm_sync_socket_create_timer_func_t = ffi.Pointer< ffi.NativeFunction< realm_sync_socket_timer_t Function( ffi.Pointer userdata, ffi.Uint64 delay_ms, - ffi.Pointer realm_callback)>>; + ffi.Pointer timer_callback)>>; +typedef realm_sync_socket_post_callback_t = realm_sync_socket_callback; typedef realm_sync_socket_post_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer userdata, - ffi.Pointer realm_callback)>>; + ffi.Pointer post_callback)>>; typedef realm_sync_socket_t = realm_sync_socket; +typedef realm_sync_socket_timer_callback_t = realm_sync_socket_callback; typedef realm_sync_socket_timer_canceled_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer userdata, @@ -12120,15 +12212,16 @@ typedef realm_sync_socket_websocket_async_write_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Void Function( ffi.Pointer userdata, - realm_sync_socket_websocket_t websocket_userdata, + realm_sync_socket_websocket_t websocket, ffi.Pointer data, ffi.Size size, - ffi.Pointer realm_callback)>>; + ffi.Pointer write_callback)>>; typedef realm_sync_socket_websocket_free_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Void Function(ffi.Pointer userdata, - realm_sync_socket_websocket_t websocket_userdata)>>; + realm_sync_socket_websocket_t websocket)>>; typedef realm_sync_socket_websocket_t = ffi.Pointer; +typedef realm_sync_socket_write_callback_t = realm_sync_socket_callback; typedef realm_sync_ssl_verify_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Bool Function( @@ -12149,8 +12242,8 @@ typedef realm_sync_ssl_verify_func_t = ffi.Pointer< /// @param error Null, if the operation completed successfully. typedef realm_sync_wait_for_completion_func_t = ffi.Pointer< ffi.NativeFunction< - ffi.Void Function(ffi.Pointer userdata, - ffi.Pointer error)>>; + ffi.Void Function( + ffi.Pointer userdata, ffi.Pointer error)>>; typedef realm_t = shared_realm; final class realm_thread_safe_reference extends ffi.Opaque {} diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index df0e98582..7f97f8fd1 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -631,10 +631,10 @@ class _RealmCore { }, unlockCallbackFunc); } - void raiseError(Session session, SyncErrorCategory category, int errorCode, bool isFatal) { + void raiseError(Session session, int errorCode, bool isFatal) { using((arena) { final message = "Simulated session error".toCharPtr(arena); - _realmLib.realm_sync_session_handle_error_for_testing(session.handle._pointer, errorCode, category.index, message, isFatal); + _realmLib.realm_sync_session_handle_error_for_testing(session.handle._pointer, errorCode, message, isFatal); }); } @@ -685,8 +685,8 @@ class _RealmCore { if (error != nullptr) { final err = arena(); - _realmLib.realm_get_async_error(error, err); - completer.completeError(RealmException("Failed to open realm ${err.ref.toLastError().toString()}")); + bool success = _realmLib.realm_get_async_error(error, err); + completer.completeError(RealmException("Failed to open realm ${success ? err.ref.toLastError().toString() : ''}")); return; } @@ -1638,18 +1638,6 @@ class _RealmCore { _realmLib.realm_app_config_set_bundle_id(handle._pointer, getBundleId().toCharPtr(arena)); - if (configuration.localAppName != null) { - _realmLib.realm_app_config_set_local_app_name(handle._pointer, configuration.localAppName!.toCharPtr(arena)); - } else { - _realmLib.realm_app_config_set_local_app_name(handle._pointer, ''.toCharPtr(arena)); - } - - if (configuration.localAppVersion != null) { - _realmLib.realm_app_config_set_local_app_version(handle._pointer, configuration.localAppVersion!.toCharPtr(arena)); - } else { - _realmLib.realm_app_config_set_local_app_version(handle._pointer, ''.toCharPtr(arena)); - } - return handle; }); } @@ -2228,11 +2216,6 @@ class _RealmCore { return deviceId.cast().toRealmDartString(treatEmptyAsNull: true, freeRealmMemory: true); } - AuthProviderType userGetAuthProviderType(User user) { - final provider = _realmLib.realm_user_get_auth_provider(user.handle._pointer); - return AuthProviderTypeInternal.getByValue(provider); - } - AuthProviderType userGetCredentialsProviderType(Credentials credentials) { final provider = _realmLib.realm_auth_credentials_get_provider(credentials.handle._pointer); return AuthProviderTypeInternal.getByValue(provider); @@ -2339,7 +2322,7 @@ class _RealmCore { Future sessionWaitForUpload(Session session) { final completer = Completer(); - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); + final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_upload_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); @@ -2349,7 +2332,7 @@ class _RealmCore { Future sessionWaitForDownload(Session session, [CancellationToken? cancellationToken]) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); + final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); @@ -2357,7 +2340,7 @@ class _RealmCore { return completer.future; } - static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { + static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { final completer = userdata as Completer; if (completer.isCompleted) { return; @@ -3195,19 +3178,14 @@ extension on Pointer { extension on realm_sync_error { SyncErrorDetails toSyncErrorDetails() { - final message = error_code.message.cast().toRealmDartString()!; - final SyncErrorCategory category = SyncErrorCategory.values[error_code.category]; - final detailedMessage = detailed_message.cast().toRealmDartString(); - + final message = status.message.cast().toRealmDartString()!; final userInfoMap = user_info_map.toMap(user_info_length); final originalFilePathKey = c_original_file_path_key.cast().toRealmDartString(); final recoveryFilePathKey = c_recovery_file_path_key.cast().toRealmDartString(); return SyncErrorDetails( message, - category, - error_code.value, - detailedMessage: detailedMessage, + status.error, isFatal: is_fatal, isClientResetRequested: is_client_reset_requested, originalFilePath: userInfoMap?[originalFilePathKey], @@ -3250,10 +3228,10 @@ extension on Pointer { } } -extension on Pointer { +extension on Pointer { SyncError toSyncError() { final message = ref.message.cast().toDartString(); - final details = SyncErrorDetails(message, SyncErrorCategory.values[ref.category], ref.value); + final details = SyncErrorDetails(message, ref.error); return SyncErrorInternal.createSyncError(details); } } @@ -3411,19 +3389,17 @@ extension PlatformEx on Platform { /// @nodoc class SyncErrorDetails { final String message; - final SyncErrorCategory category; final int code; - final String? detailedMessage; + final String? path; final bool isFatal; final bool isClientResetRequested; final String? originalFilePath; final String? backupFilePath; final List? compensatingWrites; - const SyncErrorDetails( + SyncErrorDetails( this.message, - this.category, this.code, { - this.detailedMessage, + this.path, this.isFatal = false, this.isClientResetRequested = false, this.originalFilePath, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 38044f8fb..da4b5c508 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -15,6 +15,7 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// + import 'dart:async'; import 'dart:ffi'; import 'dart:io'; @@ -71,8 +72,6 @@ export "configuration.dart" DiscardUnsyncedChangesHandler, DisconnectedSyncConfiguration, FlexibleSyncConfiguration, - GeneralSyncError, - GeneralSyncErrorCode, InitialDataCallback, InMemoryConfiguration, LocalConfiguration, @@ -83,12 +82,21 @@ export "configuration.dart" RecoverUnsyncedChangesHandler, SchemaObject, ShouldCompactCallback, - SyncClientError, - SyncConnectionError, SyncError, SyncErrorHandler, + // ignore: deprecated_member_use_from_same_package + SyncClientError, + // ignore: deprecated_member_use_from_same_package + SyncConnectionError, + // ignore: deprecated_member_use_from_same_package + GeneralSyncError, + // ignore: deprecated_member_use_from_same_package + GeneralSyncErrorCode, + // ignore: deprecated_member_use_from_same_package SyncResolveError, + // ignore: deprecated_member_use_from_same_package SyncWebSocketError, + // ignore: deprecated_member_use_from_same_package SyncSessionError; export 'credentials.dart' show AuthProviderType, Credentials, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges, ListExtension; @@ -117,11 +125,18 @@ export 'session.dart' ConnectionState, Session, SessionState, + SyncErrorCode, + // ignore: deprecated_member_use_from_same_package SyncClientErrorCode, + // ignore: deprecated_member_use_from_same_package SyncConnectionErrorCode, + // ignore: deprecated_member_use_from_same_package SyncErrorCategory, + // ignore: deprecated_member_use_from_same_package SyncResolveErrorCode, + // ignore: deprecated_member_use_from_same_package SyncWebSocketErrorCode, + // ignore: deprecated_member_use_from_same_package SyncSessionErrorCode; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState, ApiKeyClient, UserIdentity, ApiKey, FunctionsClient; @@ -571,26 +586,15 @@ class Realm implements Finalizable { throw RealmException("Can't compact an in-memory Realm"); } - late Configuration compactConfig; - if (!File(config.path).existsSync()) { + print("realm file doesn't exist: ${config.path}"); return false; } - if (config is LocalConfiguration) { - // `compact` opens the realm file so it can triger schema version upgrade, file format upgrade, migration and initial data callbacks etc. - // We must allow that to happen so use the local config as is. - compactConfig = config; - } else if (config is DisconnectedSyncConfiguration) { - compactConfig = config; - } else if (config is FlexibleSyncConfiguration) { - compactConfig = Configuration.disconnectedSync(config.schemaObjects.toList(), - path: config.path, fifoFilesFallbackPath: config.fifoFilesFallbackPath, encryptionKey: config.encryptionKey); - } else { - throw RealmError("Unsupported realm configuration type ${config.runtimeType}"); + final realm = Realm(config); + if (config is FlexibleSyncConfiguration) { + realm.syncSession.pause(); } - - final realm = Realm(compactConfig); try { return realmCore.compact(realm); } finally { diff --git a/lib/src/session.dart b/lib/src/session.dart index 26ca4b8f6..91c8fef07 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -22,6 +22,8 @@ import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; +import '../src/native/realm_bindings.dart'; + /// An object encapsulating a synchronization session. Sessions represent the /// communication between the client (and a local Realm file on disk), and the /// server. Sessions are always created by the SDK and vended out through various @@ -74,10 +76,6 @@ class Session implements Finalizable { final controller = SessionConnectionStateController(this); return controller.createStream(); } - - void _raiseSessionError(SyncErrorCategory category, int errorCode, bool isFatal) { - realmCore.raiseError(this, category, errorCode, isFatal); - } } /// A type containing information about the progress state at a given instant. @@ -121,8 +119,8 @@ extension SessionInternal on Session { return _handle; } - void raiseError(SyncErrorCategory category, int errorCode, bool isFatal) { - realmCore.raiseError(this, category, errorCode, isFatal); + void raiseError(int errorCode, bool isFatal) { + realmCore.raiseError(this, errorCode, isFatal); } static SyncProgress createSyncProgress(int transferredBytes, int transferableBytes) => @@ -245,7 +243,74 @@ enum ProgressMode { forCurrentlyOutstandingWork, } +/// Error code enumeration, indicating the type of [SyncError]. +enum SyncErrorCode { + /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. + runtimeError(realm_errno.RLM_ERR_RUNTIME), + + /// The partition value specified by the user is not valid - i.e. its the wrong type or is encoded incorrectly. + badPartitionValue(realm_errno.RLM_ERR_BAD_SYNC_PARTITION_VALUE), + + /// A fundamental invariant in the communication between the client and the server was not upheld. This typically indicates + /// a bug in the synchronization layer and should be reported at https://github.com/realm/realm-core/issues. + protocolInvariantFailed(realm_errno.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED), + + /// The changeset is invalid. + badChangeset(realm_errno.RLM_ERR_BAD_CHANGESET), + + /// The client attempted to create a subscription for a query is invalid/malformed. + invalidSubscriptionQuery(realm_errno.RLM_ERR_INVALID_SUBSCRIPTION_QUERY), + + /// A client reset has occurred. This error code will only be reported via a [ClientResetError] and only + /// in the case manual client reset handling is required - either via [ManualRecoveryHandler] or when + /// `onManualReset` is invoked on one of the automatic client reset handlers. + clientReset(realm_errno.RLM_ERR_SYNC_CLIENT_RESET_REQUIRED), + + /// The client attempted to upload an invalid schema change - either an additive schema change + /// when developer mode is off or a destructive schema change. + invalidSchemaChange(realm_errno.RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE), + + /// Permission to Realm has been denied. + permissionDenied(realm_errno.RLM_ERR_SYNC_PERMISSION_DENIED), + + /// The server permissions for this file have changed since the last time it was used. + serverPermissionsChanged(realm_errno.RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED), + + /// The user for this session doesn't match the user who originally created the file. This can happen + /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with + /// user A, then with user B without changing the on-disk path. + userMismatch(realm_errno.RLM_ERR_SYNC_USER_MISMATCH), + + /// Client attempted a write that is disallowed by permissions, or modifies an object + /// outside the current query - this will result in a [CompensatingWriteError]. + writeNotAllowed(realm_errno.RLM_ERR_SYNC_WRITE_NOT_ALLOWED), + + /// Automatic client reset has failed. This will only be reported via [ClientResetError] + /// when an automatic client reset handler was used but it failed to perform the client reset operation - + /// typically due to a breaking schema change in the server schema or due to an exception occurring in the + /// before or after client reset callbacks. + autoClientResetFailed(realm_errno.RLM_ERR_AUTO_CLIENT_RESET_FAILED), + + /// The wrong sync type was used to connect to the server. This means that you're trying to connect + /// to an app configured to use partition sync. + wrongSyncType(realm_errno.RLM_ERR_WRONG_SYNC_TYPE), + + /// Client attempted a write that is disallowed by permissions, or modifies an + /// object outside the current query, and the server undid the modification. + compensatingWrite(realm_errno.RLM_ERR_SYNC_COMPENSATING_WRITE); + + static final Map _valuesMap = {for (var value in SyncErrorCode.values) value.code: value}; + + static SyncErrorCode fromInt(int code) { + return SyncErrorCode._valuesMap[code] ?? SyncErrorCode.runtimeError; + } + + final int code; + const SyncErrorCode(this.code); +} + /// The category of a [SyncError]. +@Deprecated("Sync errors are not classified by SyncErrorCategory anymore.") enum SyncErrorCategory { /// The error originated from the client client, @@ -271,6 +336,7 @@ enum SyncErrorCategory { /// These errors will terminate the network connection /// (disconnect all sessions associated with the affected connection), /// and the error will be reported via the connection state change listeners of the affected sessions. +@Deprecated("Use SyncError or its subclasses instead.") enum SyncClientErrorCode { /// Connection closed (no error) connectionClosed(100), @@ -382,6 +448,7 @@ enum SyncClientErrorCode { /// Protocol connection errors discovered by the server, and reported to the client /// /// These errors will be reported via the error handlers of the affected sessions. +@Deprecated("Use SyncError or its subclasses instead of using error codes.") enum SyncConnectionErrorCode { // Connection level and protocol errors /// Connection closed (no error) @@ -445,6 +512,7 @@ enum SyncConnectionErrorCode { /// Protocol session errors discovered by the server, and reported to the client /// /// These errors will be reported via the error handlers of the affected sessions. +@Deprecated("Use SyncError or its subclasses instead of using error codes.") enum SyncSessionErrorCode { /// Session closed (no error) sessionClosed(200), @@ -598,6 +666,7 @@ enum SyncResolveErrorCode { /// Web socket errors. /// /// These errors will be reported via the error handlers of the affected sessions. +@Deprecated("Use SyncError or its subclasses instead of using error codes.") enum SyncWebSocketErrorCode { /// Web socket resolution failed websocketResolveFailed(4400), diff --git a/lib/src/user.dart b/lib/src/user.dart index 1ca8c0819..778434e71 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -47,7 +47,6 @@ class User { /// [API Keys Authentication Docs](https://docs.mongodb.com/realm/authentication/api-key/) ApiKeyClient get apiKeys { _ensureLoggedIn('access API keys'); - _ensureCanAccessAPIKeys(); return _apiKeys; } @@ -89,8 +88,9 @@ class User { } /// Gets the [AuthProviderType] this [User] is currently logged in with. + @Deprecated("Get the auth provider from the user identity.") AuthProviderType get provider { - return realmCore.userGetAuthProviderType(this); + return identities.first.provider; } /// Gets the profile information for this [User]. @@ -161,12 +161,6 @@ class User { throw RealmError('User must be logged in to $clarification'); } } - - void _ensureCanAccessAPIKeys() { - if (provider == AuthProviderType.apiKey) { - throw RealmError('Users logged in with API key cannot manage API keys'); - } - } } /// The current state of a [User]. diff --git a/src/realm-core b/src/realm-core index 21925e4d8..56a048d9f 160000 --- a/src/realm-core +++ b/src/realm-core @@ -1 +1 @@ -Subproject commit 21925e4d81598959bd128ad0e687ce95efd12c45 +Subproject commit 56a048d9f450445f2d52c7405f3019a533bdaa3f diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index 8846e3aae..ba148f972 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -69,7 +69,8 @@ RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, r struct error_copy { std::string message; - std::string detailed_message; + realm_errno_e error; + realm_error_categories categories; std::string original_file_path_key; std::string recovery_file_path_key; bool is_fatal; @@ -80,8 +81,10 @@ RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, r std::vector compensating_writes_errors_info; } buf; - buf.message = error.error_code.message; - buf.detailed_message = std::string(error.detailed_message); + buf.message = std::string(error.status.message); + buf.categories = error.status.categories; + buf.error = error.status.error; + // TODO: Map usercode_error and path when issue https://github.com/realm/realm-core/issues/6925 is fixed buf.original_file_path_key = std::string(error.c_original_file_path_key); buf.recovery_file_path_key = std::string(error.c_recovery_file_path_key); buf.is_fatal = error.is_fatal; @@ -112,8 +115,9 @@ RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, r auto ud = reinterpret_cast(userdata); ud->scheduler->invoke([ud, session = *session, error = std::move(error), buf = std::move(buf)]() mutable { //we moved buf so we need to update the error pointers here. - error.error_code.message = buf.message.c_str(); - error.detailed_message = buf.detailed_message.c_str(); + error.status.message = buf.message.c_str(); + error.status.error = buf.error; + error.status.categories = buf.categories; error.c_original_file_path_key = buf.original_file_path_key.c_str(); error.c_recovery_file_path_key = buf.recovery_file_path_key.c_str(); error.is_fatal = buf.is_fatal; @@ -124,15 +128,16 @@ RLM_API void realm_dart_sync_error_handler_callback(realm_userdata_t userdata, r }); } -RLM_API void realm_dart_sync_wait_for_completion_callback(realm_userdata_t userdata, realm_sync_error_code_t* error) +RLM_API void realm_dart_sync_wait_for_completion_callback(realm_userdata_t userdata, realm_error_t* error) { // we need to make a deep copy of error, because the message pointer points to stack memory - struct realm_dart_sync_error_code : realm_sync_error_code + struct realm_dart_sync_error_code : realm_error_t { - realm_dart_sync_error_code(const realm_sync_error_code& error) - : realm_sync_error_code(error) - , message_buffer(error.message) + realm_dart_sync_error_code(const realm_error& error_input) + : message_buffer(error_input.message) { + error = error_input.error; + categories = error_input.categories; message = message_buffer.c_str(); } @@ -189,10 +194,10 @@ bool invoke_dart_and_await_result(realm::util::UniqueFunction main([List? args]) async { baseFilePath: Directory.systemTemp, baseUrl: Uri.parse('https://not_re.al'), defaultRequestTimeout: const Duration(seconds: 2), - localAppName: 'bar', - localAppVersion: "1.0.0", metadataPersistenceMode: MetadataPersistenceMode.disabled, maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, @@ -79,8 +77,6 @@ Future main([List? args]) async { baseFilePath: Directory.systemTemp, baseUrl: Uri.parse('https://not_re.al'), defaultRequestTimeout: const Duration(seconds: 2), - localAppName: 'bar', - localAppVersion: "1.0.0", metadataPersistenceMode: MetadataPersistenceMode.encrypted, metadataEncryptionKey: base64.decode("ekey"), maxConnectionTimeout: const Duration(minutes: 1), @@ -262,10 +258,7 @@ Future main([List? args]) async { baasTest('App.reconnect', (appConfiguration) async { final app = App(appConfiguration); - - final user = await app.logIn(Credentials.anonymous()); - final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = getRealm(configuration); + final realm = await getIntegrationRealm(app: app); final session = realm.syncSession; // TODO: We miss a way to force a disconnect. Once we implement GenericNetworkTransport diff --git a/test/asymmetric_test.dart b/test/asymmetric_test.dart index c585a9bd5..a7584b944 100644 --- a/test/asymmetric_test.dart +++ b/test/asymmetric_test.dart @@ -21,43 +21,11 @@ import 'package:test/expect.dart' hide throws; import '../lib/realm.dart'; import 'test.dart'; -part 'asymmetric_test.g.dart'; - -@RealmModel(ObjectType.asymmetricObject) -class _Asymmetric { - @PrimaryKey() - @MapTo('_id') - late ObjectId id; - - late List<_Embedded> embeddedObjects; -} - -@RealmModel(ObjectType.embeddedObject) -class _Embedded { - late int value; - late RealmValue any; - _Symmetric? symmetric; -} - -@RealmModel() -class _Symmetric { - @PrimaryKey() - @MapTo('_id') - late ObjectId id; -} - Future main([List? args]) async { await setupTests(args); - Future getSyncRealm(AppConfiguration config) async { - final app = App(config); - final user = await getAnonymousUser(app); - final realmConfig = Configuration.flexibleSync(user, [Asymmetric.schema, Embedded.schema, Symmetric.schema]); - return getRealm(realmConfig); - } - baasTest('Asymmetric objects die even before upload', (config) async { - final realm = await getSyncRealm(config); + final realm = await getIntegrationRealm(appConfig: config); realm.syncSession.pause(); final oid = ObjectId(); @@ -74,7 +42,7 @@ Future main([List? args]) async { }); baasTest('Asymmetric re-add same PK', (config) async { - final realm = await getSyncRealm(config); + final realm = await getIntegrationRealm(appConfig: config); final oid = ObjectId(); realm.write(() { @@ -92,7 +60,7 @@ Future main([List? args]) async { }); baasTest('Asymmetric tricks to add non-embedded links', (config) async { - final realm = await getSyncRealm(config); + final realm = await getIntegrationRealm(appConfig: config); realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realm.all()); @@ -115,7 +83,7 @@ Future main([List? args]) async { }); test("Asymmetric don't work with disconnectedSync", () { - final config = Configuration.disconnectedSync([Asymmetric.schema, Embedded.schema, Symmetric.schema], path: 'asymmetric_disconnected.realm'); + final config = Configuration.disconnectedSync([Asymmetric.schema, Embedded.schema, Symmetric.schema], path: generateRandomRealmPath()); expect(() => Realm(config), throws()); }); diff --git a/test/asymmetric_test.g.dart b/test/asymmetric_test.g.dart deleted file mode 100644 index 41344b439..000000000 --- a/test/asymmetric_test.g.dart +++ /dev/null @@ -1,142 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'asymmetric_test.dart'; - -// ************************************************************************** -// RealmObjectGenerator -// ************************************************************************** - -// ignore_for_file: type=lint -class Asymmetric extends _Asymmetric - with RealmEntity, RealmObjectBase, AsymmetricObject { - Asymmetric( - ObjectId id, { - Iterable embeddedObjects = const [], - }) { - RealmObjectBase.set(this, '_id', id); - RealmObjectBase.set>( - this, 'embeddedObjects', RealmList(embeddedObjects)); - } - - Asymmetric._(); - - @override - ObjectId get id => RealmObjectBase.get(this, '_id') as ObjectId; - @override - set id(ObjectId value) => RealmObjectBase.set(this, '_id', value); - - @override - RealmList get embeddedObjects => - RealmObjectBase.get(this, 'embeddedObjects') - as RealmList; - @override - set embeddedObjects(covariant RealmList value) => - throw RealmUnsupportedSetError(); - - @override - Stream> get changes => - RealmObjectBase.getChanges(this); - - @override - Asymmetric freeze() => RealmObjectBase.freezeObject(this); - - static SchemaObject get schema => _schema ??= _initSchema(); - static SchemaObject? _schema; - static SchemaObject _initSchema() { - RealmObjectBase.registerFactory(Asymmetric._); - return const SchemaObject( - ObjectType.asymmetricObject, Asymmetric, 'Asymmetric', [ - SchemaProperty('id', RealmPropertyType.objectid, - mapTo: '_id', primaryKey: true), - SchemaProperty('embeddedObjects', RealmPropertyType.object, - linkTarget: 'Embedded', collectionType: RealmCollectionType.list), - ]); - } -} - -// ignore_for_file: type=lint -class Embedded extends _Embedded - with RealmEntity, RealmObjectBase, EmbeddedObject { - Embedded( - int value, { - RealmValue any = const RealmValue.nullValue(), - Symmetric? symmetric, - }) { - RealmObjectBase.set(this, 'value', value); - RealmObjectBase.set(this, 'any', any); - RealmObjectBase.set(this, 'symmetric', symmetric); - } - - Embedded._(); - - @override - int get value => RealmObjectBase.get(this, 'value') as int; - @override - set value(int value) => RealmObjectBase.set(this, 'value', value); - - @override - RealmValue get any => - RealmObjectBase.get(this, 'any') as RealmValue; - @override - set any(RealmValue value) => RealmObjectBase.set(this, 'any', value); - - @override - Symmetric? get symmetric => - RealmObjectBase.get(this, 'symmetric') as Symmetric?; - @override - set symmetric(covariant Symmetric? value) => - RealmObjectBase.set(this, 'symmetric', value); - - @override - Stream> get changes => - RealmObjectBase.getChanges(this); - - @override - Embedded freeze() => RealmObjectBase.freezeObject(this); - - static SchemaObject get schema => _schema ??= _initSchema(); - static SchemaObject? _schema; - static SchemaObject _initSchema() { - RealmObjectBase.registerFactory(Embedded._); - return const SchemaObject(ObjectType.embeddedObject, Embedded, 'Embedded', [ - SchemaProperty('value', RealmPropertyType.int), - SchemaProperty('any', RealmPropertyType.mixed, optional: true), - SchemaProperty('symmetric', RealmPropertyType.object, - optional: true, linkTarget: 'Symmetric'), - ]); - } -} - -// ignore_for_file: type=lint -class Symmetric extends _Symmetric - with RealmEntity, RealmObjectBase, RealmObject { - Symmetric( - ObjectId id, - ) { - RealmObjectBase.set(this, '_id', id); - } - - Symmetric._(); - - @override - ObjectId get id => RealmObjectBase.get(this, '_id') as ObjectId; - @override - set id(ObjectId value) => RealmObjectBase.set(this, '_id', value); - - @override - Stream> get changes => - RealmObjectBase.getChanges(this); - - @override - Symmetric freeze() => RealmObjectBase.freezeObject(this); - - static SchemaObject get schema => _schema ??= _initSchema(); - static SchemaObject? _schema; - static SchemaObject _initSchema() { - RealmObjectBase.registerFactory(Symmetric._); - return const SchemaObject(ObjectType.realmObject, Symmetric, 'Symmetric', [ - SchemaProperty('id', RealmPropertyType.objectid, - mapTo: '_id', primaryKey: true), - ]); - } -} diff --git a/test/client_reset_test.dart b/test/client_reset_test.dart index 211ea1878..6fc89cbbb 100644 --- a/test/client_reset_test.dart +++ b/test/client_reset_test.dart @@ -35,21 +35,21 @@ Future main([List? args]) async { expect( Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: ManualRecoveryHandler((syncError) {}), ).clientResetHandler.clientResyncMode, ClientResyncModeInternal.manual); expect( Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: const DiscardUnsyncedChangesHandler(), ).clientResetHandler.clientResyncMode, ClientResyncModeInternal.discardLocal); expect( Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: const RecoverUnsyncedChangesHandler(), ).clientResetHandler.clientResyncMode, ClientResyncModeInternal.recover); @@ -57,12 +57,12 @@ Future main([List? args]) async { expect( Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: const RecoverOrDiscardUnsyncedChangesHandler(), ).clientResetHandler.clientResyncMode, ClientResyncModeInternal.recoverOrDiscard); - expect(Configuration.flexibleSync(user, [Task.schema, Schedule.schema]).clientResetHandler.clientResyncMode, ClientResyncModeInternal.recoverOrDiscard); + expect(Configuration.flexibleSync(user, getSyncSchema()).clientResetHandler.clientResyncMode, ClientResyncModeInternal.recoverOrDiscard); }); baasTest('ManualRecoveryHandler error is reported in callback', (appConfig) async { @@ -72,7 +72,7 @@ Future main([List? args]) async { final resetCompleter = Completer(); final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: ManualRecoveryHandler((syncError) { resetCompleter.completeError(syncError); }), @@ -93,7 +93,7 @@ Future main([List? args]) async { final resetCompleter = Completer(); final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: ManualRecoveryHandler((clientResetError) { resetCompleter.completeError(clientResetError); }), @@ -122,7 +122,7 @@ Future main([List? args]) async { final resetCompleter = Completer(); final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: ManualRecoveryHandler((clientResetError) { resetCompleter.completeError(clientResetError); }), @@ -152,7 +152,7 @@ Future main([List? args]) async { final user = await getIntegrationUser(app); final onManualResetFallback = Completer(); - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema], + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: Creator.create( clientResetHandlerType, onBeforeReset: (beforeResetRealm) => throw Exception("This fails!"), @@ -177,7 +177,7 @@ Future main([List? args]) async { throw Exception("This fails!"); } - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema], + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: Creator.create( clientResetHandlerType, onAfterRecovery: clientResetHandlerType != DiscardUnsyncedChangesHandler ? onAfterReset : null, @@ -204,7 +204,7 @@ Future main([List? args]) async { onAfterCompleter.complete(); } - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema], + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: Creator.create( clientResetHandlerType, onBeforeReset: (beforeResetRealm) => onBeforeCompleter.complete(), @@ -233,7 +233,7 @@ Future main([List? args]) async { int onAfterRecoveryOccurred = 0; final onAfterCompleter = Completer(); - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema], + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: Creator.create( clientResetHandlerType, onBeforeReset: (beforeResetRealm) => onBeforeResetOccurred++, @@ -249,21 +249,25 @@ Future main([List? args]) async { final realm = await getRealmAsync(config); await realm.syncSession.waitForUpload(); - + final objectId = ObjectId(); + final addedObjectId = ObjectId(); + final query = realm.query(r'_id IN $0', [ + [objectId, addedObjectId] + ]); realm.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(query); }); await realm.subscriptions.waitForSynchronization(); await realm.syncSession.waitForDownload(); - final tasksCount = realm.all().length; + final tasksCount = query.length; realm.syncSession.pause(); - realm.write(() => realm.add(Task(ObjectId()))); - expect(tasksCount, lessThan(realm.all().length)); + realm.write(() => realm.add(Task(addedObjectId))); + expect(tasksCount, lessThan(query.length)); final notifications = []; - final subscription = realm.all().changes.listen((event) { + final subscription = query.changes.listen((event) { notifications.add(event); }); @@ -296,24 +300,26 @@ Future main([List? args]) async { final user = await getIntegrationUser(app); final onAfterCompleter = Completer(); - final syncedProduct = Product(ObjectId(), "always synced"); - final maybeProduct = Product(ObjectId(), "maybe synced"); - comparer(Product p1, Product p2) => p1.id == p2.id; - final config = Configuration.flexibleSync(user, [Product.schema], + final syncedId = ObjectId(); + final maybeId = ObjectId(); + + comparer(Product p1, ObjectId expectedId) => p1.id == expectedId; + + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: Creator.create( clientResetHandlerType, onBeforeReset: (beforeResetRealm) { - _checkProducts(beforeResetRealm, comparer, expectedList: [syncedProduct, maybeProduct]); + _checkProducts(beforeResetRealm, comparer, expectedList: [syncedId, maybeId]); }, onAfterRecovery: (beforeResetRealm, afterResetRealm) { - _checkProducts(beforeResetRealm, comparer, expectedList: [syncedProduct, maybeProduct]); - _checkProducts(afterResetRealm, comparer, expectedList: [syncedProduct, maybeProduct]); + _checkProducts(beforeResetRealm, comparer, expectedList: [syncedId, maybeId]); + _checkProducts(afterResetRealm, comparer, expectedList: [syncedId, maybeId]); onAfterCompleter.complete(); }, onAfterDiscard: (beforeResetRealm, afterResetRealm) { - _checkProducts(beforeResetRealm, comparer, expectedList: [syncedProduct, maybeProduct]); - _checkProducts(afterResetRealm, comparer, expectedList: [syncedProduct], notExpectedList: [maybeProduct]); + _checkProducts(beforeResetRealm, comparer, expectedList: [syncedId, maybeId]); + _checkProducts(afterResetRealm, comparer, expectedList: [syncedId], notExpectedList: [maybeId]); onAfterCompleter.complete(); }, onManualResetFallback: (clientResetError) => onAfterCompleter.completeError(clientResetError), @@ -321,15 +327,17 @@ Future main([List? args]) async { final realm = await getRealmAsync(config); realm.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.query(r'_id IN $0', [ + [syncedId, maybeId] + ])); }); await realm.subscriptions.waitForSynchronization(); - realm.write(() => realm.add(syncedProduct)); + realm.write(() => realm.add(Product(syncedId, "always synced"))); await realm.syncSession.waitForUpload(); realm.syncSession.pause(); - realm.write(() => realm.add(maybeProduct)); + realm.write(() => realm.add(Product(maybeId, "maybe synced"))); await triggerClientReset(realm, restartSession: false); realm.syncSession.resume(); @@ -349,7 +357,7 @@ Future main([List? args]) async { bool recovery = false; bool discard = false; - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema], + final config = Configuration.flexibleSync(user, getSyncSchema(), clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler( onBeforeReset: (beforeResetRealm) => onBeforeCompleter.complete(), onAfterRecovery: (Realm beforeResetRealm, Realm afterResetRealm) { @@ -389,7 +397,7 @@ Future main([List? args]) async { final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: DiscardUnsyncedChangesHandler( onBeforeReset: (beforeResetRealm) async { await Future.delayed(Duration(seconds: 1)); @@ -426,7 +434,7 @@ Future main([List? args]) async { late ClientResetError clientResetErrorOnManualFallback; final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: DiscardUnsyncedChangesHandler( onBeforeReset: (beforeResetRealm) { onBeforeResetOccurred++; @@ -457,9 +465,7 @@ Future main([List? args]) async { expect(onAfterResetOccurred, 1); expect(onBeforeResetOccurred, 1); - expect(clientResetErrorOnManualFallback.category, SyncErrorCategory.client); - expect(clientResetErrorOnManualFallback.code, SyncClientErrorCode.autoClientResetFailure); - expect(clientResetErrorOnManualFallback.sessionErrorCode, SyncSessionErrorCode.unknown); + expect(clientResetErrorOnManualFallback.message, isNotEmpty); }); // 1. userA adds [task0, task1, task2] and syncs it, then disconnects @@ -480,10 +486,10 @@ Future main([List? args]) async { final task1Id = ObjectId(); final task2Id = ObjectId(); final task3Id = ObjectId(); - + List filterByIds = [task0Id, task1Id, task2Id, task3Id]; comparer(Task t1, ObjectId id) => t1.id == id; - final configA = Configuration.flexibleSync(userA, [Task.schema], clientResetHandler: RecoverUnsyncedChangesHandler( + final configA = Configuration.flexibleSync(userA, getSyncSchema(), clientResetHandler: RecoverUnsyncedChangesHandler( onAfterReset: (beforeResetRealm, afterResetRealm) { try { _checkProducts(beforeResetRealm, comparer, expectedList: [task0Id, task1Id], notExpectedList: [task2Id, task3Id]); @@ -495,7 +501,7 @@ Future main([List? args]) async { }, )); - final configB = Configuration.flexibleSync(userB, [Schedule.schema, Task.schema], clientResetHandler: RecoverUnsyncedChangesHandler( + final configB = Configuration.flexibleSync(userB, getSyncSchema(), clientResetHandler: RecoverUnsyncedChangesHandler( onAfterReset: (beforeResetRealm, afterResetRealm) { try { _checkProducts(beforeResetRealm, comparer, expectedList: [task0Id, task1Id, task2Id, task3Id]); @@ -507,8 +513,8 @@ Future main([List? args]) async { }, )); - final realmA = await _syncRealmForUser(configA, [Task(task0Id), Task(task1Id), Task(task2Id)]); - final realmB = await _syncRealmForUser(configB); + final realmA = await _syncRealmForUser(configA, filterByIds, [Task(task0Id), Task(task1Id), Task(task2Id)]); + final realmB = await _syncRealmForUser(configB, filterByIds); realmA.syncSession.pause(); realmB.syncSession.pause(); @@ -542,7 +548,7 @@ Future main([List? args]) async { late ClientResetError clientResetError; final config = Configuration.flexibleSync( user, - [Task.schema, Schedule.schema], + getSyncSchema(), clientResetHandler: ManualRecoveryHandler((syncError) { clientResetError = syncError; resetCompleter.complete(); @@ -554,21 +560,16 @@ Future main([List? args]) async { await triggerClientReset(realm); await resetCompleter.future.wait(defaultWaitTimeout, "ClientResetError is not reported."); - expect(clientResetError.category, SyncErrorCategory.session); - expect(clientResetError.code, SyncClientErrorCode.unknown); - expect(clientResetError.sessionErrorCode, SyncSessionErrorCode.badClientFileIdent); - expect(clientResetError.isFatal, isTrue); + expect(clientResetError.message, isNotEmpty); - expect(clientResetError.detailedMessage, isNotEmpty); - expect(clientResetError.message == clientResetError.detailedMessage, isFalse); expect(clientResetError.backupFilePath, isNotEmpty); }); } -Future _syncRealmForUser(FlexibleSyncConfiguration config, [List? items]) async { +Future _syncRealmForUser(FlexibleSyncConfiguration config, List filterByIds, [List? items]) async { final realm = getRealm(config); realm.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.query(r'_id IN $0', [filterByIds])); }); await realm.subscriptions.waitForSynchronization(); @@ -581,8 +582,8 @@ Future _syncRealmForUser(FlexibleSyncConfiguration return realm; } -void _checkProducts(Realm realm, bool Function(T, O) truePredicate, - {required List expectedList, List? notExpectedList}) { +void _checkProducts(Realm realm, bool Function(T, ObjectId) truePredicate, + {required List expectedList, List? notExpectedList}) { final all = realm.all(); for (var expected in expectedList) { if (!all.any((p) => truePredicate(p, expected))) { diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 93b80494f..94a3884b3 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -82,7 +82,7 @@ Future main([List? args]) async { var customDefaultRealmName = "myRealmName.realm"; Configuration.defaultRealmName = customDefaultRealmName; - var config = Configuration.flexibleSync(user, [Task.schema]); + var config = Configuration.flexibleSync(user, getSyncSchema()); expect(path.basename(config.path), path.basename(customDefaultRealmName)); var realm = getRealm(config); @@ -91,7 +91,7 @@ Future main([List? args]) async { //set a new defaultRealmName customDefaultRealmName = "anotherRealmName.realm"; Configuration.defaultRealmName = customDefaultRealmName; - config = Configuration.flexibleSync(user, [Task.schema]); + config = Configuration.flexibleSync(user, getSyncSchema()); realm = getRealm(config); expect(path.basename(realm.config.path), customDefaultRealmName); }); @@ -108,7 +108,7 @@ Future main([List? args]) async { var app = App(appConfig); var user = await app.logIn(Credentials.anonymous()); - var config = Configuration.flexibleSync(user, [Task.schema]); + var config = Configuration.flexibleSync(user, getSyncSchema()); expect(path.dirname(config.path), startsWith(path.dirname(customDefaultRealmPath))); var realm = getRealm(config); @@ -125,7 +125,7 @@ Future main([List? args]) async { app = App(appConfig); user = await app.logIn(Credentials.anonymous()); - config = Configuration.flexibleSync(user, [Task.schema]); + config = Configuration.flexibleSync(user, getSyncSchema()); realm = getRealm(config); expect(path.dirname(realm.config.path), startsWith(path.dirname(customDefaultRealmPath))); }); @@ -497,7 +497,7 @@ Future main([List? args]) async { final user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); var invoked = false; - var config = Configuration.flexibleSync(user, [Event.schema], shouldCompactCallback: (totalSize, usedSize) { + var config = Configuration.flexibleSync(user, getSyncSchema(), shouldCompactCallback: (totalSize, usedSize) { invoked = true; return false; }); @@ -510,7 +510,7 @@ Future main([List? args]) async { final app = App(appConfig); final user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); - final config = Configuration.flexibleSync(user, [Car.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); expect(config.path, contains(user.id)); expect(config.path, contains(appConfig.appId)); @@ -520,7 +520,7 @@ Future main([List? args]) async { final app = App(appConfig); final user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); - final config = Configuration.flexibleSync(user, [Car.schema], path: 'my-custom-path.realm'); + final config = Configuration.flexibleSync(user, getSyncSchema(), path: 'my-custom-path.realm'); expect(config.path, 'my-custom-path.realm'); }); @@ -532,7 +532,7 @@ Future main([List? args]) async { path.dirname(Configuration.defaultStoragePath), path.basename('my-custom-realm-name.realm'), ); - final config = Configuration.flexibleSync(user, [Event.schema], path: customPath); + final config = Configuration.flexibleSync(user, getSyncSchema(), path: customPath); var realm = getRealm(config); }); @@ -543,8 +543,7 @@ Future main([List? args]) async { final dir = await Directory.systemTemp.createTemp(); final realmPath = path.join(dir.path, 'test.realm'); - final schema = [Task.schema]; - final flexibleSyncConfig = Configuration.flexibleSync(user, schema, path: realmPath); + final flexibleSyncConfig = Configuration.flexibleSync(user, getSyncSchema(), path: realmPath); final realm = getRealm(flexibleSyncConfig); final oid = ObjectId(); realm.subscriptions.update((mutableSubscriptions) { @@ -553,7 +552,7 @@ Future main([List? args]) async { realm.write(() => realm.add(Task(oid))); realm.close(); - final disconnectedSyncConfig = Configuration.disconnectedSync(schema, path: realmPath); + final disconnectedSyncConfig = Configuration.disconnectedSync([Task.schema], path: realmPath); final disconnectedRealm = getRealm(disconnectedSyncConfig); expect(disconnectedRealm.find(oid), isNotNull); }); @@ -587,7 +586,7 @@ Future main([List? args]) async { List key = List.generate(encryptionKeySize + 10, (i) => random.nextInt(256)); expect( - () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), + () => Configuration.flexibleSync(user, getSyncSchema(), encryptionKey: key), throws("Wrong encryption key size"), ); }); @@ -611,7 +610,7 @@ Future main([List? args]) async { final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final config = Configuration.flexibleSync(user, [Task.schema], maxNumberOfActiveVersions: 1); + final config = Configuration.flexibleSync(user, getSyncSchema(), maxNumberOfActiveVersions: 1); final realm = await getRealmAsync(config); // First writing to the Realm when opening realm.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm.all())); expect(() => realm.write(() {}), throws("in the Realm exceeded the limit of 1")); @@ -621,7 +620,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final disconnectedConfig = Configuration.disconnectedSync([Task.schema], path: config.path, maxNumberOfActiveVersions: 1); final realm = getRealm(disconnectedConfig); // First writing to the Realm when opening diff --git a/test/credentials_test.dart b/test/credentials_test.dart index 78f1d29f8..13cec2e9d 100644 --- a/test/credentials_test.dart +++ b/test/credentials_test.dart @@ -38,8 +38,10 @@ Future main([List? args]) async { expect(user1, user2); expect(user1, isNot(user3)); - expect(user1.provider, AuthProviderType.anonymous); - expect(user3.provider, AuthProviderType.anonymous); + expect(user1.identities.length, 1); + expect(user1.identities.first.provider, AuthProviderType.anonymous); + expect(user3.identities.length, 1); + expect(user3.identities.first.provider, AuthProviderType.anonymous); }); test('Credentials email/password', () { @@ -208,7 +210,7 @@ Future main([List? args]) async { expect(user.state, UserState.loggedIn); expect(user.identities[0].id, userId); - expect(user.provider, AuthProviderType.jwt); + expect(user.identities[0].provider, AuthProviderType.jwt); expect(user.profile.email, username); expect(user.profile.name, username); expect(user.profile.gender, "male"); @@ -261,7 +263,7 @@ Future main([List? args]) async { var userId = emailIdentity.id; expect(user.state, UserState.loggedIn); - expect(user.provider, AuthProviderType.emailPassword); + expect(user.identities.first.provider, AuthProviderType.emailPassword); expect(user.profile.email, username); expect(user.profile.name, isNull); expect(user.profile.gender, isNull); @@ -285,7 +287,6 @@ Future main([List? args]) async { expect(jwtUser.state, UserState.loggedIn); expect(jwtUser.identities.singleWhere((identity) => identity.provider == AuthProviderType.jwt).id, jwtUserId); expect(jwtUser.identities.singleWhere((identity) => identity.provider == AuthProviderType.emailPassword).id, userId); - expect(jwtUser.provider, AuthProviderType.jwt); expect(jwtUser.profile.email, username); expect(jwtUser.profile.name, username); expect(jwtUser.profile.gender, "male"); @@ -335,7 +336,6 @@ Future main([List? args]) async { final credentials = Credentials.function(payload); final user = await app.logIn(credentials); expect(user.identities[0].id, userId); - expect(user.provider, AuthProviderType.function); expect(user.identities[0].provider, AuthProviderType.function); }); @@ -347,14 +347,14 @@ Future main([List? args]) async { final credentials = Credentials.function(payload); final user = await app.logIn(credentials); expect(user.identities[0].id, userId); - expect(user.provider, AuthProviderType.function); + expect(user.identities[0].provider, AuthProviderType.function); user.logOut(); final sameUser = await app.logIn(credentials); expect(sameUser.id, user.id); expect(sameUser.identities[0].id, userId); - expect(sameUser.provider, AuthProviderType.function); + expect(sameUser.identities[0].provider, AuthProviderType.function); }); test('Credentials providers', () { diff --git a/test/embedded_test.dart b/test/embedded_test.dart index 656c52180..d58374809 100644 --- a/test/embedded_test.dart +++ b/test/embedded_test.dart @@ -16,8 +16,6 @@ // //////////////////////////////////////////////////////////////////////////////// -import 'dart:typed_data'; - import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; @@ -36,14 +34,6 @@ Future main([List? args]) async { return getRealm(config); } - Future getSyncRealm(AppConfiguration config) async { - final app = App(config); - final user = await getAnonymousUser(app); - final realmConfig = Configuration.flexibleSync( - user, [AllTypesEmbedded.schema, ObjectWithEmbedded.schema, RecursiveEmbedded1.schema, RecursiveEmbedded2.schema, RecursiveEmbedded3.schema]); - return getRealm(realmConfig); - } - test('Local Realm with orphan embedded schemas works', () { final config = Configuration.local([AllTypesEmbedded.schema]); final realm = getRealm(config); @@ -58,10 +48,10 @@ Future main([List? args]) async { baasTest('Synchronized Realm with orphan embedded schemas throws', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [AllTypesEmbedded.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); expect(() => getRealm(config), throws("Embedded object 'AllTypesEmbedded' is unreachable by any link path from top level objects")); - }); + }, skip: "This test requires a new app service with missing embedded parent schema."); test('Embedded object roundtrip', () { final realm = getLocalRealm(); @@ -400,7 +390,7 @@ Future main([List? args]) async { }); baasTest('Embedded objects synchronization', (config) async { - final realm1 = await getSyncRealm(config); + final realm1 = await getIntegrationRealm(appConfig: config); final differentiator = Uuid.v4(); realm1.subscriptions.update((mutableSubscriptions) { @@ -417,7 +407,7 @@ Future main([List? args]) async { await realm1.subscriptions.waitForSynchronization(); await realm1.syncSession.waitForUpload(); - final realm2 = await getSyncRealm(config); + final realm2 = await getIntegrationRealm(appConfig: config); realm2.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realm2.query(r'differentiator = $0', [differentiator])); }); diff --git a/test/list_test.dart b/test/list_test.dart index 7644396aa..f93dab8a7 100644 --- a/test/list_test.dart +++ b/test/list_test.dart @@ -1233,7 +1233,7 @@ Future main([List? args]) async { playersAsResults.changes, emitsInOrder([ isA>().having((changes) => changes.inserted, 'inserted', []), // always an empty event on subscription - isA>().having((changes) => changes.isCleared, 'isCleared', true), + isA>().having((changes) => changes.results.isEmpty, 'isCleared', true), ])); realm.write(() => team.players.clear()); expect(playersAsResults.length, 0); diff --git a/test/manual_test.dart b/test/manual_test.dart index b5c0922df..3847bb53a 100644 --- a/test/manual_test.dart +++ b/test/manual_test.dart @@ -148,7 +148,7 @@ Future main([List? args]) async { final credentials = Credentials.facebook(accessToken); final user = await app.logIn(credentials); expect(user.state, UserState.loggedIn); - expect(user.provider, AuthProviderType.facebook); + expect(user.identities[0].provider, AuthProviderType.facebook); expect(user.profile.name, "Open Graph Test User"); }, skip: "Manual test"); }, skip: "Manual tests"); diff --git a/test/realm_logger_test.dart b/test/realm_logger_test.dart index 3903d10eb..7d527acea 100644 --- a/test/realm_logger_test.dart +++ b/test/realm_logger_test.dart @@ -212,11 +212,14 @@ Future main([List? args]) async { return result; }); + final expected = [ + const LoggedMessage(RealmLogLevel.error, "2"), + const LoggedMessage(RealmLogLevel.error, "2"), + const LoggedMessage(RealmLogLevel.error, "first only"), + const LoggedMessage(RealmLogLevel.trace, "3") + ]; + //first isolate should have collected all the messages - expect(actual.length, 4); - expect(actual[0], const LoggedMessage(RealmLogLevel.error, "2")); - expect(actual[1], const LoggedMessage(RealmLogLevel.error, "2")); - expect(actual[2], const LoggedMessage(RealmLogLevel.error, "first only")); - expect(actual[3], const LoggedMessage(RealmLogLevel.trace, "3")); + expect(actual, expected); }); } diff --git a/test/realm_set_test.dart b/test/realm_set_test.dart index ac85116d7..e99df0d6e 100644 --- a/test/realm_set_test.dart +++ b/test/realm_set_test.dart @@ -565,9 +565,6 @@ Future main([List? args]) async { }); test('RealmSet<$type> basic operations on unmanaged sets', () { - var config = Configuration.local([TestRealmSets.schema, Car.schema]); - var realm = getRealm(config); - var testSet = TestRealmSets(1); var set = testSet.setByType(type).set; var values = testSet.setByType(type).values; @@ -716,7 +713,7 @@ Future main([List? args]) async { carsResult.changes, emitsInOrder([ isA>().having((changes) => changes.inserted, 'inserted', []), // always an empty event on subscription - isA>().having((changes) => changes.isCleared, 'isCleared', true), + isA>().having((changes) => changes.results.isEmpty, 'isCleared', true), ])); realm.write(() => testSets.objectsSet.clear()); }); @@ -801,7 +798,7 @@ Future main([List? args]) async { if (count > 1) fail('Should only receive one event'); } }); - + test('Query on RealmSet with IN-operator', () { var config = Configuration.local([TestRealmSets.schema, Car.schema]); var realm = getRealm(config); diff --git a/test/realm_test.dart b/test/realm_test.dart index 09c17a3cc..ebd184552 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -564,7 +564,7 @@ Future main([List? args]) async { // Wait for realm2 to see the changes. This would not be necessary if we // cache native instances. - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(Duration(milliseconds: 10)); expect(realm2.all().length, 1); expect(realm2.all().single.name, "Peter"); @@ -995,12 +995,12 @@ Future main([List? args]) async { final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); List key = List.generate(encryptionKeySize, (i) => random.nextInt(256)); - final configuration = Configuration.flexibleSync(user, [Task.schema], encryptionKey: key); + final configuration = Configuration.flexibleSync(user, getSyncSchema(), encryptionKey: key); final realm = getRealm(configuration); expect(realm.isClosed, false); expect( - () => getRealm(Configuration.flexibleSync(user, [Task.schema])), + () => getRealm(Configuration.flexibleSync(user, getSyncSchema())), throws("already opened with a different encryption key"), ); }); @@ -1017,7 +1017,7 @@ Future main([List? args]) async { Future.delayed(Duration(milliseconds: 10), () => transaction.commit()); final transaction1 = await realm.beginWriteAsync(); - + await transaction1.commitAsync(); expect(transaction.isOpen, false); @@ -1231,7 +1231,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final realm = await getRealmAsync(configuration); expect(realm.isClosed, false); @@ -1254,7 +1254,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final cancellationToken = CancellationToken(); cancellationToken.cancel(); @@ -1265,7 +1265,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final cancellationToken = CancellationToken(); final isRealmCancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).isCancelled(); @@ -1277,7 +1277,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final cancellationToken = CancellationToken(); @@ -1292,7 +1292,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final cancellationToken1 = CancellationToken(); final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken1).isCancelled(); @@ -1308,12 +1308,12 @@ Future main([List? args]) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous()); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); + final configuration1 = Configuration.flexibleSync(user1, getSyncSchema()); final cancellationToken1 = CancellationToken(); final isRealm1Cancelled = getRealmAsync(configuration1, cancellationToken: cancellationToken1).isCancelled(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); + final configuration2 = Configuration.flexibleSync(user2, getSyncSchema()); final cancellationToken2 = CancellationToken(); final isRealm2Cancelled = getRealmAsync(configuration2, cancellationToken: cancellationToken2).isCancelled(); @@ -1326,7 +1326,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); final cancellationToken = CancellationToken(); final realm = await getRealmAsync(configuration, cancellationToken: cancellationToken); @@ -1342,7 +1342,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); + final configuration = Configuration.flexibleSync(user, getSyncSchema()); int transferredBytes = -1; final completer = Completer(); @@ -1399,8 +1399,7 @@ Future main([List? args]) async { expect(progressReturned, isFalse); }); - const compactTest = "compact_test"; - void addDataForCompact(Realm realm) { + void addDataForCompact(Realm realm, String compactTest) { realm.write(() { for (var i = 0; i < 2500; i++) { realm.add(Product(ObjectId(), compactTest)); @@ -1418,6 +1417,7 @@ Future main([List? args]) async { Future createRealmForCompact(Configuration config) async { var realm = getRealm(config); + final compactTest = generateRandomString(10); if (config is FlexibleSyncConfiguration) { realm.subscriptions.update((mutableSubscriptions) { @@ -1426,7 +1426,7 @@ Future main([List? args]) async { await realm.subscriptions.waitForSynchronization(); } - addDataForCompact(realm); + addDataForCompact(realm, compactTest); if (config is FlexibleSyncConfiguration) { await realm.syncSession.waitForDownload(); @@ -1533,41 +1533,34 @@ Future main([List? args]) async { baasTest('Realm - synced realm can be compacted', (appConfiguration) async { final app = App(appConfiguration); - final credentials = Credentials.anonymous(); + final credentials = Credentials.anonymous(reuseCredentials: false); var user = await app.logIn(credentials); final path = p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm"); - final config = Configuration.flexibleSync(user, [Product.schema], path: path); + final config = Configuration.flexibleSync(user, getSyncSchema(), path: path); final beforeCompactSize = await createRealmForCompact(config); - user.logOut(); - Future.delayed(Duration(seconds: 5)); - - final compacted = Realm.compact(config); + final compacted = await runWithRetries(() => Realm.compact(config)); validateCompact(compacted, config.path, beforeCompactSize); //test the realm can be opened. - final realm = getRealm(Configuration.disconnectedSync([Product.schema], path: path)); - }); + final realm = getRealm(config); + }, appName: AppNames.autoConfirm); baasTest('Realm - synced encrypted realm can be compacted', (appConfiguration) async { final app = App(appConfiguration); - final credentials = Credentials.anonymous(); - final path = p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm"); + final credentials = Credentials.anonymous(reuseCredentials: false); var user = await app.logIn(credentials); List key = List.generate(encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.flexibleSync(user, [Product.schema], encryptionKey: key, path: path); + final path = p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm"); + final config = Configuration.flexibleSync(user, getSyncSchema(), encryptionKey: key, path: path); final beforeCompactSize = await createRealmForCompact(config); - user.logOut(); - Future.delayed(Duration(seconds: 5)); - - final compacted = Realm.compact(config); + final compacted = await runWithRetries(() => Realm.compact(config)); validateCompact(compacted, config.path, beforeCompactSize); - user = await app.logIn(credentials); //test the realm can be opened. - final realm = getRealm(Configuration.disconnectedSync([Product.schema], path: path, encryptionKey: key)); - }); + final realm = getRealm(config); + }, appName: AppNames.autoConfirm); test('Realm writeCopy local to existing file', () { final config = Configuration.local([Car.schema]); @@ -1593,7 +1586,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(reuseCredentials: false); var user = await app.logIn(credentials); - final configCopy = Configuration.flexibleSync(user, [Product.schema]); + final configCopy = Configuration.flexibleSync(user, getSyncSchema()); expect(() => originalRealm.writeCopy(configCopy), throws("Realm cannot be converted to a flexible sync realm unless flexible sync is already enabled")); }); @@ -1709,14 +1702,14 @@ Future main([List? args]) async { baasTest('Realm writeCopy Sync->Sync - $testDescription can be opened and synced', (appConfiguration) async { final app = App(appConfiguration); var user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final originalConfig = Configuration.flexibleSync(user1, [Product.schema], encryptionKey: sourceEncryptedKey); + final originalConfig = Configuration.flexibleSync(user1, getSyncSchema(), encryptionKey: sourceEncryptedKey); final originalRealm = getRealm(originalConfig); var itemsCount = 2; final productNamePrefix = generateRandomString(10); await _addDataToAtlas(originalRealm, productNamePrefix, itemsCount: itemsCount); var user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configCopy = Configuration.flexibleSync(user2, [Product.schema], encryptionKey: destinationEncryptedKey); + final configCopy = Configuration.flexibleSync(user2, getSyncSchema(), encryptionKey: destinationEncryptedKey); originalRealm.writeCopy(configCopy); originalRealm.close(); @@ -1737,7 +1730,7 @@ Future main([List? args]) async { // Create another user's realm and download the data var anotherUser = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final anotherUserRealm = getRealm(Configuration.flexibleSync(anotherUser, [Product.schema])); + final anotherUserRealm = getRealm(Configuration.flexibleSync(anotherUser, getSyncSchema())); await _addSubscriptions(anotherUserRealm, productNamePrefix); await anotherUserRealm.syncSession.waitForUpload(); await anotherUserRealm.syncSession.waitForDownload(); @@ -1757,7 +1750,7 @@ Future main([List? args]) async { baasTest('Realm writeCopy Sync->Local - $testDescription can be opened and synced', (appConfiguration) async { final app = App(appConfiguration); var user = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final originalConfig = Configuration.flexibleSync(user, [Product.schema], encryptionKey: sourceEncryptedKey); + final originalConfig = Configuration.flexibleSync(user, getSyncSchema(), encryptionKey: sourceEncryptedKey); final originalRealm = getRealm(originalConfig); var itemsCount = 2; final productNamePrefix = generateRandomString(10); @@ -1896,7 +1889,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final productName = generateRandomUnicodeString(); final user = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final config = Configuration.flexibleSync(user, [Product.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); await _addSubscriptions(realm, productName); realm.write(() => realm.add(Product(ObjectId(), productName))); @@ -1954,13 +1947,13 @@ extension on Future { Future _subscribeForAtlasAddedData(App app, {String? queryDifferentiator, int itemsCount = 100}) async { final productNamePrefix = queryDifferentiator ?? generateRandomString(10); final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final config1 = Configuration.flexibleSync(user1, [Product.schema]); + final config1 = Configuration.flexibleSync(user1, getSyncSchema()); final realm1 = getRealm(config1); await _addSubscriptions(realm1, productNamePrefix); realm1.close(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final config2 = Configuration.flexibleSync(user2, [Product.schema]); + final config2 = Configuration.flexibleSync(user2, getSyncSchema()); final realm2 = getRealm(config2); await _addDataToAtlas(realm2, productNamePrefix, itemsCount: itemsCount); realm2.close(); diff --git a/test/session_test.dart b/test/session_test.dart index 27e1a4d36..2c60938a2 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -17,10 +17,8 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:async'; - import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; -import '../lib/src/session.dart' show SessionInternal; import 'test.dart'; Future main([List? args]) async { @@ -43,7 +41,7 @@ Future main([List? args]) async { baasTest('SyncSession.user returns a valid user', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); expect(realm.syncSession.user, user); @@ -55,7 +53,7 @@ Future main([List? args]) async { baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); // Don't use getRealm because we want the Realm to survive final realm = Realm(config); @@ -279,41 +277,6 @@ Future main([List? args]) async { await downloadData.subscription.cancel(); }); - baasTest('SyncSession test error handler', (configuration) async { - final app = App(configuration); - final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: (syncError) { - expect(syncError, isA()); - final sessionError = syncError.as(); - expect(sessionError.category, SyncErrorCategory.session); - expect(sessionError.isFatal, false); - expect(sessionError.code, SyncSessionErrorCode.badAuthentication); - expect(sessionError.detailedMessage, "Simulated session error"); - expect(sessionError.message, "Bad user authentication (BIND)"); - }); - - final realm = getRealm(config); - - realm.syncSession.raiseError(SyncErrorCategory.session, SyncSessionErrorCode.badAuthentication.code, false); - }); - - baasTest('SyncSession test fatal error handler', (configuration) async { - final app = App(configuration); - final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: (syncError) { - expect(syncError, isA()); - final syncClientError = syncError.as(); - expect(syncClientError.category, SyncErrorCategory.client); - expect(syncClientError.isFatal, true); - expect(syncClientError.code, SyncClientErrorCode.badChangeset); - expect(syncClientError.detailedMessage, "Simulated session error"); - expect(syncClientError.message, "Bad changeset (DOWNLOAD)"); - }); - final realm = getRealm(config); - - realm.syncSession.raiseError(SyncErrorCategory.client, SyncClientErrorCode.badChangeset.code, true); - }); - baasTest('SyncSession.getConnectionStateStream', (configuration) async { final realm = await getIntegrationRealm(); @@ -355,7 +318,7 @@ Future main([List? args]) async { baasTest('SyncSession when Realm is closed gets closed as well', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); final session = realm.syncSession; @@ -365,28 +328,6 @@ Future main([List? args]) async { expect(() => session.state, throws()); }); - - for (SyncWebSocketErrorCode errorCode in SyncWebSocketErrorCode.values.where((v) => v != SyncWebSocketErrorCode.unknown)) { - baasTest('Sync Web Socket Error ${errorCode.name}', (configuration) async { - final app = App(configuration); - final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync( - user, - [Task.schema], - syncErrorHandler: (syncError) { - expect(syncError, isA()); - final sessionError = syncError.as(); - expect(sessionError.category, SyncErrorCategory.webSocket); - expect(sessionError.code, errorCode); - expect(sessionError.detailedMessage, "Simulated session error"); - expect(sessionError.detailedMessage, isNot(sessionError.message)); - expect(syncError.codeValue, errorCode.code); - }, - ); - final realm = getRealm(config); - realm.syncSession.raiseError(SyncErrorCategory.webSocket, errorCode.code, false); - }); - } } class StreamProgressData { diff --git a/test/subscription_test.dart b/test/subscription_test.dart index fbbae0e9c..75cf7441b 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -35,12 +35,7 @@ void testSubscriptions(String name, FutureOr Function(Realm) testFunc) asy final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [ - Task.schema, - Schedule.schema, - Event.schema, - ]) - ..sessionStopPolicy = SessionStopPolicy.immediately; + final configuration = Configuration.flexibleSync(user, getSyncSchema())..sessionStopPolicy = SessionStopPolicy.immediately; final realm = getRealm(configuration); await testFunc(realm); }); @@ -482,26 +477,22 @@ Future main([List? args]) async { final userX = await appX.logIn(credentials); final userY = await appY.logIn(credentials); - final realmX = getRealm(Configuration.flexibleSync(userX, [Task.schema])); - final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema])); + final realmX = getRealm(Configuration.flexibleSync(userX, getSyncSchema())); + final objectId = ObjectId(); realmX.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realmX.all()); + mutableSubscriptions.add(realmX.query(r'_id == $0', [objectId])); }); - - final objectId = ObjectId(); + await realmX.subscriptions.waitForSynchronization(); realmX.write(() => realmX.add(Task(objectId))); + await realmX.syncSession.waitForUpload(); + final realmY = getRealm(Configuration.flexibleSync(userY, getSyncSchema())); realmY.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realmY.all()); + mutableSubscriptions.add(realmY.query(r'_id == $0', [objectId])); }); - - await realmX.subscriptions.waitForSynchronization(); await realmY.subscriptions.waitForSynchronization(); - - await realmX.syncSession.waitForUpload(); await realmY.syncSession.waitForDownload(); - final task = realmY.find(objectId); expect(task, isNotNull); }); @@ -510,10 +501,7 @@ Future main([List? args]) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync( - user, - [Task.schema], - ); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); expect(() => realm.write(() => realm.add(Task(ObjectId()))), throws("no flexible sync subscription has been created")); @@ -553,7 +541,7 @@ Future main([List? args]) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); final subscriptions = realm.subscriptions; @@ -568,7 +556,7 @@ Future main([List? args]) async { final productNamePrefix = generateRandomString(4); final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Product.schema], syncErrorHandler: (syncError) { + final config = Configuration.flexibleSync(user, getSyncSchema(), syncErrorHandler: (syncError) { compensatingWriteError = syncError; }); final realm = getRealm(config); @@ -581,13 +569,8 @@ Future main([List? args]) async { await realm.syncSession.waitForUpload(); expect(compensatingWriteError, isA()); - final sessionError = compensatingWriteError.as(); - expect(sessionError.category, SyncErrorCategory.session); - expect(sessionError.code, SyncSessionErrorCode.compensatingWrite); - expect(sessionError.message!.startsWith('Client attempted a write that is disallowed by permissions, or modifies an object outside the current query'), - isTrue); - expect(sessionError.detailedMessage, isNotEmpty); - expect(sessionError.message == sessionError.detailedMessage, isFalse); + final sessionError = compensatingWriteError as CompensatingWriteError; + expect(sessionError.message, startsWith('Client attempted a write that is not allowed')); expect(sessionError.compensatingWrites, isNotNull); final writeReason = sessionError.compensatingWrites!.first; expect(writeReason, isNotNull); diff --git a/test/test.dart b/test/test.dart index da73cca3b..8f16db576 100644 --- a/test/test.dart +++ b/test/test.dart @@ -327,6 +327,29 @@ class _ObjectWithDecimal { Decimal128? nullableDecimal; } +@RealmModel(ObjectType.asymmetricObject) +class _Asymmetric { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; + + late List<_Embedded> embeddedObjects; +} + +@RealmModel(ObjectType.embeddedObject) +class _Embedded { + late int value; + late RealmValue any; + _Symmetric? symmetric; +} + +@RealmModel() +class _Symmetric { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; +} + String? testName; Map arguments = {}; final baasApps = {}; @@ -393,7 +416,7 @@ Future setupTests(List? args) async { setUp(() { Realm.logger = Logger.detached('test run') - ..level = Level.INFO + ..level = Level.ALL ..onRecord.listen((record) { testing.printOnFailure('${record.time} ${record.level.name}: ${record.message}'); }); @@ -404,7 +427,7 @@ Future setupTests(List? args) async { addTearDown(() async { final paths = HashSet(); paths.add(path); - + realmCore.clearCachedApps(); while (_openRealms.isNotEmpty) { @@ -680,11 +703,11 @@ Future createServerApiKey(App app, String name, {bool enabled = true}) a return await client.createApiKey(baasApp, name, enabled); } -Future getIntegrationRealm({App? app, ObjectId? differentiator}) async { - app ??= App(await getAppConfig()); +Future getIntegrationRealm({App? app, ObjectId? differentiator, AppConfiguration? appConfig}) async { + app ??= App(appConfig ?? await getAppConfig()); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema, NullableTypes.schema]); + final config = Configuration.flexibleSync(user, getSyncSchema()); final realm = getRealm(config); if (differentiator != null) { realm.subscriptions.update((mutableSubscriptions) { @@ -809,3 +832,43 @@ void printSplunkLogLink(AppNames appName, String? uriVariable) { "https://splunk.corp.mongodb.com/en-US/app/search/search?q=search index=baas$host \"${app.uniqueName}-*\" | reverse | top error msg&earliest=-7d&latest=now&display.general.type=visualizations"); print("Splunk logs: $splunk"); } + +/// Schema list for default app service +/// used for all the flexible sync tests. +/// The full list of schemas is required when creating +/// a flexibleSync configuration to the default app service +/// to avoid causing breaking changes in development mode. +List getSyncSchema() { + return [ + Task.schema, + Schedule.schema, + Product.schema, + Event.schema, + AllTypesEmbedded.schema, + ObjectWithEmbedded.schema, + RecursiveEmbedded1.schema, + RecursiveEmbedded2.schema, + RecursiveEmbedded3.schema, + NullableTypes.schema, + Asymmetric.schema, + Embedded.schema, + Symmetric.schema, + ]; +} + +Future runWithRetries(bool Function() tester, {int retryDelay = 100, int attempts = 100}) async { + var success = tester(); + var timeout = retryDelay * attempts; + + while (!success && attempts > 0) { + await Future.delayed(Duration(milliseconds: retryDelay)); + success = tester(); + attempts--; + } + + if (!success) { + throw TimeoutException('Failed to meet condition after $timeout ms.'); + } + + return success; +} diff --git a/test/test.g.dart b/test/test.g.dart index b5d1e074b..3f7e00d55 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -2092,3 +2092,138 @@ class ObjectWithDecimal extends _ObjectWithDecimal ]); } } + +// ignore_for_file: type=lint +class Asymmetric extends _Asymmetric + with RealmEntity, RealmObjectBase, AsymmetricObject { + Asymmetric( + ObjectId id, { + Iterable embeddedObjects = const [], + }) { + RealmObjectBase.set(this, '_id', id); + RealmObjectBase.set>( + this, 'embeddedObjects', RealmList(embeddedObjects)); + } + + Asymmetric._(); + + @override + ObjectId get id => RealmObjectBase.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => RealmObjectBase.set(this, '_id', value); + + @override + RealmList get embeddedObjects => + RealmObjectBase.get(this, 'embeddedObjects') + as RealmList; + @override + set embeddedObjects(covariant RealmList value) => + throw RealmUnsupportedSetError(); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + Asymmetric freeze() => RealmObjectBase.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObjectBase.registerFactory(Asymmetric._); + return const SchemaObject( + ObjectType.asymmetricObject, Asymmetric, 'Asymmetric', [ + SchemaProperty('id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + SchemaProperty('embeddedObjects', RealmPropertyType.object, + linkTarget: 'Embedded', collectionType: RealmCollectionType.list), + ]); + } +} + +// ignore_for_file: type=lint +class Embedded extends _Embedded + with RealmEntity, RealmObjectBase, EmbeddedObject { + Embedded( + int value, { + RealmValue any = const RealmValue.nullValue(), + Symmetric? symmetric, + }) { + RealmObjectBase.set(this, 'value', value); + RealmObjectBase.set(this, 'any', any); + RealmObjectBase.set(this, 'symmetric', symmetric); + } + + Embedded._(); + + @override + int get value => RealmObjectBase.get(this, 'value') as int; + @override + set value(int value) => RealmObjectBase.set(this, 'value', value); + + @override + RealmValue get any => + RealmObjectBase.get(this, 'any') as RealmValue; + @override + set any(RealmValue value) => RealmObjectBase.set(this, 'any', value); + + @override + Symmetric? get symmetric => + RealmObjectBase.get(this, 'symmetric') as Symmetric?; + @override + set symmetric(covariant Symmetric? value) => + RealmObjectBase.set(this, 'symmetric', value); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + Embedded freeze() => RealmObjectBase.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObjectBase.registerFactory(Embedded._); + return const SchemaObject(ObjectType.embeddedObject, Embedded, 'Embedded', [ + SchemaProperty('value', RealmPropertyType.int), + SchemaProperty('any', RealmPropertyType.mixed, optional: true), + SchemaProperty('symmetric', RealmPropertyType.object, + optional: true, linkTarget: 'Symmetric'), + ]); + } +} + +// ignore_for_file: type=lint +class Symmetric extends _Symmetric + with RealmEntity, RealmObjectBase, RealmObject { + Symmetric( + ObjectId id, + ) { + RealmObjectBase.set(this, '_id', id); + } + + Symmetric._(); + + @override + ObjectId get id => RealmObjectBase.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => RealmObjectBase.set(this, '_id', value); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + Symmetric freeze() => RealmObjectBase.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObjectBase.registerFactory(Symmetric._); + return const SchemaObject(ObjectType.realmObject, Symmetric, 'Symmetric', [ + SchemaProperty('id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + ]); + } +} diff --git a/test/user_test.dart b/test/user_test.dart index 53958a74f..0980af261 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -97,16 +97,6 @@ Future main([List? args]) async { expect(user.deviceId, isNotNull); }); - baasTest('User provider', (configuration) async { - final app = App(configuration); - final credentials = Credentials.anonymous(); - var user = await app.logIn(credentials); - expect(user.provider, AuthProviderType.anonymous); - - user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); - expect(user.provider, AuthProviderType.emailPassword); - }); - baasTest('User profile', (configuration) async { final app = App(configuration); final user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); @@ -380,9 +370,7 @@ Future main([List? args]) async { final credentials = Credentials.apiKey(key.value!); final apiKeyUser = await app.logIn(credentials); - expect(apiKeyUser.provider, AuthProviderType.apiKey); expect(apiKeyUser.id, user.id); - expect(apiKeyUser.refreshToken, isNot(user.refreshToken)); }); baasTest('User.apiKeys can login with reenabled key', (configuration) async { @@ -405,9 +393,7 @@ Future main([List? args]) async { await enableAndVerifyApiKey(user, key.id); final apiKeyUser = await app.logIn(credentials); - expect(apiKeyUser.provider, AuthProviderType.apiKey); expect(apiKeyUser.id, user.id); - expect(apiKeyUser.refreshToken, isNot(user.refreshToken)); }); baasTest("User.apiKeys can't login with deleted key", (configuration) async { @@ -452,16 +438,6 @@ Future main([List? args]) async { await expectLater(() => apiKeys.fetchAll(), throws('User must be logged in to fetch all API keys')); }); - baasTest("Credentials.apiKey user cannot access API keys", (configuration) async { - final app = App(configuration); - final user = await getIntegrationUser(app); - final apiKey = await createAndVerifyApiKey(user, 'my-key'); - - final apiKeyUser = await app.logIn(Credentials.apiKey(apiKey.value!)); - - expect(() => apiKeyUser.apiKeys, throws('Users logged in with API key cannot manage API keys')); - }); - baasTest("Credentials.apiKey with server-generated can login user", (configuration) async { final app = App(configuration); @@ -470,7 +446,6 @@ Future main([List? args]) async { final apiKeyUser = await app.logIn(credentials); - expect(apiKeyUser.provider, AuthProviderType.apiKey); expect(apiKeyUser.state, UserState.loggedIn); });