From 11ab21228639c3287c7e979ef9c52cb925241278 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 15 Sep 2023 17:30:42 +0200 Subject: [PATCH] Migrate to new sync error codes (#3440) --- .github/workflows/deploy-baas.yml | 2 +- CHANGELOG.md | 2 +- Realm/Realm/Exceptions/Sync/ClientError.cs | 6 - Realm/Realm/Exceptions/Sync/ErrorCode.cs | 314 +++++++++++------- Realm/Realm/Extensions/TestingExtensions.cs | 2 +- Realm/Realm/Handles/SessionHandle.cs | 6 +- Realm/Realm/Sync/Session.cs | 3 +- .../Realm.Tests/Sync/AsymmetricObjectTests.cs | 58 +--- Tests/Realm.Tests/Sync/FlexibleSyncTests.cs | 2 + Tests/Realm.Tests/Sync/SessionTests.cs | 21 +- Tests/Realm.Tests/Sync/SyncTestBase.cs | 32 +- wrappers/realm-core | 2 +- wrappers/src/error_handling.cpp | 2 +- wrappers/src/shared_realm_cs.cpp | 4 +- wrappers/src/sync_session_cs.cpp | 17 +- wrappers/src/websocket_cs.cpp | 2 +- 16 files changed, 261 insertions(+), 214 deletions(-) diff --git a/.github/workflows/deploy-baas.yml b/.github/workflows/deploy-baas.yml index 3008c6d486..0f772a9ba5 100755 --- a/.github/workflows/deploy-baas.yml +++ b/.github/workflows/deploy-baas.yml @@ -54,7 +54,7 @@ jobs: apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} clusterName: ${{ steps.generate-cluster-name.outputs.clusterName }}-${{ matrix.differentiator }} - clusterSize: M20 + clusterSize: M5 deploy-apps: name: Deploy Apps needs: deploy-baas diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d262b9873..0ebbb49f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## vNext (TBD) ### Enhancements -* None +* Streamlined some of the error codes reported in `SessionException`. A few error codes have been combined and some have been deprecated since they are no longer reported by the server. (Issue [#3295](https://github.com/realm/realm-dotnet/issues/3295)) ### Fixed * Fixed the message of the `MissingMemberException` being thrown when attempting to access a non-existent property with the dynamic API. (PR [#3432](https://github.com/realm/realm-dotnet/pull/3432)) diff --git a/Realm/Realm/Exceptions/Sync/ClientError.cs b/Realm/Realm/Exceptions/Sync/ClientError.cs index 4697cc5710..6cf93d3133 100644 --- a/Realm/Realm/Exceptions/Sync/ClientError.cs +++ b/Realm/Realm/Exceptions/Sync/ClientError.cs @@ -26,12 +26,6 @@ internal enum ClientError AutoClientResetFailed = 132, } - internal enum SessionErrorCategory : byte - { - ClientError = 0, - SessionError = 1 - } - internal enum ServerRequestsAction { NoAction = 0, diff --git a/Realm/Realm/Exceptions/Sync/ErrorCode.cs b/Realm/Realm/Exceptions/Sync/ErrorCode.cs index fd1fc7cf0d..f78d79dfeb 100644 --- a/Realm/Realm/Exceptions/Sync/ErrorCode.cs +++ b/Realm/Realm/Exceptions/Sync/ErrorCode.cs @@ -16,125 +16,201 @@ // //////////////////////////////////////////////////////////////////////////// -namespace Realms.Sync.Exceptions +using System; +using Realms.Sync.ErrorHandling; +using static System.Net.WebRequestMethods; + +namespace Realms.Sync.Exceptions; + +/// +/// Error code enumeration, indicating the type of the session error. +/// +/// +public enum ErrorCode { /// - /// Error code enumeration, indicating the type of the session error. - /// - /// - public enum ErrorCode - { - /// - /// Unrecognized error code. It usually indicates incompatibility between the authentication server and client SDK versions. - /// - Unknown = -1, - - /// - /// Other session level error has occurred. - /// - OtherSessionError = 201, - - /// - /// Path to Realm is invalid. - /// - IllegalRealmPath = 204, - - /// - /// Permission to Realm has been denied. - /// - PermissionDenied = 206, - - /// - /// The client file identifier is invalid. - /// - BadClientFileIdentifier = 208, - - /// - /// The server version is invalid. - /// - BadServerVersion = 209, - - /// - /// The client version is invalid. - /// - BadClientVersion = 210, - - /// - /// Histories have diverged and cannot be merged. - /// - DivergingHistories = 211, - - /// - /// The changeset is invalid. - /// - BadChangeset = 212, - - /// - /// The client file is invalid. - /// - BadClientFile = 217, - - /// - /// Client file has expired likely due to history compaction on the server. - /// - ClientFileExpired = 222, - - /// - /// 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 = 223, - - /// - /// The server has received too many sessions from this client. This is typically a transient error - /// but can also indicate that the client has too many Realms open at the same time. - /// - TooManySessions = 224, - - /// - /// 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 = 225, - - /// - /// The client attempted to create a subscription for a query is invalid/malformed. - /// - BadQuery = 226, - - /// - /// The client attempted to create an object that already exists outside their view. - /// - ObjectAlreadyExists = 227, - - /// - /// The server permissions for this file have changed since the last time it was used. - /// - ServerPermissionsChanged = 228, - - /// - /// The client tried to synchronize before initial sync has completed. Please wait for - /// the server process to complete and try again. - /// - InitialSyncNotCompleted = 229, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an object - /// outside the current query - requires client reset. - /// - WriteNotAllowed = 230, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an - /// object outside the current query, and the server undid the modification. - /// - CompensatingWrite = 231, - - /// - /// An error sent by the server when its data structures used to track client progress - /// become corrupted. - /// - BadProgress = 233, - } + /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. + /// + RuntimeError = 1000, + + /// + /// The partition value specified by the user is not valid - i.e. its the wrong type or is encoded incorrectly. + /// + BadPartitionValue = 1029, + + /// + /// 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 = 1038, + + /// + /// The changeset is invalid. + /// + BadChangeset = 1015, + + /// + /// The client attempted to create a subscription for a query is invalid/malformed. + /// + BadQuery = 1031, + + /// + /// A client reset has occurred. This error code will only be reported via a and only + /// in the case manual client reset handling is required - either via or when + /// ManualResetFallback is invoked on one of the automatic client reset handlers. + /// + /// + /// + ClientReset = 1032, + + /// + /// 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 = 1036, + + /// + /// Permission to Realm has been denied. + /// + PermissionDenied = 1037, + + /// + /// The server permissions for this file have changed since the last time it was used. + /// + ServerPermissionsChanged = 1040, + + /// + /// 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 = 1041, + + /// + /// Client attempted a write that is disallowed by permissions, or modifies an object + /// outside the current query - this will result in a . + /// + WriteNotAllowed = 1044, + + /// + /// Automatic client reset has failed. This will only be reported via + /// 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 = 1028, + + /// + /// The wrong sync type was used to connect to the server. This means that you're using + /// to connect to an app configured for flexible sync or that you're using to connect + /// to an app configured to use partition sync. + /// + WrongSyncType = 1043, + + /// + /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. + /// + [Obsolete("Use RuntimeError instead.")] + Unknown = RuntimeError, + + /// + /// Other session level error has occurred. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use RuntimeError instead.")] + OtherSessionError = RuntimeError, + + /// + /// Path to Realm is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use BadPartitionValue instead")] + IllegalRealmPath = BadPartitionValue, + + /// + /// The client file identifier is invalid. + /// + /// + [Obsolete("Use ClientReset instead")] + BadClientFileIdentifier = ClientReset, + + /// + /// The server version is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadServerVersion = ProtocolInvariantFailed, + + /// + /// The client version is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadClientVersion = ProtocolInvariantFailed, + + /// + /// Histories have diverged and cannot be merged. + /// + /// + [Obsolete("Use ClientReset instead")] + DivergingHistories = ClientReset, + + /// + /// The client file is invalid. + /// + /// + [Obsolete("Use ClientReset instead")] + BadClientFile = ClientReset, + + /// + /// Client file has expired likely due to history compaction on the server. + /// + /// + [Obsolete("Use ClientReset instead")] + ClientFileExpired = ClientReset, + + /// + /// The server has received too many sessions from this client. This is typically a transient error + /// but can also indicate that the client has too many Realms open at the same time. + /// + [Obsolete("This error code is no longer reported")] + TooManySessions = -2, + + /// + /// The client attempted to create an object that already exists outside their view. + /// + [Obsolete("This error code is no longer reported")] + ObjectAlreadyExists = -3, + + /// + /// The client tried to synchronize before initial sync has completed. Please wait for + /// the server process to complete and try again. + /// + [Obsolete("This error code is no longer reported")] + InitialSyncNotCompleted = -4, + + /// + /// Client attempted a write that is disallowed by permissions, or modifies an + /// object outside the current query, and the server undid the modification. + /// + /// + CompensatingWrite = 1033, + + /// + /// An error sent by the server when its data structures used to track client progress + /// become corrupted. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadProgress = ProtocolInvariantFailed, } diff --git a/Realm/Realm/Extensions/TestingExtensions.cs b/Realm/Realm/Extensions/TestingExtensions.cs index ab270a54ec..8e9917138a 100644 --- a/Realm/Realm/Extensions/TestingExtensions.cs +++ b/Realm/Realm/Extensions/TestingExtensions.cs @@ -44,7 +44,7 @@ public static void SimulateError(this Session session, ErrorCode errorCode, stri Argument.NotNull(session, nameof(session)); Argument.NotNull(message, nameof(message)); - session.ReportErrorForTesting((int)errorCode, SessionErrorCategory.SessionError, message, isFatal, ServerRequestsAction.ApplicationBug); + session.ReportErrorForTesting((int)errorCode, message, isFatal, ServerRequestsAction.ApplicationBug); } } } diff --git a/Realm/Realm/Handles/SessionHandle.cs b/Realm/Realm/Handles/SessionHandle.cs index 463a25fe64..e2c958603d 100644 --- a/Realm/Realm/Handles/SessionHandle.cs +++ b/Realm/Realm/Handles/SessionHandle.cs @@ -102,7 +102,7 @@ public static extern ulong register_progress_notifier(SessionHandle session, public static extern void wait(SessionHandle session, IntPtr task_completion_source, ProgressDirection direction, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_report_error_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern void report_error_for_testing(SessionHandle session, int error_code, SessionErrorCategory error_category, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal, int action); + public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal, int action); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_stop", CallingConvention = CallingConvention.Cdecl)] public static extern void stop(SessionHandle session, out NativeException ex); @@ -246,9 +246,9 @@ public IntPtr GetRawPointer() return NativeMethods.get_raw_pointer(this); } - public void ReportErrorForTesting(int errorCode, SessionErrorCategory errorCategory, string errorMessage, bool isFatal, ServerRequestsAction action) + public void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) { - NativeMethods.report_error_for_testing(this, errorCode, errorCategory, errorMessage, (IntPtr)errorMessage.Length, isFatal, (int)action); + NativeMethods.report_error_for_testing(this, errorCode, errorMessage, (IntPtr)errorMessage.Length, isFatal, (int)action); } public void Stop() diff --git a/Realm/Realm/Sync/Session.cs b/Realm/Realm/Sync/Session.cs index ac20ce3437..a07eb8496d 100644 --- a/Realm/Realm/Sync/Session.cs +++ b/Realm/Realm/Sync/Session.cs @@ -222,7 +222,8 @@ internal void CloseHandle(bool waitForShutdown = false) } } - internal void ReportErrorForTesting(int errorCode, SessionErrorCategory sessionErrorCategory, string errorMessage, bool isFatal, ServerRequestsAction action) => Handle.ReportErrorForTesting(errorCode, sessionErrorCategory, errorMessage, isFatal, action); + internal void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) + => Handle.ReportErrorForTesting(errorCode, errorMessage, isFatal, action); internal void RaisePropertyChanged(string propertyName) { diff --git a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs b/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs index 59c8c1cf89..a7fa3431d0 100644 --- a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs +++ b/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs @@ -28,8 +28,6 @@ using Realms.Sync; #if TEST_WEAVER using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; -using TestRealmObject = Realms.RealmObject; #else using TestAsymmetricObject = Realms.IAsymmetricObject; #endif @@ -90,31 +88,12 @@ public class AsymmetricObjectTests : SyncTestBase new object[] { "NullableGuidProperty", Guid.Parse("{C4EC8CEF-D62A-405E-83BB-B0A3D8DABB36}") } }; - [Test] - public void AddAsymmetricObjNotInSchema_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - - Assert.Throws(() => - { - realm.Write(() => - { - realm.Add(new BasicAsymmetricObject()); - }); - }); - }); - } - [Test] public void AddCollectionOfAsymmetricObjs() { SyncTestHelpers.RunBaasTestAsync(async () => { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; using var realm = await GetRealmAsync(flxConfig); var partitionLike = Guid.NewGuid().ToString(); @@ -148,7 +127,6 @@ public void AddCollection_WithSomeObjectsAlreadyAdded_Throws() SyncTestHelpers.RunBaasTestAsync(async () => { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; using var realm = await GetRealmAsync(flxConfig); var partitionLike = Guid.NewGuid().ToString(); @@ -183,7 +161,6 @@ public void AddHugeAsymmetricObj() ObjectId id = default; var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -206,9 +183,7 @@ public void AccessAsymmetricObjAfterAddedToRealm_Throws() SyncTestHelpers.RunBaasTestAsync(async () => { var partitionLike = Guid.NewGuid().ToString(); - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var asymmetribObj = new BasicAsymmetricObject { @@ -233,9 +208,7 @@ public void AddSameAsymmetricObjTwice_Throws() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var partitionLike = Guid.NewGuid().ToString(); var asymmetricObj = new BasicAsymmetricObject { @@ -261,8 +234,6 @@ public void SetAndRemotelyReadValue(string propertyName, object propertyValue) { ObjectId id = default; var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; - using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -290,7 +261,6 @@ public void MixAddingObjectAsymmetricAndNot() var partitionLike = Guid.NewGuid().ToString(); var id = new Random().Next(); var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject), typeof(PrimaryKeyInt32Object) }; flxConfig.PopulateInitialSubscriptions = (realm) => { @@ -346,15 +316,7 @@ public void EmbeddedObject_WhenParentAccessed_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] - { - typeof(AsymmetricObjectWithEmbeddedRecursiveObject), - typeof(EmbeddedLevel1), - typeof(EmbeddedLevel2), - typeof(EmbeddedLevel3) - }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedRecursiveObject { @@ -387,9 +349,7 @@ public void EmbeddedObject_WhenParentAccessedInList_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithEmbeddedListObject), typeof(EmbeddedIntPropertyObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedListObject(); parent.EmbeddedListObject.Add(new EmbeddedIntPropertyObject()); @@ -408,9 +368,7 @@ public void EmbeddedObject_WhenParentAccessedInDictionary_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithEmbeddedDictionaryObject), typeof(EmbeddedIntPropertyObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedDictionaryObject(); parent.EmbeddedDictionaryObject.Add("child", new EmbeddedIntPropertyObject()); @@ -452,9 +410,7 @@ public void NonEmbeddedObject_WhenParentAccessed_Throws() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var topLevel = new BasicAsymmetricObject { @@ -627,7 +583,6 @@ public void DynamicAccess([Values(true, false)] bool isDynamic) { var flxConfig = await GetFLXIntegrationConfigAsync(); flxConfig.IsDynamic = isDynamic; - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -698,7 +653,6 @@ private static Task GetRemoteObjects(User user, string remoteFieldName, private async Task GetRealmWithRealmValueSchemaAsync() { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(RealmValueObject), typeof(BasicAsymmetricObject) }; flxConfig.PopulateInitialSubscriptions = (realm) => { var query = realm.All(); diff --git a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs b/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs index efa2fb8603..999927d4d2 100644 --- a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs +++ b/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs @@ -2211,6 +2211,8 @@ public void Results_Subscribe_FirstTimeOnly_DoesntWaitForChanges([Values("abc", writerRealm.Add(new SyncAllTypesObject { DoubleProperty = 3.5, GuidProperty = testGuid, }); }); + await WaitForUploadAsync(writerRealm); + // Resubscribe to the same query with waitForSync.FirstTime. Since // we already have this subscription, SubscribeAsync should return // immediately without waiting for the download to happen. diff --git a/Tests/Realm.Tests/Sync/SessionTests.cs b/Tests/Realm.Tests/Sync/SessionTests.cs index 48fbeb0a77..a415b73427 100644 --- a/Tests/Realm.Tests/Sync/SessionTests.cs +++ b/Tests/Realm.Tests/Sync/SessionTests.cs @@ -136,7 +136,7 @@ public void Session_ClientReset_ManualRecovery_InitiateClientReset(string appTyp Assert.That(clientEx.Message, Does.Contain("Bad client file identifier")); Assert.That(clientEx.InnerException, Is.Null); - await TryInitiateClientReset(realm, clientEx, (int)ErrorCode.BadClientFileIdentifier); + await TryInitiateClientReset(realm, clientEx, ErrorCode.ClientReset); }); } @@ -169,7 +169,7 @@ void beforeCb(Realm _) var clientEx = await errorTcs.Task.Timeout(20_000, "Expected client reset"); - await TryInitiateClientReset(realm, clientEx, (int)ClientError.AutoClientResetFailed); + await TryInitiateClientReset(realm, clientEx, ErrorCode.AutoClientResetFailed); }); } @@ -216,6 +216,8 @@ public void Session_AutomaticRecoveryFallsbackToDiscardLocal(string appType) { SyncTestHelpers.RunBaasTestAsync(async () => { + await DisableClientResetRecoveryOnServer(appType); + var automaticResetCalled = false; var discardLocalResetCalled = false; @@ -254,7 +256,6 @@ public void Session_AutomaticRecoveryFallsbackToDiscardLocal(string appType) realm.Add(new ObjectWithPartitionValue(guid)); }); - await DisableClientResetRecoveryOnServer(appType); await TriggerClientReset(realm); await tcsAfterClientReset.Task.Timeout(20_000, detail: "Expected client reset"); @@ -410,8 +411,6 @@ public void SessionIntegrationTest_ClientResetHandlers_OutOfBoundArrayInsert_Add config = GetIntegrationConfig(user); } - config.Schema = new[] { typeof(ObjectWithPartitionValue) }; - return (config, guid); } @@ -731,7 +730,7 @@ public void Session_OnSessionError() { Assert.That(sender, Is.InstanceOf()); Assert.That(e, Is.InstanceOf()); - Assert.That(e.ErrorCode, Is.EqualTo(ErrorCode.TooManySessions)); + Assert.That(e.ErrorCode, Is.EqualTo(ErrorCode.WriteNotAllowed)); Assert.That(e.Message, Is.EqualTo(errorMsg)); Assert.That(e.InnerException, Is.Null); Assert.That(sessionErrorTriggered, Is.False); @@ -741,7 +740,9 @@ public void Session_OnSessionError() using var realm = await GetRealmAsync(config, waitForSync: true); var session = GetSession(realm); - session.SimulateError(ErrorCode.TooManySessions, errorMsg); + + var protocolError = 230; // ProtocolError::write_not_allowed + session.SimulateError((ErrorCode)protocolError, errorMsg); await tcs.Task; @@ -1189,7 +1190,7 @@ public void Session_WhenDisposed_MethodsThrow() Assert.Throws(() => _ = session.Equals(session)); Assert.Throws(() => _ = session.WaitForDownloadAsync()); Assert.Throws(() => _ = session.WaitForUploadAsync()); - Assert.Throws(() => session.ReportErrorForTesting(1, SessionErrorCategory.SessionError, "test", false, ServerRequestsAction.ApplicationBug)); + Assert.Throws(() => session.ReportErrorForTesting(1, "test", false, ServerRequestsAction.ApplicationBug)); // Calling CloseHandle multiple times should be fine session.CloseHandle(); @@ -1458,14 +1459,14 @@ private static ClientResetHandlerBase GetClientResetHandler( return handler; } - private static async Task TryInitiateClientReset(Realm realm, ClientResetException ex, int expectedError) + private static async Task TryInitiateClientReset(Realm realm, ClientResetException ex, ErrorCode expectedError) { if (!realm.IsClosed) { realm.Dispose(); } - Assert.That((int)ex.ErrorCode, Is.EqualTo(expectedError)); + Assert.That(ex.ErrorCode, Is.EqualTo(expectedError)); Assert.That(File.Exists(realm.Config.DatabasePath), Is.True); var didReset = false; diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs index 39e5b94b2c..9af7590fbd 100644 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ b/Tests/Realm.Tests/Sync/SyncTestBase.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using Baas; using MongoDB.Bson; +using Realms.Schema; using Realms.Sync; using Realms.Sync.Exceptions; using static Realms.Tests.TestHelpers; @@ -220,7 +221,36 @@ protected async Task GetRealmAsync(SyncConfigurationBase config, bool wai private static T UpdateConfig(T config) where T : SyncConfigurationBase { - config.Schema = new[] { typeof(HugeSyncObject), typeof(PrimaryKeyStringObject), typeof(ObjectIdPrimaryKeyWithValueObject), typeof(SyncCollectionsObject), typeof(IntPropertyObject), typeof(EmbeddedIntPropertyObject), typeof(SyncAllTypesObject) }; + var schema = new RealmSchema.Builder() + { + typeof(HugeSyncObject), + typeof(PrimaryKeyStringObject), + typeof(ObjectIdPrimaryKeyWithValueObject), + typeof(SyncCollectionsObject), + typeof(IntPropertyObject), + typeof(EmbeddedIntPropertyObject), + typeof(SyncAllTypesObject), + typeof(ObjectWithPartitionValue), + }; + + if (config is FlexibleSyncConfiguration) + { + // We need to add all objects ever used by sync to the flx schema due to the way breaking schema changes work + // in dev mode. When a client connects with a subset of the server schema, they'll experience client reset as the + // server removes the missing tables and re-bootstraps. + schema.Add(typeof(BasicAsymmetricObject)); + schema.Add(typeof(AsymmetricObjectWithAllTypes)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedRecursiveObject)); + schema.Add(typeof(EmbeddedLevel1)); + schema.Add(typeof(EmbeddedLevel2)); + schema.Add(typeof(EmbeddedLevel3)); + schema.Add(typeof(RealmValueObject)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedDictionaryObject)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedListObject)); + schema.Add(typeof(PrimaryKeyInt32Object)); + } + + config.Schema = schema; config.SessionStopPolicy = SessionStopPolicy.Immediately; return config; diff --git a/wrappers/realm-core b/wrappers/realm-core index 03ba58ace5..673be45ce2 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 03ba58ace5d29685154a9287d1f914aabd9b4928 +Subproject commit 673be45ce266cef009ac5af58e8ff7327dd0d234 diff --git a/wrappers/src/error_handling.cpp b/wrappers/src/error_handling.cpp index 2438ecb37b..7ac267441d 100644 --- a/wrappers/src/error_handling.cpp +++ b/wrappers/src/error_handling.cpp @@ -62,7 +62,7 @@ namespace realm { catch (const SyncError& e) { REALM_ASSERT_DEBUG(false); - return NativeException(e); + return NativeException(e.status.code(), e.status.reason()); } catch (const Exception& e) { return NativeException(e); diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index ee687a90ed..53dc338c9e 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -178,7 +178,7 @@ Realm::Config get_shared_realm_config(Configuration configuration, std::optional config.schema_mode = sync_configuration->schema_mode; if (sync_configuration->is_flexible_sync) { - config.sync_config = std::make_shared(*sync_configuration->user, realm::SyncConfig::FLXSyncEnabled{}); + config.sync_config = std::make_shared(*sync_configuration->user, realm::SyncConfig::FLXSyncEnabled{}); } else { std::string partition(Utf16StringAccessor(sync_configuration->partition, sync_configuration->partition_len)); @@ -202,7 +202,7 @@ Realm::Config get_shared_realm_config(Configuration configuration, std::optional } realm_sync_error marshaled_error{ - error.get_system_error().value(), + error.status.code(), to_capi(error.simple_message), to_capi(error.logURL), error.is_client_reset_requested(), diff --git a/wrappers/src/sync_session_cs.cpp b/wrappers/src/sync_session_cs.cpp index 96ecd8339b..c4f915a0c5 100644 --- a/wrappers/src/sync_session_cs.cpp +++ b/wrappers/src/sync_session_cs.cpp @@ -175,24 +175,13 @@ enum class SessionErrorCategory : uint8_t { SessionError = 1 }; -REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, SessionErrorCategory error_category, const uint16_t* message_buf, size_t message_len, bool is_fatal, int server_requests_action) +REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, const uint16_t* message_buf, size_t message_len, bool is_fatal, int server_requests_action) { Utf16StringAccessor message(message_buf, message_len); std::error_code error_code; - switch (error_category) { - case SessionErrorCategory::ClientError: - error_code = std::error_code(err, realm::sync::client_error_category()); - break; - case SessionErrorCategory::SessionError: - error_code = std::error_code(err, realm::sync::protocol_error_category()); - break; - default: - // in case a new category isn't handle, just don't trigger any error - return; - } - - sync::SessionErrorInfo error{ error_code, std::move(message), is_fatal }; + sync::ProtocolErrorInfo protocol_error(err, message, is_fatal); + sync::SessionErrorInfo error(protocol_error); error.server_requests_action = static_cast(server_requests_action); SyncSession::OnlyForTesting::handle_error(*session, std::move(error)); diff --git a/wrappers/src/websocket_cs.cpp b/wrappers/src/websocket_cs.cpp index 419c6e34a4..635c8b399c 100644 --- a/wrappers/src/websocket_cs.cpp +++ b/wrappers/src/websocket_cs.cpp @@ -187,7 +187,7 @@ extern "C" { } REALM_EXPORT void realm_websocket_observer_closed_handler(WebSocketObserver* observer, bool was_clean, websocket::WebSocketError error_code, realm_string_t reason) { - observer->websocket_closed_handler(was_clean, Status(error_code, from_capi(reason))); + observer->websocket_closed_handler(was_clean, error_code, capi_to_std(reason)); } }