From 97ded13d03fd8d69a89d5842130abb2d672def3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 13 Mar 2024 12:54:53 +0100 Subject: [PATCH 01/20] Add sync support for collections in mixed --- CHANGELOG.md | 2 +- .../Sync/DataTypeSynchronizationTests.cs | 106 +++++++++++++++++- Tests/Realm.Tests/Sync/SyncTestBase.cs | 1 + .../Sync/SynchronizedInstanceTests.cs | 18 +++ Tools/DeployApps/BaasClient.cs | 46 ++++++++ wrappers/realm-core | 2 +- 6 files changed, 169 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09565451e3..c3f36ce6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,7 +96,7 @@ * Realm Studio: 15.0.0 or later. ### Internal -* Using Core 14.1.0. +* Using Core v14.2.0-11-g687bb983e. ## 11.7.0 (2024-02-05) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index d5b3056e0b..041cd8d298 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -22,10 +22,16 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Baas; using MongoDB.Bson; +using Nito.AsyncEx; using NUnit.Framework; +using Realms.Exceptions; using Realms.Extensions; using Realms.Helpers; +using Realms.Logging; +using Realms.Sync; +using Realms.Sync.Exceptions; namespace Realms.Tests.Sync { @@ -273,6 +279,40 @@ public class DataTypeSynchronizationTests : SyncTestBase new object[] { (RealmValue)true, RealmValue.Object(new IntPropertyObject { Int = 10 }) }, new object[] { RealmValue.Null, (RealmValue)5m }, new object[] { (RealmValue)12.5f, (RealmValue)15d }, + new object[] { (RealmValue)12.5f, (RealmValue)15d }, + new object[] { (RealmValue)new List { + RealmValue.Null, + 1, + true, + "string", + new byte[] { 0, 1, 2 }, + new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), + 1f, + 2d, + 3m, + new ObjectId("5f63e882536de46d71877979"), + Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, (RealmValue)15d }, + new object[] { (RealmValue)new Dictionary { + { "key1", RealmValue.Null}, + { "key2", 1}, + { "key3", true}, + { "key4", "string"}, + { "key5", new byte[] {0, 1, 2, 3}}, + { "key6", + new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero)}, + { "key7", 1f}, + { "key8", 2d}, + { "key9", 3m}, + { "key10", new ObjectId("5f63e882536de46d71877979")}, + { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31")}, + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, (RealmValue)15d }, }; [TestCaseSource(nameof(RealmTestValues))] @@ -476,13 +516,49 @@ private void TestDictionaryCore(Func(Func getter, Action setter, T item1, T item2, Func? equalsOverride = null) { + Logger.LogLevel = LogLevel.Debug; + equalsOverride ??= (a, b) => a?.Equals(b) == true; SyncTestHelpers.RunBaasTestAsync(async () => { - var partition = Guid.NewGuid().ToString(); - var realm1 = await GetIntegrationRealmAsync(partition); - var realm2 = await GetIntegrationRealmAsync(partition); + var errorTcs = new TaskCompletionSource(); + + // HACK To flush invalid state data from the server to be able to bootstrap new realms + var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); + var user = await GetUserAsync(app); + // var client = user.GetMongoClient("BackingDB"); + // var collection = client.GetCollection(); + // await collection.DeleteManyAsync(); + // await Task.Delay(5000); + + var config = GetFLXIntegrationConfig(user); + // Alternative configuration that causes tests to succeed (green) even thought opening the realm from + // this config with a invalid state on the server will timeout. Haven't identified why an error here + // wont propagate as the using GetFLXIntegrationConfig + // var config = await GetFLXIntegrationConfigAsync(); + // config.PopulateInitialSubscriptions = (r) => + // { + // var query = r.All(); + // r.Subscriptions.Add(query); + // }; + + config.OnSessionError = (_, error) => + { + Console.WriteLine("Session error: " + error); + errorTcs.TrySetResult(error); + }; + var realm1 = await GetRealmAsync(config); + + realm1.Subscriptions.Update(() => + { + realm1.Subscriptions.Add(realm1.All()); + }); + var realm2 = await GetFLXIntegrationRealmAsync(); + realm2.Subscriptions.Update(() => + { + realm2.Subscriptions.Add(realm2.All()); + }); var obj1 = realm1.Write(() => { @@ -501,8 +577,8 @@ private void TestPropertyCore(Func getter, Action { @@ -516,9 +592,31 @@ private void TestPropertyCore(Func getter, Action(T config) typeof(SyncAllTypesObject), typeof(ObjectWithPartitionValue), typeof(RemappedTypeObject), + typeof(RealmValueObject), }; if (config is FlexibleSyncConfiguration) diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs index 2b55ea6b3a..d7bfc8929c 100644 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs @@ -819,6 +819,24 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() }); } + [Test] + public void Add_ThrowsWhenAddingNestedCollectionsToSyncedRealm() + { + SyncTestHelpers.RunBaasTestAsync(async () => + { + var config = await GetIntegrationConfigAsync(); + + using (var realm = GetRealm(config)) + { + RealmException? ex = Assert.Throws(() => + { + realm.Write(() => realm.Add(new RealmValueObject { RealmValueProperty = new List { 1, 2, 3 } })); + }); + Assert.That(ex.Message, Does.Contain("Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled")); + } + }); + } + private const int DummyDataSize = 100; private static void AddDummyData(Realm realm, bool singleTransaction) diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index ab1664ec06..61182c355a 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -148,6 +148,7 @@ public class FunctionReturn };"; private readonly HttpClient _client = new(); + private readonly HttpClient _privateclient = new(); private readonly string? _clusterName; @@ -196,6 +197,9 @@ private BaasClient(Uri baseUri, string differentiator, TextWriter output, string _clusterName = clusterName; Differentiator = differentiator; _output = output; + + _privateclient.BaseAddress = new Uri(baseUri, "api/private/v1.0/"); + _privateclient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json"); } public static async Task Docker(Uri baseUri, string differentiator, TextWriter output) @@ -293,6 +297,7 @@ private async Task Authenticate(string provider, object credentials) _refreshToken = authDoc!["refresh_token"].AsString; _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); + _privateclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); } public static async Task GetOrDeployContainer(string baasaasApiKey, string differentiator, TextWriter output) @@ -555,6 +560,14 @@ public async Task SetAutomaticRecoveryEnabled(BaasApp app, bool enabled) { development_mode_enabled = true, }); + // Retrieve feature flags + // var features = await GetPrivateAsync($"groups/{_groupId}/apps/{app}/features"); + // ENABLE LEGACY MIXED SUPPORT + // await PostPrivateAsync($"features/legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "enable"} ); + // await PostPrivateAsync($"features/bypass_legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "disable"} ); + // DISABLE LEGACY MIXED SUPPORT - Default on latest test server - https://github.com/10gen/baas/blob/master/etc/configs/test_config.json#L303 + // await PostPrivateAsync($"features/legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "disable"} ); + // await PostPrivateAsync($"features/bypass_legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "enable"} ); return (app, mongoServiceId); } @@ -699,10 +712,13 @@ private async Task RefreshAccessTokenAsync() } private Task PostAsync(string relativePath, object obj) => SendAsync(HttpMethod.Post, relativePath, obj); + private Task PostPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Post, relativePath, obj); private Task GetAsync(string relativePath) => SendAsync(HttpMethod.Get, relativePath); + private Task GetPrivateAsync(string relativePath) => SendPrivateAsync(HttpMethod.Get, relativePath); private Task PutAsync(string relativePath, object obj) => SendAsync(HttpMethod.Put, relativePath, obj); + private Task PutPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Put, relativePath, obj); private Task PatchAsync(string relativePath, object obj) => SendAsync(new HttpMethod("PATCH"), relativePath, obj); @@ -736,6 +752,36 @@ private async Task RefreshAccessTokenAsync() return default; } + private async Task SendPrivateAsync(HttpMethod method, string relativePath, object? payload = null) + { + using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); + if (payload != null) + { + message.Content = GetJsonContent(payload); + } + + var response = await _privateclient.SendAsync(message); + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.Unauthorized && _refreshToken != null) + { + await RefreshAccessTokenAsync(); + return await SendAsync(method, relativePath, payload); + } + + var content = await response.Content.ReadAsStringAsync(); + throw new Exception($"An error ({response.StatusCode}) occurred while executing {method} {relativePath}: {content}"); + } + + var json = await response.Content.ReadAsStringAsync(); + + if (!string.IsNullOrWhiteSpace(json)) + { + return BsonSerializer.Deserialize(json); + } + + return default; + } public static HttpContent GetJsonContent(object obj) { diff --git a/wrappers/realm-core b/wrappers/realm-core index e1042b8504..687bb983eb 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit e1042b8504e277820f14cb884bd6ee5578ef8cb6 +Subproject commit 687bb983eb4de3f4167aabbcac548884cc7e54b4 From 26886f3112279c46963dbb45e8f4e7e8456157f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 14 Mar 2024 15:16:07 +0100 Subject: [PATCH 02/20] Fix for reassigning mixed property with a list --- .../Database/RealmValueWithCollections.cs | 27 ++ .../Sync/DataTypeSynchronizationTests.cs | 296 +++++++++++++++++- wrappers/src/object_cs.cpp | 2 + 3 files changed, 322 insertions(+), 3 deletions(-) diff --git a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs index 21f438e724..ba21cd2aa5 100644 --- a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs +++ b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs @@ -253,6 +253,33 @@ public void List_AfterCreation_CanBeAssigned([Values(true, false)] bool isManage Assert.That(rvo.RealmValueProperty == newStringVal); } + [Test] + public void List_AfterCreation_CanBeReassigned([Values(true, false)] bool isManaged) + { + var initialList = (RealmValue)new List{ 1, 2, 3}; + var rvo = new RealmValueObject { RealmValueProperty = initialList }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actual = rvo.RealmValueProperty.AsList(); + Assert.AreEqual(initialList.AsList().Count, actual.Count); + + var updatedList = (RealmValue)new List{4, 5, 6}; + _realm.Write(() => + { + rvo.RealmValueProperty = updatedList; + }); + + actual = rvo.RealmValueProperty.AsList(); + Assert.AreEqual(updatedList.AsList().Count, actual.Count); + } + [Test] public void List_WhenManaged_CanBeModified() { diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 041cd8d298..a2ff3c0347 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -598,8 +598,8 @@ private void TestPropertyCore(Func getter, Action(Func getter, Action + { + var realm = await GetFLXIntegrationRealmAsync(); + realm.Subscriptions.Update(() => + { + realm.Subscriptions.Add(realm.All()); + }); + + var child = new SyncAllTypesObject(); + child.StringProperty = "CHILD"; + var valuesValue = (RealmValue)new List() + { + 1, + (RealmValue)"Realm", + (RealmValue)child, + (RealmValue)new List() + { + 1, "Realm", child + }, + (RealmValue)new Dictionary() + { + { "key1" , 1 }, + { "key2" , "Realm" }, + { "key3" , child }, + } + }; + + var parentId = realm.Write(() => + { + var parent = realm.Add(new SyncAllTypesObject()); + parent.StringProperty = "PARENT"; + parent.RealmValueProperty = valuesValue; + return parent.Id; + }); + // How to add timeout? + realm.SyncSession.WaitForUploadAsync(); + + realm.Dispose(); + + var realm1 = await GetFLXIntegrationRealmAsync(); + realm1.Subscriptions.Update(() => + { + realm1.Subscriptions.Add(realm1.All()); + }); + realm1.SyncSession.WaitForDownloadAsync(); + + var syncedParent = realm1.All().Where(i => i.Id == parentId).Single(); + Assert.True(valuesValue.Equals(syncedParent.RealmValueProperty), "Values {0} {1}", valuesValue, syncedParent.RealmValueProperty); + }); + } + + public static readonly IList RealmValueCollectionTestValues = new List() + { + (RealmValue)new List + { + RealmValue.Null, + 1, + true, + "string", + new byte[] { 0, 1, 2 }, + new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), + 1f, + 2d, + 3m, + new ObjectId("5f63e882536de46d71877979"), + Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, + (RealmValue)"abc", + (RealmValue)new ObjectId("5f63e882536de46d71877979"), + (RealmValue)new byte[] { 0, 1, 2 }, + (RealmValue)DateTimeOffset.FromUnixTimeSeconds(1616137641), + (RealmValue)true, + RealmValue.Null, + (RealmValue)5m, + (RealmValue)12.5f, + (RealmValue)15d, + + (RealmValue)new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + { "key5", new byte[] { 0, 1, 2, 3 } }, + { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, + { "key7", 1f }, + { "key8", 2d }, + { "key9", 3m }, + { "key10", new ObjectId("5f63e882536de46d71877979") }, + { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, + }; + + + [Test] + public void ListManipulations() + { + // Logger.LogLevel = LogLevel.All; + // Logger.Default = Logger.File("sync.log"); + // equalsOverride ??= (a, b) => a?.Equals(b) == true; + + SyncTestHelpers.RunBaasTestAsync(async () => + { + var realm1 = await GetFLXIntegrationRealmAsync(); + realm1.Subscriptions.Update(() => + { + realm1.Subscriptions.Add(realm1.All()); + }); + var realm2 = await GetFLXIntegrationRealmAsync(); + realm2.Subscriptions.Update(() => + { + realm2.Subscriptions.Add(realm2.All()); + }); + + var obj1 = realm1.Write(() => + { + Logger.Default.Log(LogLevel.Debug, $"Writing: initial object"); + var o = realm1.Add(new SyncAllTypesObject()); + o.RealmValueProperty = new List(); + return o; + }); + Logger.Default.Log(LogLevel.Debug, $"Wrote: initial object"); + + await WaitForObjectAsync(obj1, realm2); + + foreach (var realmTestValue in RealmValueCollectionTestValues) + { + realm1.Write(() => + { + Console.WriteLine($"Writing: {realmTestValue}"); + Logger.Default.Log(LogLevel.Debug, $"Writing: {realmTestValue}"); + obj1.RealmValueProperty.AsList().Add(realmTestValue); + }); + Logger.Default.Log(LogLevel.Debug, $"Wrote: {realmTestValue}"); + await realm1.SyncSession.WaitForUploadAsync(); + + Logger.Default.Log(LogLevel.Debug, $"Awaiting write: {realmTestValue}"); + await realm2.SyncSession.WaitForDownloadAsync(); + var obj2 = await WaitForObjectAsync(obj1, realm2); + var expectedValues = obj1.RealmValueProperty.AsList(); + var actualValues = obj2.RealmValueProperty.AsList(); + Assert.AreEqual(expectedValues.Count, actualValues.Count, "Value: {0}", expectedValues); + // FIXME Equals does not test elementwise + // foreach (var valueTuple in expectedValues.Zip(actualValues, (x, y) => (x, y))) + // { + // Assert.AreEqual(valueTuple.x, valueTuple.y); + // } + } + + foreach (var realmTestValue in RealmValueCollectionTestValues) + { + realm1.Write(() => + { + Logger.Default.Log(LogLevel.Debug, $"Removing: {realmTestValue}"); + obj1.RealmValueProperty.AsList().RemoveAt(0); + }); + Logger.Default.Log(LogLevel.Debug, $"Removed: {realmTestValue}"); + + await realm1.SyncSession.WaitForUploadAsync(); + Logger.Default.Log(LogLevel.Debug, $"Awaiting remove: {realmTestValue}"); + await realm2.SyncSession.WaitForDownloadAsync(); + + var obj2 = await WaitForObjectAsync(obj1, realm2); + var expectedValues = obj1.RealmValueProperty.AsList(); + var actualValues = obj2.RealmValueProperty.AsList(); + Assert.AreEqual(expectedValues.Count, actualValues.Count); + // Objects, List and Dictionaries will never 'Contain' the source element, so maybe refine this a bit? + Assert.False(obj2.RealmValueProperty.AsList().Contains(realmTestValue)); + } + + foreach (var realmTestValue in RealmValueCollectionTestValues) + { + realm1.Write(() => + { + Logger.Default.Log(LogLevel.Debug, $"Setting: {realmTestValue}"); + + if (obj1.RealmValueProperty.AsList().Count == 0) + { + obj1.RealmValueProperty.AsList().Insert(0, realmTestValue); + + } + else + { + obj1.RealmValueProperty.AsList()[0] = realmTestValue; + } + }); + Logger.Default.Log(LogLevel.Debug, $"Set: {realmTestValue}"); + + await realm1.SyncSession.WaitForUploadAsync(); + Logger.Default.Log(LogLevel.Debug, $"Awaiting set: {realmTestValue}"); + await realm2.SyncSession.WaitForDownloadAsync(); + + var obj2 = await WaitForObjectAsync(obj1, realm2); + var expectedValues = obj1.RealmValueProperty.AsList(); + var actualValues = obj2.RealmValueProperty.AsList(); + Assert.AreEqual(expectedValues.Count, actualValues.Count); + // FIXME Equals does not test elementwise + // Assert.AreEqual(expectedValues[0], actualValues[0]); + } + }); + } + + [Test] + public void DictionaryManipulations() + { + Logger.LogLevel = LogLevel.All; + Logger.Default = Logger.File("sync.log"); + + // equalsOverride ??= (a, b) => a?.Equals(b) == true; + + SyncTestHelpers.RunBaasTestAsync(async () => + { + var realm1 = await GetFLXIntegrationRealmAsync(); + realm1.Subscriptions.Update(() => + { + realm1.Subscriptions.Add(realm1.All()); + }); + var realm2 = await GetFLXIntegrationRealmAsync(); + realm2.Subscriptions.Update(() => + { + realm2.Subscriptions.Add(realm2.All()); + }); + + var obj1 = realm1.Write(() => + { + Logger.Default.Log(LogLevel.Debug, $"Writing: initial object"); + var o = realm1.Add(new SyncAllTypesObject()); + o.RealmValueProperty = new Dictionary(); + return o; + }); + Logger.Default.Log(LogLevel.Debug, $"Wrote: initial object"); + + await WaitForObjectAsync(obj1, realm2); + foreach (var (index, realmTestValue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) + { + realm1.Write(() => + { + Logger.Default.Log(LogLevel.Debug, $"Writing: [{index}] = {realmTestValue}"); + obj1.RealmValueProperty.AsDictionary()[$"{index}"] = realmTestValue; + }); + Logger.Default.Log(LogLevel.Debug, $"Wrote: [{index}] = {realmTestValue}"); + + await realm1.SyncSession.WaitForUploadAsync(); + Logger.Default.Log(LogLevel.Debug, $"Awaiting: [{index}] = {realmTestValue}"); + await realm2.SyncSession.WaitForDownloadAsync(); + var obj2 = await WaitForObjectAsync(obj1, realm2); + var expectedValues = obj1.RealmValueProperty.AsDictionary(); + var actualValues = obj2.RealmValueProperty.AsDictionary(); + Assert.AreEqual(expectedValues.Count, actualValues.Count, "Value: {0}", expectedValues); + // FIXME Equals does not test elementwise + // foreach (var valueTuple in expectedValues.Zip(actualValues, (x, y) => (x, y))) + // { + // Assert.AreEqual(valueTuple.x, valueTuple.y); + // } + } + + foreach (var (index, realmTestvalue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) + { + realm1.Write(() => + { + obj1.RealmValueProperty.AsDictionary().Remove($"{index}"); + }); + + await realm1.SyncSession.WaitForUploadAsync(); + Logger.Default.Log(LogLevel.Debug, $"Awaiting: remove[{index}]"); + // await Task.Delay(1000); + await realm2.SyncSession.WaitForDownloadAsync(); + + var obj2 = await WaitForObjectAsync(obj1, realm2); + var expectedValues = obj1.RealmValueProperty.AsDictionary(); + var actualValues = obj2.RealmValueProperty.AsDictionary(); + Assert.AreEqual(expectedValues.Count, actualValues.Count); + // FIXME Equals does not test elementwise + Assert.False(obj2.RealmValueProperty.AsDictionary().ContainsKey($"{index}")); + } + + }); + } + private static RealmValue Clone(RealmValue original) { diff --git a/wrappers/src/object_cs.cpp b/wrappers/src/object_cs.cpp index 4ab900189f..2581da4f14 100644 --- a/wrappers/src/object_cs.cpp +++ b/wrappers/src/object_cs.cpp @@ -184,9 +184,11 @@ extern "C" { switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: + object.get_obj().set_null(prop.column_key); object.get_obj().set_collection(prop.column_key, CollectionType::List); return new List(object.realm(), object.get_obj(), prop.column_key); case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: + object.get_obj().set_null(prop.column_key); object.get_obj().set_collection(prop.column_key, CollectionType::Dictionary); return new object_store::Dictionary(object.realm(), object.get_obj(), prop.column_key); default: From 1ac8dfd96e6eaa4dc1d101425e9270b31cb1c4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 15 Mar 2024 12:49:23 +0100 Subject: [PATCH 03/20] More testing --- .../Database/RealmValueWithCollections.cs | 139 ++++++- .../Sync/DataTypeSynchronizationTests.cs | 376 ++++++++---------- .../Sync/SynchronizedInstanceTests.cs | 18 - wrappers/src/dictionary_cs.cpp | 2 + wrappers/src/list_cs.cpp | 2 + 5 files changed, 307 insertions(+), 230 deletions(-) diff --git a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs index ba21cd2aa5..f3c0590c26 100644 --- a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs +++ b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs @@ -280,6 +280,61 @@ public void List_AfterCreation_CanBeReassigned([Values(true, false)] bool isMana Assert.AreEqual(updatedList.AsList().Count, actual.Count); } + [Test] + public void List_AfterCreation_EmbeddedListCanBeReassigned([Values(true, false)] bool isManaged) + { + var embeddedList = new List{ new List{1, 2, 3}}; + var rvo = new RealmValueObject { RealmValueProperty = new List{ embeddedList} }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsList(); + Assert.AreEqual(embeddedList.Count, actualEmbedded.Count); + + var updatedList = (RealmValue)new List{4, 5, 6}; + _realm.Write(() => + { + rvo.RealmValueProperty.AsList()[0] = updatedList; + }); + + actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsList(); + Assert.AreEqual(updatedList.AsList().Count, actualEmbedded.Count); + } + + [Test] + public void List_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, false)] bool isManaged) + { + var embeddedDictionary = new Dictionary{{ "key1", 1}}; + var rvo = new RealmValueObject { RealmValueProperty = new List{ embeddedDictionary} }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsDictionary(); + Assert.AreEqual(embeddedDictionary.Count, actualEmbedded.Count); + + var updatedDictionary = new Dictionary{{ "key2", 2}}; + _realm.Write(() => + { + rvo.RealmValueProperty.AsList()[0] = updatedDictionary; + }); + + actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsDictionary(); + Assert.AreEqual(updatedDictionary.Count, actualEmbedded.Count); + } + + [Test] public void List_WhenManaged_CanBeModified() { @@ -678,6 +733,88 @@ public void Dictionary_AfterCreation_CanBeAssigned([Values(true, false)] bool is Assert.That(rvo.RealmValueProperty == newStringVal); } + [Test] + public void Dictionary_AfterCreation_CanBeReassigned([Values(true, false)] bool isManaged) + { + var initialDictionary = (RealmValue) new Dictionary { {"key1" , 1 } }; + var rvo = new RealmValueObject { RealmValueProperty = initialDictionary }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actual = rvo.RealmValueProperty.AsDictionary(); + Assert.AreEqual(initialDictionary.AsDictionary().Count, actual.Count); + + var updatedDictionary = (RealmValue) new Dictionary { {"key2" , 2 } }; + _realm.Write(() => + { + rvo.RealmValueProperty = updatedDictionary; + }); + + actual = rvo.RealmValueProperty.AsDictionary(); + Assert.AreEqual(updatedDictionary.AsDictionary().Count, actual.Count); + } + + [Test] + public void Dictionary_AfterCreation_EmbeddedListCanBeReassigned([Values(true, false)] bool isManaged) + { + var embeddedList = new List{ new List{1, 2, 3}}; + var rvo = new RealmValueObject { RealmValueProperty = new Dictionary{ { "key", embeddedList}} }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); + Assert.AreEqual(embeddedList.Count, actualEmbedded.Count); + + var updatedList = (RealmValue)new List{4, 5, 6}; + _realm.Write(() => + { + rvo.RealmValueProperty.AsDictionary()["key"] = updatedList; + }); + + actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); + Assert.AreEqual(updatedList.AsList().Count, actualEmbedded.Count); + } + + [Test] + public void Dict_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, false)] bool isManaged) + { + var embeddedDictionary = new Dictionary{{ "key1", 1}}; + var rvo = new RealmValueObject { RealmValueProperty = new Dictionary{ { "key", embeddedDictionary} } }; + + if (isManaged) + { + _realm.Write(() => + { + _realm.Add(rvo); + }); + } + + var actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsDictionary(); + Assert.AreEqual(embeddedDictionary.Count, actualEmbedded.Count); + + var updatedDictionary = new Dictionary{{ "key2", 2}}; + _realm.Write(() => + { + rvo.RealmValueProperty.AsDictionary()["key"] = updatedDictionary; + }); + + actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsDictionary(); + Assert.AreEqual(updatedDictionary.Count, actualEmbedded.Count); + } + + [Test] public void Dictionary_WhenManaged_CanBeModified() { @@ -985,7 +1122,7 @@ public void IndexedRealmValue_WithCollection_BasicTest() #endregion - private class RealmValueComparer : IEqualityComparer + internal class RealmValueComparer : IEqualityComparer { public bool Equals(RealmValue x, RealmValue y) { diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index a2ff3c0347..1be592adf2 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -32,12 +32,15 @@ using Realms.Logging; using Realms.Sync; using Realms.Sync.Exceptions; +using Realms.Tests.Database; namespace Realms.Tests.Sync { [TestFixture, Preserve(AllMembers = true)] public class DataTypeSynchronizationTests : SyncTestBase { + private readonly RealmValueWithCollections.RealmValueComparer _rvComparer = new(); + #region Boolean [Test] @@ -271,7 +274,7 @@ public class DataTypeSynchronizationTests : SyncTestBase #region RealmValue - public static readonly object[] RealmTestValues = new[] + public static readonly object[] RealmTestPrimitiveValues = new[] { new object[] { (RealmValue)"abc", (RealmValue)10 }, new object[] { (RealmValue)new ObjectId("5f63e882536de46d71877979"), (RealmValue)new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}") }, @@ -279,57 +282,75 @@ public class DataTypeSynchronizationTests : SyncTestBase new object[] { (RealmValue)true, RealmValue.Object(new IntPropertyObject { Int = 10 }) }, new object[] { RealmValue.Null, (RealmValue)5m }, new object[] { (RealmValue)12.5f, (RealmValue)15d }, - new object[] { (RealmValue)12.5f, (RealmValue)15d }, - new object[] { (RealmValue)new List { - RealmValue.Null, - 1, - true, - "string", - new byte[] { 0, 1, 2 }, - new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), - 1f, - 2d, - 3m, - new ObjectId("5f63e882536de46d71877979"), - Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, - }, (RealmValue)15d }, - new object[] { (RealmValue)new Dictionary { - { "key1", RealmValue.Null}, - { "key2", 1}, - { "key3", true}, - { "key4", "string"}, - { "key5", new byte[] {0, 1, 2, 3}}, - { "key6", - new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero)}, - { "key7", 1f}, - { "key8", 2d}, - { "key9", 3m}, - { "key10", new ObjectId("5f63e882536de46d71877979")}, - { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31")}, - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, - }, (RealmValue)15d }, }; - [TestCaseSource(nameof(RealmTestValues))] - public void List_RealmValue(RealmValue first, RealmValue second) => TestListCore(o => o.RealmValueList, Clone(first), Clone(second), equalsOverride: RealmValueEquals); + public static readonly object[] RealmTestValuesWithCollections = RealmTestPrimitiveValues.Concat( new[] + { + new object[] { (RealmValue)12.5f, (RealmValue)15d }, new object[] + { + (RealmValue)new List + { + RealmValue.Null, + 1, + true, + "string", + new byte[] { 0, 1, 2 }, + new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero), + 1f, + 2d, + 3m, + new ObjectId("5f63e882536de46d71877979"), + Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, + (RealmValue)15d + }, + new object[] + { + (RealmValue)new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + { "key5", new byte[] { 0, 1, 2, 3 } }, + { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, + { "key7", 1f }, + { "key8", 2d }, + { "key9", 3m }, + { "key10", new ObjectId("5f63e882536de46d71877979") }, + { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, + }, + (RealmValue)15d + }, + }).ToArray(); + + [TestCaseSource(nameof(RealmTestValuesWithCollections))] + public void List_RealmValue(RealmValue first, RealmValue second) => TestListCore(o => o.RealmValueList, + Clone(first), Clone(second), equalsOverride: RealmValueEquals); - [TestCaseSource(nameof(RealmTestValues))] - public void Set_RealmValue(RealmValue first, RealmValue second) => TestSetCore(o => o.RealmValueSet, Clone(first), Clone(second), equalsOverride: RealmValueEquals); + [TestCaseSource(nameof(RealmTestPrimitiveValues))] + public void Set_RealmValue(RealmValue first, RealmValue second) => TestSetCore(o => o.RealmValueSet, + Clone(first), Clone(second), equalsOverride: RealmValueEquals); - [TestCaseSource(nameof(RealmTestValues))] - public void Dict_RealmValue(RealmValue first, RealmValue second) => TestDictionaryCore(o => o.RealmValueDict, Clone(first), Clone(second), equalsOverride: RealmValueEquals); + [TestCaseSource(nameof(RealmTestValuesWithCollections))] + public void Dict_RealmValue(RealmValue first, RealmValue second) => TestDictionaryCore(o => o.RealmValueDict, + Clone(first), Clone(second), equalsOverride: RealmValueEquals); - [TestCaseSource(nameof(RealmTestValues))] - public void Property_RealmValue(RealmValue first, RealmValue second) => TestPropertyCore(o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, Clone(first), Clone(second), equalsOverride: RealmValueEquals); + [TestCaseSource(nameof(RealmTestValuesWithCollections))] + public void Property_RealmValue(RealmValue first, RealmValue second) => TestPropertyCore( + o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, Clone(first), Clone(second), + equalsOverride: RealmValueEquals); #endregion - private void TestListCore(Func> getter, T item1, T item2, Func? equalsOverride = null) + private void TestListCore(Func> getter, T item1, T item2, + Func? equalsOverride = null) { equalsOverride ??= (a, b) => a?.Equals(b) == true; @@ -514,50 +535,24 @@ private void TestDictionaryCore(Func(Func getter, Action setter, T item1, T item2, Func? equalsOverride = null) + private void TestPropertyCore(Func getter, Action setter, + T item1, T item2, Func? equalsOverride = null) { - Logger.LogLevel = LogLevel.Debug; - - equalsOverride ??= (a, b) => a?.Equals(b) == true; - SyncTestHelpers.RunBaasTestAsync(async () => { - var errorTcs = new TaskCompletionSource(); - - // HACK To flush invalid state data from the server to be able to bootstrap new realms - var app = App.Create(SyncTestHelpers.GetAppConfig(AppConfigType.FlexibleSync)); - var user = await GetUserAsync(app); - // var client = user.GetMongoClient("BackingDB"); - // var collection = client.GetCollection(); - // await collection.DeleteManyAsync(); - // await Task.Delay(5000); - - var config = GetFLXIntegrationConfig(user); - // Alternative configuration that causes tests to succeed (green) even thought opening the realm from - // this config with a invalid state on the server will timeout. Haven't identified why an error here - // wont propagate as the using GetFLXIntegrationConfig - // var config = await GetFLXIntegrationConfigAsync(); - // config.PopulateInitialSubscriptions = (r) => - // { - // var query = r.All(); - // r.Subscriptions.Add(query); - // }; - - config.OnSessionError = (_, error) => - { - Console.WriteLine("Session error: " + error); - errorTcs.TrySetResult(error); - }; - var realm1 = await GetRealmAsync(config); + var realm1 = await GetFLXIntegrationRealmAsync(); realm1.Subscriptions.Update(() => { realm1.Subscriptions.Add(realm1.All()); + realm1.Subscriptions.Add(realm1.All()); }); + var realm2 = await GetFLXIntegrationRealmAsync(); realm2.Subscriptions.Update(() => { realm2.Subscriptions.Add(realm2.All()); + realm2.Subscriptions.Add(realm2.All()); }); var obj1 = realm1.Write(() => @@ -577,8 +572,8 @@ private void TestPropertyCore(Func getter, Action { @@ -590,90 +585,91 @@ private void TestPropertyCore(Func getter, Action { - var realm = await GetFLXIntegrationRealmAsync(); - realm.Subscriptions.Update(() => + var realm1 = await GetFLXIntegrationRealmAsync(); + realm1.Subscriptions.Update(() => { - realm.Subscriptions.Add(realm.All()); + realm1.Subscriptions.Add(realm1.All()); + realm1.Subscriptions.Add(realm1.All()); }); - var child = new SyncAllTypesObject(); - child.StringProperty = "CHILD"; - var valuesValue = (RealmValue)new List() + var child = new IntPropertyObject(); + var valuesValue = new List() + { + 1, + (RealmValue)"Realm", + (RealmValue)child, + (RealmValue)new List() { 1, "Realm", child }, + (RealmValue)new Dictionary() { - 1, - (RealmValue)"Realm", - (RealmValue)child, - (RealmValue)new List() - { - 1, "Realm", child - }, - (RealmValue)new Dictionary() - { - { "key1" , 1 }, - { "key2" , "Realm" }, - { "key3" , child }, - } - }; + { "key1", 1 }, { "key2", "Realm" }, { "key3", child }, + } + }; - var parentId = realm.Write(() => + var (parentId, childId) = realm1.Write(() => { - var parent = realm.Add(new SyncAllTypesObject()); + var parent = realm1.Add(new SyncAllTypesObject()); parent.StringProperty = "PARENT"; + parent.ObjectProperty = child; parent.RealmValueProperty = valuesValue; - return parent.Id; + return (parent.Id, child.Id); }); - // How to add timeout? - realm.SyncSession.WaitForUploadAsync(); - realm.Dispose(); + await realm1.SyncSession.WaitForUploadAsync(); + realm1.Dispose(); - var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => + var realm2 = await GetFLXIntegrationRealmAsync(); + realm2.Subscriptions.Update(() => { - realm1.Subscriptions.Add(realm1.All()); + realm2.Subscriptions.Add(realm2.All()); + realm2.Subscriptions.Add(realm2.All()); }); - realm1.SyncSession.WaitForDownloadAsync(); - - var syncedParent = realm1.All().Where(i => i.Id == parentId).Single(); - Assert.True(valuesValue.Equals(syncedParent.RealmValueProperty), "Values {0} {1}", valuesValue, syncedParent.RealmValueProperty); + await realm2.SyncSession.WaitForDownloadAsync(); + + var syncedParent = + await TestHelpers.WaitForConditionAsync(() => realm2.FindCore(parentId), + o => o != null); + var syncedChild = + await TestHelpers.WaitForConditionAsync(() => realm2.FindCore(childId), + o => o != null); + var syncedValues = syncedParent.RealmValueProperty.AsList(); + Assert.AreEqual(valuesValue[0], syncedValues[0]); + Assert.AreEqual(valuesValue[1], syncedValues[1]); + Assert.AreEqual(childId, syncedValues[2].AsRealmObject().Id); + var nestedExpectedList = valuesValue[3].AsList(); + var nestedSyncedList = syncedValues[3].AsList(); + Assert.AreEqual(nestedExpectedList[0], nestedSyncedList[0]); + Assert.AreEqual(nestedExpectedList[1], nestedSyncedList[1]); + Assert.AreEqual(childId, nestedSyncedList[2].AsRealmObject().Id); + + var nestedExpectedDictionary = valuesValue[4].AsDictionary(); + var nestedSyncedDictionary = syncedValues[4].AsDictionary(); + Assert.AreEqual(nestedExpectedDictionary["key1"], nestedSyncedDictionary["key1"]); + Assert.AreEqual(nestedExpectedDictionary["key2"], nestedSyncedDictionary["key2"]); + Assert.AreEqual(childId, nestedSyncedDictionary["key3"].AsRealmObject().Id); }); } public static readonly IList RealmValueCollectionTestValues = new List() { + (RealmValue)"abc", + (RealmValue)new ObjectId("5f63e882536de46d71877979"), + (RealmValue)new byte[] { 0, 1, 2 }, + (RealmValue)DateTimeOffset.FromUnixTimeSeconds(1616137641), + (RealmValue)true, + RealmValue.Null, + (RealmValue)5m, + (RealmValue)12.5f, + (RealmValue)15d, (RealmValue)new List { RealmValue.Null, @@ -691,32 +687,22 @@ public void Bootstrap() // innerList, // innerDict, }, - (RealmValue)"abc", - (RealmValue)new ObjectId("5f63e882536de46d71877979"), - (RealmValue)new byte[] { 0, 1, 2 }, - (RealmValue)DateTimeOffset.FromUnixTimeSeconds(1616137641), - (RealmValue)true, - RealmValue.Null, - (RealmValue)5m, - (RealmValue)12.5f, - (RealmValue)15d, - (RealmValue)new Dictionary { - { "key1", RealmValue.Null }, - { "key2", 1 }, - { "key3", true }, - { "key4", "string" }, - { "key5", new byte[] { 0, 1, 2, 3 } }, - { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, - { "key7", 1f }, - { "key8", 2d }, - { "key9", 3m }, - { "key10", new ObjectId("5f63e882536de46d71877979") }, - { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + { "key5", new byte[] { 0, 1, 2, 3 } }, + { "key6", new DateTimeOffset(1234, 5, 6, 7, 8, 9, TimeSpan.Zero) }, + { "key7", 1f }, + { "key8", 2d }, + { "key9", 3m }, + { "key10", new ObjectId("5f63e882536de46d71877979") }, + { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, + // new InternalObject { IntProperty = 10, StringProperty = "brown" }, + // innerList, + // innerDict, }, }; @@ -724,107 +710,76 @@ public void Bootstrap() [Test] public void ListManipulations() { - // Logger.LogLevel = LogLevel.All; - // Logger.Default = Logger.File("sync.log"); - // equalsOverride ??= (a, b) => a?.Equals(b) == true; - SyncTestHelpers.RunBaasTestAsync(async () => { var realm1 = await GetFLXIntegrationRealmAsync(); - realm1.Subscriptions.Update(() => - { - realm1.Subscriptions.Add(realm1.All()); - }); var realm2 = await GetFLXIntegrationRealmAsync(); - realm2.Subscriptions.Update(() => - { - realm2.Subscriptions.Add(realm2.All()); - }); + + realm1.Subscriptions.Update(() => { realm1.Subscriptions.Add(realm1.All()); }); + realm2.Subscriptions.Update(() => { realm2.Subscriptions.Add(realm2.All()); }); var obj1 = realm1.Write(() => { - Logger.Default.Log(LogLevel.Debug, $"Writing: initial object"); var o = realm1.Add(new SyncAllTypesObject()); o.RealmValueProperty = new List(); return o; }); - Logger.Default.Log(LogLevel.Debug, $"Wrote: initial object"); await WaitForObjectAsync(obj1, realm2); + // Append elements one by one and verify that they are synced foreach (var realmTestValue in RealmValueCollectionTestValues) { realm1.Write(() => { - Console.WriteLine($"Writing: {realmTestValue}"); - Logger.Default.Log(LogLevel.Debug, $"Writing: {realmTestValue}"); obj1.RealmValueProperty.AsList().Add(realmTestValue); }); - Logger.Default.Log(LogLevel.Debug, $"Wrote: {realmTestValue}"); await realm1.SyncSession.WaitForUploadAsync(); - Logger.Default.Log(LogLevel.Debug, $"Awaiting write: {realmTestValue}"); await realm2.SyncSession.WaitForDownloadAsync(); var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); - Assert.AreEqual(expectedValues.Count, actualValues.Count, "Value: {0}", expectedValues); - // FIXME Equals does not test elementwise - // foreach (var valueTuple in expectedValues.Zip(actualValues, (x, y) => (x, y))) - // { - // Assert.AreEqual(valueTuple.x, valueTuple.y); - // } + Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } + // Remove elements one by one and verify that changes are synced foreach (var realmTestValue in RealmValueCollectionTestValues) { realm1.Write(() => { - Logger.Default.Log(LogLevel.Debug, $"Removing: {realmTestValue}"); obj1.RealmValueProperty.AsList().RemoveAt(0); }); - Logger.Default.Log(LogLevel.Debug, $"Removed: {realmTestValue}"); - await realm1.SyncSession.WaitForUploadAsync(); - Logger.Default.Log(LogLevel.Debug, $"Awaiting remove: {realmTestValue}"); - await realm2.SyncSession.WaitForDownloadAsync(); + await realm2.SyncSession.WaitForDownloadAsync(); var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); - Assert.AreEqual(expectedValues.Count, actualValues.Count); - // Objects, List and Dictionaries will never 'Contain' the source element, so maybe refine this a bit? - Assert.False(obj2.RealmValueProperty.AsList().Contains(realmTestValue)); + Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } + // Insert/override elements at index 0 and verify that changes are synced foreach (var realmTestValue in RealmValueCollectionTestValues) { realm1.Write(() => { - Logger.Default.Log(LogLevel.Debug, $"Setting: {realmTestValue}"); - - if (obj1.RealmValueProperty.AsList().Count == 0) - { - obj1.RealmValueProperty.AsList().Insert(0, realmTestValue); - - } - else - { - obj1.RealmValueProperty.AsList()[0] = realmTestValue; - } + if (obj1.RealmValueProperty.AsList().Count == 0) + { + obj1.RealmValueProperty.AsList().Insert(0, realmTestValue); + } + else + { + obj1.RealmValueProperty.AsList()[0] = realmTestValue; + } }); - Logger.Default.Log(LogLevel.Debug, $"Set: {realmTestValue}"); - await realm1.SyncSession.WaitForUploadAsync(); - Logger.Default.Log(LogLevel.Debug, $"Awaiting set: {realmTestValue}"); - await realm2.SyncSession.WaitForDownloadAsync(); + await realm2.SyncSession.WaitForDownloadAsync(); var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); - Assert.AreEqual(expectedValues.Count, actualValues.Count); - // FIXME Equals does not test elementwise - // Assert.AreEqual(expectedValues[0], actualValues[0]); + Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } }); } @@ -902,7 +857,6 @@ public void DictionaryManipulations() // FIXME Equals does not test elementwise Assert.False(obj2.RealmValueProperty.AsDictionary().ContainsKey($"{index}")); } - }); } diff --git a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs index d7bfc8929c..2b55ea6b3a 100644 --- a/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs +++ b/Tests/Realm.Tests/Sync/SynchronizedInstanceTests.cs @@ -819,24 +819,6 @@ public void SyncLogger_WhenLevelChanges_LogsAtNewLevel() }); } - [Test] - public void Add_ThrowsWhenAddingNestedCollectionsToSyncedRealm() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var config = await GetIntegrationConfigAsync(); - - using (var realm = GetRealm(config)) - { - RealmException? ex = Assert.Throws(() => - { - realm.Write(() => realm.Add(new RealmValueObject { RealmValueProperty = new List { 1, 2, 3 } })); - }); - Assert.That(ex.Message, Does.Contain("Writing a copy to a flexible sync realm is not supported unless flexible sync is already enabled")); - } - }); - } - private const int DummyDataSize = 100; private static void AddDummyData(Realm realm, bool singleTransaction) diff --git a/wrappers/src/dictionary_cs.cpp b/wrappers/src/dictionary_cs.cpp index cec6395d17..daea6c6e25 100644 --- a/wrappers/src/dictionary_cs.cpp +++ b/wrappers/src/dictionary_cs.cpp @@ -84,9 +84,11 @@ extern "C" { switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: + dictionary.insert(dict_key, Mixed{}); dictionary.insert_collection(dict_key, CollectionType::List); return new List(dictionary.get_list(dict_key)); case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: + dictionary.insert(dict_key, Mixed{}); dictionary.insert_collection(dict_key, CollectionType::Dictionary); return new object_store::Dictionary(dictionary.get_dictionary(dict_key)); default: diff --git a/wrappers/src/list_cs.cpp b/wrappers/src/list_cs.cpp index c296b7544f..8f3558a197 100644 --- a/wrappers/src/list_cs.cpp +++ b/wrappers/src/list_cs.cpp @@ -94,9 +94,11 @@ REALM_EXPORT void* list_set_collection(List& list, size_t list_ndx, realm_value_ switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: + list.set_any(list_ndx, Mixed{}); list.set_collection(list_ndx, CollectionType::List); return new List(list.get_list(list_ndx)); case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: + list.set_any(list_ndx, Mixed{}); list.set_collection(list_ndx, CollectionType::Dictionary); return new object_store::Dictionary(list.get_dictionary(list_ndx)); default: From 0afc55c5c802159628a563837370cf33690c4a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 15 Mar 2024 13:29:42 +0100 Subject: [PATCH 04/20] Improved testing and clean up --- .../Database/RealmValueWithCollections.cs | 85 ++++++++++--------- Tests/Realm.Tests/Sync/SyncTestBase.cs | 1 - Tools/DeployApps/BaasClient.cs | 6 +- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs index f3c0590c26..738387d1cf 100644 --- a/Tests/Realm.Tests/Database/RealmValueWithCollections.cs +++ b/Tests/Realm.Tests/Database/RealmValueWithCollections.cs @@ -256,7 +256,7 @@ public void List_AfterCreation_CanBeAssigned([Values(true, false)] bool isManage [Test] public void List_AfterCreation_CanBeReassigned([Values(true, false)] bool isManaged) { - var initialList = (RealmValue)new List{ 1, 2, 3}; + var initialList = (RealmValue)new List { 1, 2, 3 }; var rvo = new RealmValueObject { RealmValueProperty = initialList }; if (isManaged) @@ -267,24 +267,24 @@ public void List_AfterCreation_CanBeReassigned([Values(true, false)] bool isMana }); } - var actual = rvo.RealmValueProperty.AsList(); - Assert.AreEqual(initialList.AsList().Count, actual.Count); + var actualList = rvo.RealmValueProperty; + Assert.That(initialList, Is.EqualTo(actualList).Using(_rvComparer)); - var updatedList = (RealmValue)new List{4, 5, 6}; + var updatedList = (RealmValue)new List { 4, 5 }; _realm.Write(() => { rvo.RealmValueProperty = updatedList; }); - actual = rvo.RealmValueProperty.AsList(); - Assert.AreEqual(updatedList.AsList().Count, actual.Count); + actualList = rvo.RealmValueProperty; + Assert.That(updatedList, Is.EqualTo(actualList).Using(_rvComparer)); } [Test] public void List_AfterCreation_EmbeddedListCanBeReassigned([Values(true, false)] bool isManaged) { - var embeddedList = new List{ new List{1, 2, 3}}; - var rvo = new RealmValueObject { RealmValueProperty = new List{ embeddedList} }; + var initialList = (RealmValue)new List { new List { 1, 2, 3 } }; + var rvo = new RealmValueObject { RealmValueProperty = new List { initialList } }; if (isManaged) { @@ -294,24 +294,24 @@ public void List_AfterCreation_EmbeddedListCanBeReassigned([Values(true, false)] }); } - var actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsList(); - Assert.AreEqual(embeddedList.Count, actualEmbedded.Count); + var actualEmbeddedList = rvo.RealmValueProperty.AsList()[0]; + Assert.That(initialList, Is.EqualTo(actualEmbeddedList).Using(_rvComparer)); - var updatedList = (RealmValue)new List{4, 5, 6}; + var updatedList = (RealmValue)new List { 4, 5, 6 }; _realm.Write(() => { rvo.RealmValueProperty.AsList()[0] = updatedList; }); - actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsList(); - Assert.AreEqual(updatedList.AsList().Count, actualEmbedded.Count); + actualEmbeddedList = rvo.RealmValueProperty.AsList()[0]; + Assert.That(updatedList, Is.EqualTo(actualEmbeddedList).Using(_rvComparer)); } [Test] public void List_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, false)] bool isManaged) { - var embeddedDictionary = new Dictionary{{ "key1", 1}}; - var rvo = new RealmValueObject { RealmValueProperty = new List{ embeddedDictionary} }; + var initialDictionary = (RealmValue)new Dictionary { { "key1", 1 } }; + var rvo = new RealmValueObject { RealmValueProperty = new List { initialDictionary } }; if (isManaged) { @@ -321,20 +321,19 @@ public void List_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, f }); } - var actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsDictionary(); - Assert.AreEqual(embeddedDictionary.Count, actualEmbedded.Count); + var actualDictionary = rvo.RealmValueProperty.AsList()[0]; + Assert.That(initialDictionary, Is.EqualTo(actualDictionary).Using(_rvComparer)); - var updatedDictionary = new Dictionary{{ "key2", 2}}; + var updatedDictionary = (RealmValue)new Dictionary { { "key2", 2 } }; _realm.Write(() => { rvo.RealmValueProperty.AsList()[0] = updatedDictionary; }); - actualEmbedded = rvo.RealmValueProperty.AsList()[0].AsDictionary(); - Assert.AreEqual(updatedDictionary.Count, actualEmbedded.Count); + actualDictionary = rvo.RealmValueProperty.AsList()[0]; + Assert.That(updatedDictionary, Is.EqualTo(actualDictionary).Using(_rvComparer)); } - [Test] public void List_WhenManaged_CanBeModified() { @@ -533,7 +532,6 @@ public void List_WhenManaged_WorksWithNotifications() callbacks.Clear(); } - #endregion #region Dictionary @@ -736,7 +734,7 @@ public void Dictionary_AfterCreation_CanBeAssigned([Values(true, false)] bool is [Test] public void Dictionary_AfterCreation_CanBeReassigned([Values(true, false)] bool isManaged) { - var initialDictionary = (RealmValue) new Dictionary { {"key1" , 1 } }; + var initialDictionary = (RealmValue)new Dictionary { { "key1", 1 } }; var rvo = new RealmValueObject { RealmValueProperty = initialDictionary }; if (isManaged) @@ -747,24 +745,27 @@ public void Dictionary_AfterCreation_CanBeReassigned([Values(true, false)] bool }); } - var actual = rvo.RealmValueProperty.AsDictionary(); - Assert.AreEqual(initialDictionary.AsDictionary().Count, actual.Count); + var actualDictionary = rvo.RealmValueProperty; + Assert.That(initialDictionary, Is.EqualTo(actualDictionary).Using(_rvComparer)); - var updatedDictionary = (RealmValue) new Dictionary { {"key2" , 2 } }; + var updatedDictionary = (RealmValue)new Dictionary { { "key2", 2 } }; _realm.Write(() => { rvo.RealmValueProperty = updatedDictionary; }); - actual = rvo.RealmValueProperty.AsDictionary(); - Assert.AreEqual(updatedDictionary.AsDictionary().Count, actual.Count); + actualDictionary = rvo.RealmValueProperty; + Assert.That(updatedDictionary, Is.EqualTo(actualDictionary).Using(_rvComparer)); } [Test] public void Dictionary_AfterCreation_EmbeddedListCanBeReassigned([Values(true, false)] bool isManaged) { - var embeddedList = new List{ new List{1, 2, 3}}; - var rvo = new RealmValueObject { RealmValueProperty = new Dictionary{ { "key", embeddedList}} }; + var initialList = new List { new List { 1, 2, 3 } }; + var rvo = new RealmValueObject + { + RealmValueProperty = new Dictionary { { "key", initialList } } + }; if (isManaged) { @@ -774,24 +775,27 @@ public void Dictionary_AfterCreation_EmbeddedListCanBeReassigned([Values(true, f }); } - var actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); - Assert.AreEqual(embeddedList.Count, actualEmbedded.Count); + var actualEmbeddedList = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); + Assert.That(initialList, Is.EqualTo(actualEmbeddedList).Using(_rvComparer)); - var updatedList = (RealmValue)new List{4, 5, 6}; + var updatedList = (RealmValue)new List { 4, 5, 6 }; _realm.Write(() => { rvo.RealmValueProperty.AsDictionary()["key"] = updatedList; }); - actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); - Assert.AreEqual(updatedList.AsList().Count, actualEmbedded.Count); + actualEmbeddedList = rvo.RealmValueProperty.AsDictionary()["key"].AsList(); + Assert.AreEqual(updatedList.AsList().Count, actualEmbeddedList.Count); } [Test] public void Dict_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, false)] bool isManaged) { - var embeddedDictionary = new Dictionary{{ "key1", 1}}; - var rvo = new RealmValueObject { RealmValueProperty = new Dictionary{ { "key", embeddedDictionary} } }; + var embeddedDictionary = new Dictionary { { "key1", 1 } }; + var rvo = new RealmValueObject + { + RealmValueProperty = new Dictionary { { "key", embeddedDictionary } } + }; if (isManaged) { @@ -802,19 +806,18 @@ public void Dict_AfterCreation_EmbeddedDictionaryCanBeReassigned([Values(true, f } var actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsDictionary(); - Assert.AreEqual(embeddedDictionary.Count, actualEmbedded.Count); + Assert.That(embeddedDictionary, Is.EqualTo(actualEmbedded).Using(_rvComparer)); - var updatedDictionary = new Dictionary{{ "key2", 2}}; + var updatedDictionary = new Dictionary { { "key2", 2 } }; _realm.Write(() => { rvo.RealmValueProperty.AsDictionary()["key"] = updatedDictionary; }); actualEmbedded = rvo.RealmValueProperty.AsDictionary()["key"].AsDictionary(); - Assert.AreEqual(updatedDictionary.Count, actualEmbedded.Count); + Assert.That(updatedDictionary, Is.EqualTo(actualEmbedded).Using(_rvComparer)); } - [Test] public void Dictionary_WhenManaged_CanBeModified() { diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs index 8764a57bdc..57e862fcdb 100644 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ b/Tests/Realm.Tests/Sync/SyncTestBase.cs @@ -239,7 +239,6 @@ private static T UpdateConfig(T config) typeof(SyncAllTypesObject), typeof(ObjectWithPartitionValue), typeof(RemappedTypeObject), - typeof(RealmValueObject), }; if (config is FlexibleSyncConfiguration) diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index 61182c355a..ebdcecaf36 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -560,6 +560,7 @@ public async Task SetAutomaticRecoveryEnabled(BaasApp app, bool enabled) { development_mode_enabled = true, }); + // Retrieve feature flags // var features = await GetPrivateAsync($"groups/{_groupId}/apps/{app}/features"); // ENABLE LEGACY MIXED SUPPORT @@ -568,7 +569,6 @@ public async Task SetAutomaticRecoveryEnabled(BaasApp app, bool enabled) // DISABLE LEGACY MIXED SUPPORT - Default on latest test server - https://github.com/10gen/baas/blob/master/etc/configs/test_config.json#L303 // await PostPrivateAsync($"features/legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "disable"} ); // await PostPrivateAsync($"features/bypass_legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "enable"} ); - return (app, mongoServiceId); } @@ -712,12 +712,15 @@ private async Task RefreshAccessTokenAsync() } private Task PostAsync(string relativePath, object obj) => SendAsync(HttpMethod.Post, relativePath, obj); + private Task PostPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Post, relativePath, obj); private Task GetAsync(string relativePath) => SendAsync(HttpMethod.Get, relativePath); + private Task GetPrivateAsync(string relativePath) => SendPrivateAsync(HttpMethod.Get, relativePath); private Task PutAsync(string relativePath, object obj) => SendAsync(HttpMethod.Put, relativePath, obj); + private Task PutPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Put, relativePath, obj); private Task PatchAsync(string relativePath, object obj) => SendAsync(new HttpMethod("PATCH"), relativePath, obj); @@ -752,6 +755,7 @@ private async Task RefreshAccessTokenAsync() return default; } + private async Task SendPrivateAsync(HttpMethod method, string relativePath, object? payload = null) { using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); From abd8268264a0bb67d9bf6c9a04a0fb95dd70f5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 15 Mar 2024 15:53:10 +0100 Subject: [PATCH 05/20] Merge test --- .../Sync/DataTypeSynchronizationTests.cs | 114 +++++++++++++----- 1 file changed, 83 insertions(+), 31 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 1be592adf2..3e19d8d6d3 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -26,6 +26,7 @@ using MongoDB.Bson; using Nito.AsyncEx; using NUnit.Framework; +using NUnit.Framework.Constraints; using Realms.Exceptions; using Realms.Extensions; using Realms.Helpers; @@ -787,11 +788,6 @@ public void ListManipulations() [Test] public void DictionaryManipulations() { - Logger.LogLevel = LogLevel.All; - Logger.Default = Logger.File("sync.log"); - - // equalsOverride ??= (a, b) => a?.Equals(b) == true; - SyncTestHelpers.RunBaasTestAsync(async () => { var realm1 = await GetFLXIntegrationRealmAsync(); @@ -807,59 +803,115 @@ public void DictionaryManipulations() var obj1 = realm1.Write(() => { - Logger.Default.Log(LogLevel.Debug, $"Writing: initial object"); var o = realm1.Add(new SyncAllTypesObject()); o.RealmValueProperty = new Dictionary(); return o; }); - Logger.Default.Log(LogLevel.Debug, $"Wrote: initial object"); await WaitForObjectAsync(obj1, realm2); foreach (var (index, realmTestValue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) { - realm1.Write(() => - { - Logger.Default.Log(LogLevel.Debug, $"Writing: [{index}] = {realmTestValue}"); - obj1.RealmValueProperty.AsDictionary()[$"{index}"] = realmTestValue; - }); - Logger.Default.Log(LogLevel.Debug, $"Wrote: [{index}] = {realmTestValue}"); + realm1.Write(() => { obj1.RealmValueProperty.AsDictionary()[$"{index}"] = realmTestValue; }); await realm1.SyncSession.WaitForUploadAsync(); - Logger.Default.Log(LogLevel.Debug, $"Awaiting: [{index}] = {realmTestValue}"); await realm2.SyncSession.WaitForDownloadAsync(); var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsDictionary(); var actualValues = obj2.RealmValueProperty.AsDictionary(); - Assert.AreEqual(expectedValues.Count, actualValues.Count, "Value: {0}", expectedValues); - // FIXME Equals does not test elementwise - // foreach (var valueTuple in expectedValues.Zip(actualValues, (x, y) => (x, y))) - // { - // Assert.AreEqual(valueTuple.x, valueTuple.y); - // } + Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } foreach (var (index, realmTestvalue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) { - realm1.Write(() => - { - obj1.RealmValueProperty.AsDictionary().Remove($"{index}"); - }); - + realm1.Write(() => { obj1.RealmValueProperty.AsDictionary().Remove($"{index}"); }); await realm1.SyncSession.WaitForUploadAsync(); - Logger.Default.Log(LogLevel.Debug, $"Awaiting: remove[{index}]"); - // await Task.Delay(1000); - await realm2.SyncSession.WaitForDownloadAsync(); + await realm2.SyncSession.WaitForDownloadAsync(); var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsDictionary(); var actualValues = obj2.RealmValueProperty.AsDictionary(); - Assert.AreEqual(expectedValues.Count, actualValues.Count); - // FIXME Equals does not test elementwise - Assert.False(obj2.RealmValueProperty.AsDictionary().ContainsKey($"{index}")); + Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } }); } + [Test] + public void CollectionMerge() + { + Logger.LogLevel = LogLevel.All; + Logger.Default = Logger.File("sync.log"); + SyncTestHelpers.RunBaasTestAsync(async () => + { + var realm1 = await GetFLXIntegrationRealmAsync(); + realm1.Subscriptions.Update(() => + { + realm1.Subscriptions.Add(realm1.All()); + }); + var realm2 = await GetFLXIntegrationRealmAsync(); + realm2.Subscriptions.Update(() => + { + realm2.Subscriptions.Add(realm2.All()); + }); + + var obj1 = realm1.Write(() => + { + var o = realm1.Add(new SyncAllTypesObject()); + o.RealmValueProperty = new Dictionary + { + { "list", new List { 1, 2, 3 } }, + { "dictionary", new Dictionary() { { "key1", 1 } } }, + }; + return o; + }); + + var obj2 = await WaitForObjectAsync(obj1, realm2); + + realm1.SyncSession.Stop(); + realm2.SyncSession.Stop(); + + realm1.Write(() => + { + var list = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); + list.RemoveAt(0); + list.Add(4); + var dictionary = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); + dictionary.Remove("key1"); + dictionary["key2"] = 2; + }); + realm2.Write(() => + { + var list = obj2.RealmValueProperty.AsDictionary()["list"].AsList(); + list.RemoveAt(0); + list.Add(5); + var dictionary = obj2.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); + dictionary.Remove("key1"); + dictionary["key3"] = 3; + }); + + realm1.SyncSession.Start(); + realm2.SyncSession.Start(); + + await realm1.SyncSession.WaitForUploadAsync(); + await realm2.SyncSession.WaitForUploadAsync(); + await realm1.SyncSession.WaitForDownloadAsync(); + await realm2.SyncSession.WaitForDownloadAsync(); + + var list1 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); + var dictionary1 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); + var list2 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); + var dictionary2 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); + + Assert.That(list1, Contains.Value(2)); + Assert.That(list1, Contains.Value(3)); + Assert.That(list1, Contains.Value(4)); + Assert.That(list1, Contains.Value(5)); + Assert.That(list1, Is.EqualTo(list2).Using(_rvComparer)); + + Assert.That(dictionary1, Contains.Key("key2")); + Assert.That(dictionary1, Contains.Key("key3")); + Assert.That(dictionary1, Is.EqualTo(dictionary2).Using(_rvComparer)); + }); + } private static RealmValue Clone(RealmValue original) { From 9bbfcb4a2f93b302559d8fd308c0aa7c184f027c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Mar 2024 10:52:01 +0100 Subject: [PATCH 06/20] Proper verification of collection in mixed merge test --- Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 3e19d8d6d3..23f63aa537 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -901,12 +901,14 @@ public void CollectionMerge() var list2 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); var dictionary2 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - Assert.That(list1, Contains.Value(2)); - Assert.That(list1, Contains.Value(3)); - Assert.That(list1, Contains.Value(4)); - Assert.That(list1, Contains.Value(5)); + CollectionAssert.DoesNotContain(list1, (RealmValue)1); + CollectionAssert.Contains(list1, (RealmValue)2); + CollectionAssert.Contains(list1, (RealmValue)3); + CollectionAssert.Contains(list1, (RealmValue)4); + CollectionAssert.Contains(list1, (RealmValue)5); Assert.That(list1, Is.EqualTo(list2).Using(_rvComparer)); + Assert.That(dictionary1, new NotConstraint(Contains.Key("key1"))); Assert.That(dictionary1, Contains.Key("key2")); Assert.That(dictionary1, Contains.Key("key3")); Assert.That(dictionary1, Is.EqualTo(dictionary2).Using(_rvComparer)); From 8a0a719b23816ac209f852ce92ffdc9ae71e4200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Mar 2024 13:28:08 +0100 Subject: [PATCH 07/20] Ignore crashing test --- Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 23f63aa537..1764e6af2d 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -836,6 +836,7 @@ public void DictionaryManipulations() } [Test] + [Ignore("Crashes until https://github.com/realm/realm-core/issues/7488 is fixed")] public void CollectionMerge() { Logger.LogLevel = LogLevel.All; From f5c369e46450f83a7fe1c49add24af9a1bff8c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 18 Mar 2024 13:42:50 +0100 Subject: [PATCH 08/20] Bump to core 14.3.0 --- CHANGELOG.md | 2 +- wrappers/realm-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f36ce6e8..508c992854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,7 +96,7 @@ * Realm Studio: 15.0.0 or later. ### Internal -* Using Core v14.2.0-11-g687bb983e. +* Using Core 14.3.0. ## 11.7.0 (2024-02-05) diff --git a/wrappers/realm-core b/wrappers/realm-core index 687bb983eb..e55176982e 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit 687bb983eb4de3f4167aabbcac548884cc7e54b4 +Subproject commit e55176982ed9154899a39693d24609645b81586b From afc297c43ae6acbcd705aa0fe3aae971b20aedd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 20 Mar 2024 17:29:31 +0100 Subject: [PATCH 09/20] Bump action-download-artifact --- .github/workflows/publish-prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml index 98b3abf6f8..91ed3f23de 100644 --- a/.github/workflows/publish-prerelease.yml +++ b/.github/workflows/publish-prerelease.yml @@ -11,7 +11,7 @@ jobs: submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: Download all artifacts - uses: dawidd6/action-download-artifact@46b4ae883bf0726f5949d025d31cb62c7a5ac70c + uses: dawidd6/action-download-artifact@v3.1.4 with: workflow: pr.yml commit: ${{ github.sha }} From d1bba82bb5f6599490d5075cac0505c07ff995ca Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:33:46 +0100 Subject: [PATCH 10/20] Corrections to collection assignments --- wrappers/src/dictionary_cs.cpp | 14 ++++++++++---- wrappers/src/list_cs.cpp | 14 ++++++++++---- wrappers/src/object_cs.cpp | 14 ++++++++++---- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/wrappers/src/dictionary_cs.cpp b/wrappers/src/dictionary_cs.cpp index daea6c6e25..c9bc2b94e7 100644 --- a/wrappers/src/dictionary_cs.cpp +++ b/wrappers/src/dictionary_cs.cpp @@ -84,13 +84,19 @@ extern "C" { switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: - dictionary.insert(dict_key, Mixed{}); + { dictionary.insert_collection(dict_key, CollectionType::List); - return new List(dictionary.get_list(dict_key)); + auto innerList = new List(dictionary.get_list(dict_key)); + innerList->remove_all(); + return innerList; + } case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: - dictionary.insert(dict_key, Mixed{}); + { dictionary.insert_collection(dict_key, CollectionType::Dictionary); - return new object_store::Dictionary(dictionary.get_dictionary(dict_key)); + auto innerDict = new object_store::Dictionary(dictionary.get_dictionary(dict_key)); + innerDict->remove_all(); + return innerDict; + } default: REALM_TERMINATE("Invalid collection type"); } diff --git a/wrappers/src/list_cs.cpp b/wrappers/src/list_cs.cpp index 8f3558a197..591954ed10 100644 --- a/wrappers/src/list_cs.cpp +++ b/wrappers/src/list_cs.cpp @@ -94,13 +94,19 @@ REALM_EXPORT void* list_set_collection(List& list, size_t list_ndx, realm_value_ switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: - list.set_any(list_ndx, Mixed{}); + { list.set_collection(list_ndx, CollectionType::List); - return new List(list.get_list(list_ndx)); + auto innerList = new List(list.get_list(list_ndx)); + innerList->remove_all(); + return innerList; + } case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: - list.set_any(list_ndx, Mixed{}); + { list.set_collection(list_ndx, CollectionType::Dictionary); - return new object_store::Dictionary(list.get_dictionary(list_ndx)); + auto innerDict = new object_store::Dictionary(list.get_dictionary(list_ndx)); + innerDict->remove_all(); + return innerDict; + } default: REALM_TERMINATE("Invalid collection type"); } diff --git a/wrappers/src/object_cs.cpp b/wrappers/src/object_cs.cpp index 2581da4f14..59db3e8a6a 100644 --- a/wrappers/src/object_cs.cpp +++ b/wrappers/src/object_cs.cpp @@ -184,13 +184,19 @@ extern "C" { switch (type) { case realm::binding::realm_value_type::RLM_TYPE_LIST: - object.get_obj().set_null(prop.column_key); + { object.get_obj().set_collection(prop.column_key, CollectionType::List); - return new List(object.realm(), object.get_obj(), prop.column_key); + auto innerList = new List(object.realm(), object.get_obj(), prop.column_key); + innerList->remove_all(); + return innerList; + } case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: - object.get_obj().set_null(prop.column_key); + { object.get_obj().set_collection(prop.column_key, CollectionType::Dictionary); - return new object_store::Dictionary(object.realm(), object.get_obj(), prop.column_key); + auto innerDict = new object_store::Dictionary(object.realm(), object.get_obj(), prop.column_key); + innerDict->remove_all(); + return innerDict; + } default: REALM_TERMINATE("Invalid collection type"); } From d402ab32cd3ef64a6f68ba3765ebbae9788a0cc0 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:41:48 +0100 Subject: [PATCH 11/20] Updated package --- .github/workflows/publish-prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml index 91ed3f23de..98b3abf6f8 100644 --- a/.github/workflows/publish-prerelease.yml +++ b/.github/workflows/publish-prerelease.yml @@ -11,7 +11,7 @@ jobs: submodules: false ref: ${{ github.event.pull_request.head.sha }} - name: Download all artifacts - uses: dawidd6/action-download-artifact@v3.1.4 + uses: dawidd6/action-download-artifact@46b4ae883bf0726f5949d025d31cb62c7a5ac70c with: workflow: pr.yml commit: ${{ github.sha }} From 5dde97095e0ca5cfa1f58da18d4a9df8f64d2459 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:44:10 +0100 Subject: [PATCH 12/20] Adding the PR packages to sleet --- .github/templates/pr.yml | 14 ++++++++++++-- .github/workflows/pr.yml | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.github/templates/pr.yml b/.github/templates/pr.yml index 22f71ff66e..30c240a867 100644 --- a/.github/templates/pr.yml +++ b/.github/templates/pr.yml @@ -1,6 +1,6 @@ #@ load("@ytt:template", "template") -#@ load("common.lib.yml", "checkoutCode", "fetchPackageArtifacts", "nugetPackages") -#@ load("build.lib.yml", "deployBaas", "cleanupBaas", "runTests", "runNetCoreTests", "runWovenClassesTests", "runSourceGenerationTests", "buildUnity", "testUnity") +#@ load("common.lib.yml", "checkoutCode", "fetchPackageArtifacts", "nugetPackages", "uploadPackagesToSleet") +#@ load("build.lib.yml", "deployBaas", "cleanupBaas", "runTests", "runNetCoreTests", "runWovenClassesTests", "runSourceGenerationTests", "buildUnity", "testUnity", "ignoreSkippedJobsCondition") --- name: PR Build "on": @@ -40,6 +40,16 @@ jobs: - build-wrappers with: build-docs: ${{ contains(github.head_ref, 'release') }} + publish-packages-to-sleet: + runs-on: ubuntu-latest + name: Publish package to S3 + needs: + - build-packages + if: #@ ignoreSkippedJobsCondition + " && (github.event_name == 'push' || github.event.inputs.publish-prerelease)" + steps: + - #@ template.replace(checkoutCode(False, False)) + - #@ template.replace(fetchPackageArtifacts("needs.build-packages.outputs.package_version")) + - #@ template.replace(uploadPackagesToSleet("needs.build-packages.outputs.package_version", True)) _: #@ template.replace(buildUnity()) _: #@ template.replace(testUnity('["Mono-Net4"]', '[{ "os": "windows", "testPlatform": "Windows64" }, { "os": "linux", "testPlatform": "Linux64" }]')) _: #@ template.replace(runTests(".NET Framework", runSyncTests = False)) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6a9889f38f..31071f80a8 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,6 +39,44 @@ jobs: - build-wrappers with: build-docs: ${{ contains(github.head_ref, 'release') }} + publish-packages-to-sleet: + runs-on: ubuntu-latest + name: Publish package to S3 + needs: + - build-packages + if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && (github.event_name == 'push' || github.event.inputs.publish-prerelease) + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + submodules: false + ref: ${{ github.event.pull_request.head.sha }} + - name: Fetch Realm.PlatformHelpers + uses: actions/download-artifact@v3 + with: + name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} + path: ${{ github.workspace }}/Realm/packages/ + - name: Fetch Realm + uses: actions/download-artifact@v3 + with: + name: Realm.${{ needs.build-packages.outputs.package_version }} + path: ${{ github.workspace }}/Realm/packages/ + - name: Configure .NET + uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a + with: + dotnet-version: 6.0.x + - name: Install sleet + run: dotnet tool install -g sleet + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1-node16 + with: + aws-access-key-id: ${{ secrets.NUGET_S3_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.NUGET_S3_SECRET_KEY }} + aws-region: us-east-1 + - name: NuGet Publish Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} + run: sleet push ${{ github.workspace }}/Realm/packages/Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource + - name: NuGet Publish Realm.${{ needs.build-packages.outputs.package_version }} + run: sleet push ${{ github.workspace }}/Realm/packages/Realm.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource build-unity: uses: ./.github/workflows/build-unity.yml name: Package From a9616003cd75e28918c7db65cfab1417130957d5 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:45:41 +0100 Subject: [PATCH 13/20] Fixed publishing to sleet --- .github/templates/pr.yml | 2 +- .github/workflows/pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/templates/pr.yml b/.github/templates/pr.yml index 30c240a867..5bea1997eb 100644 --- a/.github/templates/pr.yml +++ b/.github/templates/pr.yml @@ -45,7 +45,7 @@ jobs: name: Publish package to S3 needs: - build-packages - if: #@ ignoreSkippedJobsCondition + " && (github.event_name == 'push' || github.event.inputs.publish-prerelease)" + if: #@ ignoreSkippedJobsCondition steps: - #@ template.replace(checkoutCode(False, False)) - #@ template.replace(fetchPackageArtifacts("needs.build-packages.outputs.package_version")) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 31071f80a8..5038abda36 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -44,7 +44,7 @@ jobs: name: Publish package to S3 needs: - build-packages - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && (github.event_name == 'push' || github.event.inputs.publish-prerelease) + if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') steps: - name: Checkout code uses: actions/checkout@v3 From 5c393708b9cdc6a38b3a382e88fe531043515a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Apr 2024 09:45:52 +0200 Subject: [PATCH 14/20] Revert publishing to sleet This reverts commit 5dde97095e0ca5cfa1f58da18d4a9df8f64d2459. This reverts commit a9616003cd75e28918c7db65cfab1417130957d5. --- .github/templates/pr.yml | 14 ++------------ .github/workflows/pr.yml | 38 -------------------------------------- 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/.github/templates/pr.yml b/.github/templates/pr.yml index 5bea1997eb..22f71ff66e 100644 --- a/.github/templates/pr.yml +++ b/.github/templates/pr.yml @@ -1,6 +1,6 @@ #@ load("@ytt:template", "template") -#@ load("common.lib.yml", "checkoutCode", "fetchPackageArtifacts", "nugetPackages", "uploadPackagesToSleet") -#@ load("build.lib.yml", "deployBaas", "cleanupBaas", "runTests", "runNetCoreTests", "runWovenClassesTests", "runSourceGenerationTests", "buildUnity", "testUnity", "ignoreSkippedJobsCondition") +#@ load("common.lib.yml", "checkoutCode", "fetchPackageArtifacts", "nugetPackages") +#@ load("build.lib.yml", "deployBaas", "cleanupBaas", "runTests", "runNetCoreTests", "runWovenClassesTests", "runSourceGenerationTests", "buildUnity", "testUnity") --- name: PR Build "on": @@ -40,16 +40,6 @@ jobs: - build-wrappers with: build-docs: ${{ contains(github.head_ref, 'release') }} - publish-packages-to-sleet: - runs-on: ubuntu-latest - name: Publish package to S3 - needs: - - build-packages - if: #@ ignoreSkippedJobsCondition - steps: - - #@ template.replace(checkoutCode(False, False)) - - #@ template.replace(fetchPackageArtifacts("needs.build-packages.outputs.package_version")) - - #@ template.replace(uploadPackagesToSleet("needs.build-packages.outputs.package_version", True)) _: #@ template.replace(buildUnity()) _: #@ template.replace(testUnity('["Mono-Net4"]', '[{ "os": "windows", "testPlatform": "Windows64" }, { "os": "linux", "testPlatform": "Linux64" }]')) _: #@ template.replace(runTests(".NET Framework", runSyncTests = False)) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5038abda36..6a9889f38f 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,44 +39,6 @@ jobs: - build-wrappers with: build-docs: ${{ contains(github.head_ref, 'release') }} - publish-packages-to-sleet: - runs-on: ubuntu-latest - name: Publish package to S3 - needs: - - build-packages - if: always() && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - submodules: false - ref: ${{ github.event.pull_request.head.sha }} - - name: Fetch Realm.PlatformHelpers - uses: actions/download-artifact@v3 - with: - name: Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - - name: Fetch Realm - uses: actions/download-artifact@v3 - with: - name: Realm.${{ needs.build-packages.outputs.package_version }} - path: ${{ github.workspace }}/Realm/packages/ - - name: Configure .NET - uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a - with: - dotnet-version: 6.0.x - - name: Install sleet - run: dotnet tool install -g sleet - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1-node16 - with: - aws-access-key-id: ${{ secrets.NUGET_S3_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.NUGET_S3_SECRET_KEY }} - aws-region: us-east-1 - - name: NuGet Publish Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }} - run: sleet push ${{ github.workspace }}/Realm/packages/Realm.PlatformHelpers.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource - - name: NuGet Publish Realm.${{ needs.build-packages.outputs.package_version }} - run: sleet push ${{ github.workspace }}/Realm/packages/Realm.${{ needs.build-packages.outputs.package_version }}.nupkg --config ${{ github.workspace }}/.github/sleet.json --source NugetSource build-unity: uses: ./.github/workflows/build-unity.yml name: Package From 550ba8fa5b16f2361cf7c55092c2533f7bbffbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Apr 2024 10:01:23 +0200 Subject: [PATCH 15/20] Bump to Core 14.4.1 --- CHANGELOG.md | 2 +- wrappers/realm-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508c992854..e098fa517e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,7 +96,7 @@ * Realm Studio: 15.0.0 or later. ### Internal -* Using Core 14.3.0. +* Using Core 14.4.1. ## 11.7.0 (2024-02-05) diff --git a/wrappers/realm-core b/wrappers/realm-core index e55176982e..374dd672af 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit e55176982ed9154899a39693d24609645b81586b +Subproject commit 374dd672af357732dccc135fecc905406fec3223 From 0ea91fc8f4eb1fdffe649d8f99a2e5fd3ba2ec85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 2 Apr 2024 10:04:27 +0200 Subject: [PATCH 16/20] Remove feature flag toggling infrastructure --- Tools/DeployApps/BaasClient.cs | 51 ---------------------------------- 1 file changed, 51 deletions(-) diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index ebdcecaf36..e300f0359a 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -148,7 +148,6 @@ public class FunctionReturn };"; private readonly HttpClient _client = new(); - private readonly HttpClient _privateclient = new(); private readonly string? _clusterName; @@ -197,9 +196,6 @@ private BaasClient(Uri baseUri, string differentiator, TextWriter output, string _clusterName = clusterName; Differentiator = differentiator; _output = output; - - _privateclient.BaseAddress = new Uri(baseUri, "api/private/v1.0/"); - _privateclient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json"); } public static async Task Docker(Uri baseUri, string differentiator, TextWriter output) @@ -297,7 +293,6 @@ private async Task Authenticate(string provider, object credentials) _refreshToken = authDoc!["refresh_token"].AsString; _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); - _privateclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authDoc["access_token"].AsString); } public static async Task GetOrDeployContainer(string baasaasApiKey, string differentiator, TextWriter output) @@ -560,15 +555,6 @@ public async Task SetAutomaticRecoveryEnabled(BaasApp app, bool enabled) { development_mode_enabled = true, }); - - // Retrieve feature flags - // var features = await GetPrivateAsync($"groups/{_groupId}/apps/{app}/features"); - // ENABLE LEGACY MIXED SUPPORT - // await PostPrivateAsync($"features/legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "enable"} ); - // await PostPrivateAsync($"features/bypass_legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "disable"} ); - // DISABLE LEGACY MIXED SUPPORT - Default on latest test server - https://github.com/10gen/baas/blob/master/etc/configs/test_config.json#L303 - // await PostPrivateAsync($"features/legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "disable"} ); - // await PostPrivateAsync($"features/bypass_legacy_mixed_type", new { app_ids = new []{$"{appId}"}, action = "enable"} ); return (app, mongoServiceId); } @@ -713,16 +699,10 @@ private async Task RefreshAccessTokenAsync() private Task PostAsync(string relativePath, object obj) => SendAsync(HttpMethod.Post, relativePath, obj); - private Task PostPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Post, relativePath, obj); - private Task GetAsync(string relativePath) => SendAsync(HttpMethod.Get, relativePath); - private Task GetPrivateAsync(string relativePath) => SendPrivateAsync(HttpMethod.Get, relativePath); - private Task PutAsync(string relativePath, object obj) => SendAsync(HttpMethod.Put, relativePath, obj); - private Task PutPrivateAsync(string relativePath, object obj) => SendPrivateAsync(HttpMethod.Put, relativePath, obj); - private Task PatchAsync(string relativePath, object obj) => SendAsync(new HttpMethod("PATCH"), relativePath, obj); private async Task SendAsync(HttpMethod method, string relativePath, object? payload = null) @@ -756,37 +736,6 @@ private async Task RefreshAccessTokenAsync() return default; } - private async Task SendPrivateAsync(HttpMethod method, string relativePath, object? payload = null) - { - using var message = new HttpRequestMessage(method, new Uri(relativePath, UriKind.Relative)); - if (payload != null) - { - message.Content = GetJsonContent(payload); - } - - var response = await _privateclient.SendAsync(message); - if (!response.IsSuccessStatusCode) - { - if (response.StatusCode == HttpStatusCode.Unauthorized && _refreshToken != null) - { - await RefreshAccessTokenAsync(); - return await SendAsync(method, relativePath, payload); - } - - var content = await response.Content.ReadAsStringAsync(); - throw new Exception($"An error ({response.StatusCode}) occurred while executing {method} {relativePath}: {content}"); - } - - var json = await response.Content.ReadAsStringAsync(); - - if (!string.IsNullOrWhiteSpace(json)) - { - return BsonSerializer.Deserialize(json); - } - - return default; - } - public static HttpContent GetJsonContent(object obj) { string jsonContent; From 38eb4bfdb03f8893f009f739f90b761bb408725a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 4 Apr 2024 14:14:18 +0200 Subject: [PATCH 17/20] Updated according to review comments --- .../Sync/DataTypeSynchronizationTests.cs | 141 ++++++++++-------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 1764e6af2d..6b2ac03bff 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -246,7 +246,7 @@ public class DataTypeSynchronizationTests : SyncTestBase public void Dict_Binary() => TestDictionaryCore(o => o.ByteArrayDict, TestHelpers.GetBytes(10), TestHelpers.GetBytes(15), (a, b) => a.SequenceEqual(b)); [Test] - public void Property_Binary() => TestPropertyCore(o => o.ByteArrayProperty, (o, rv) => o.ByteArrayProperty = rv, TestHelpers.GetBytes(5), TestHelpers.GetBytes(10), (a, b) => a!.SequenceEqual(b!)); + public void Property_Binary() => TestPropertyCore(o => o.ByteArrayProperty, (o, rv) => o.ByteArrayProperty = rv, TestHelpers.GetBytes(5), TestHelpers.GetBytes(10)); #endregion @@ -275,7 +275,7 @@ public class DataTypeSynchronizationTests : SyncTestBase #region RealmValue - public static readonly object[] RealmTestPrimitiveValues = new[] + public static readonly object[] RealmTestPrimitiveValues = { new object[] { (RealmValue)"abc", (RealmValue)10 }, new object[] { (RealmValue)new ObjectId("5f63e882536de46d71877979"), (RealmValue)new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}") }, @@ -285,11 +285,12 @@ public class DataTypeSynchronizationTests : SyncTestBase new object[] { (RealmValue)12.5f, (RealmValue)15d }, }; - public static readonly object[] RealmTestValuesWithCollections = RealmTestPrimitiveValues.Concat( new[] + public static readonly object[] RealmTestValuesWithCollections = RealmTestPrimitiveValues.Concat(new[] { - new object[] { (RealmValue)12.5f, (RealmValue)15d }, new object[] + new object[] { 12.5f, 15d }, + new object[] { - (RealmValue)new List + new List { RealmValue.Null, 1, @@ -302,15 +303,20 @@ public class DataTypeSynchronizationTests : SyncTestBase 3m, new ObjectId("5f63e882536de46d71877979"), Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, + new List { 1, true, "string" }, + new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + } }, - (RealmValue)15d + 15d }, new object[] { - (RealmValue)new Dictionary + new Dictionary { { "key1", RealmValue.Null }, { "key2", 1 }, @@ -323,11 +329,18 @@ public class DataTypeSynchronizationTests : SyncTestBase { "key9", 3m }, { "key10", new ObjectId("5f63e882536de46d71877979") }, { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, + { "key12", new List { 1, true, "string" } }, + { + "key13", new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + } + }, }, - (RealmValue)15d + 15d }, }).ToArray(); @@ -345,8 +358,7 @@ public void Dict_RealmValue(RealmValue first, RealmValue second) => TestDictiona [TestCaseSource(nameof(RealmTestValuesWithCollections))] public void Property_RealmValue(RealmValue first, RealmValue second) => TestPropertyCore( - o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, Clone(first), Clone(second), - equalsOverride: RealmValueEquals); + o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, Clone(first), Clone(second)); #endregion @@ -537,7 +549,7 @@ private void TestDictionaryCore(Func(Func getter, Action setter, - T item1, T item2, Func? equalsOverride = null) + T item1, T item2) { SyncTestHelpers.RunBaasTestAsync(async () => { @@ -603,28 +615,32 @@ public void Bootstrap() realm1.Subscriptions.Add(realm1.All()); }); + var parent = new SyncAllTypesObject(); var child = new IntPropertyObject(); - var valuesValue = new List() + + var valuesValue = new List { 1, - (RealmValue)"Realm", - (RealmValue)child, - (RealmValue)new List() { 1, "Realm", child }, - (RealmValue)new Dictionary() + "Realm", + child, + new List { 1, "Realm", child }, + new Dictionary { { "key1", 1 }, { "key2", "Realm" }, { "key3", child }, } }; - var (parentId, childId) = realm1.Write(() => + realm1.Write(() => { - var parent = realm1.Add(new SyncAllTypesObject()); + realm1.Add(parent); parent.StringProperty = "PARENT"; parent.ObjectProperty = child; parent.RealmValueProperty = valuesValue; - return (parent.Id, child.Id); }); + var parentId = parent.Id; + var childId = child.Id; + await realm1.SyncSession.WaitForUploadAsync(); realm1.Dispose(); @@ -662,16 +678,16 @@ await TestHelpers.WaitForConditionAsync(() => realm2.FindCore public static readonly IList RealmValueCollectionTestValues = new List() { - (RealmValue)"abc", - (RealmValue)new ObjectId("5f63e882536de46d71877979"), - (RealmValue)new byte[] { 0, 1, 2 }, - (RealmValue)DateTimeOffset.FromUnixTimeSeconds(1616137641), - (RealmValue)true, + "abc", + new ObjectId("5f63e882536de46d71877979"), + new byte[] { 0, 1, 2 }, + DateTimeOffset.FromUnixTimeSeconds(1616137641), + true, RealmValue.Null, - (RealmValue)5m, - (RealmValue)12.5f, - (RealmValue)15d, - (RealmValue)new List + 5m, + 12.5f, + 15d, + new List { RealmValue.Null, 1, @@ -684,11 +700,16 @@ await TestHelpers.WaitForConditionAsync(() => realm2.FindCore 3m, new ObjectId("5f63e882536de46d71877979"), Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31"), - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, + new List { 1, true, "string" }, + new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + } }, - (RealmValue)new Dictionary + new Dictionary { { "key1", RealmValue.Null }, { "key2", 1 }, @@ -701,9 +722,16 @@ await TestHelpers.WaitForConditionAsync(() => realm2.FindCore { "key9", 3m }, { "key10", new ObjectId("5f63e882536de46d71877979") }, { "key11", Guid.Parse("3809d6d9-7618-4b3d-8044-2aa35fd02f31") }, - // new InternalObject { IntProperty = 10, StringProperty = "brown" }, - // innerList, - // innerDict, + { "key12", new List { 1, true, "string" } }, + { + "key13", new Dictionary + { + { "key1", RealmValue.Null }, + { "key2", 1 }, + { "key3", true }, + { "key4", "string" }, + } + }, }, }; @@ -726,7 +754,7 @@ public void ListManipulations() return o; }); - await WaitForObjectAsync(obj1, realm2); + var obj2 = await WaitForObjectAsync(obj1, realm2); // Append elements one by one and verify that they are synced foreach (var realmTestValue in RealmValueCollectionTestValues) @@ -738,14 +766,13 @@ public void ListManipulations() await realm1.SyncSession.WaitForUploadAsync(); await realm2.SyncSession.WaitForDownloadAsync(); - var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } // Remove elements one by one and verify that changes are synced - foreach (var realmTestValue in RealmValueCollectionTestValues) + for (int index = 0; index < RealmValueCollectionTestValues.Count; index++) { realm1.Write(() => { @@ -754,7 +781,6 @@ public void ListManipulations() await realm1.SyncSession.WaitForUploadAsync(); await realm2.SyncSession.WaitForDownloadAsync(); - var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); @@ -777,7 +803,6 @@ public void ListManipulations() await realm1.SyncSession.WaitForUploadAsync(); await realm2.SyncSession.WaitForDownloadAsync(); - var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsList(); var actualValues = obj2.RealmValueProperty.AsList(); Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); @@ -795,6 +820,7 @@ public void DictionaryManipulations() { realm1.Subscriptions.Add(realm1.All()); }); + var realm2 = await GetFLXIntegrationRealmAsync(); realm2.Subscriptions.Update(() => { @@ -808,26 +834,25 @@ public void DictionaryManipulations() return o; }); - await WaitForObjectAsync(obj1, realm2); - foreach (var (index, realmTestValue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) + var obj2 = await WaitForObjectAsync(obj1, realm2); + + for (int index = 0; index < RealmTestValuesWithCollections.Length; index++) { + var realmTestValue = RealmValueCollectionTestValues[index]; realm1.Write(() => { obj1.RealmValueProperty.AsDictionary()[$"{index}"] = realmTestValue; }); await realm1.SyncSession.WaitForUploadAsync(); await realm2.SyncSession.WaitForDownloadAsync(); - var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsDictionary(); var actualValues = obj2.RealmValueProperty.AsDictionary(); Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); } - foreach (var (index, realmTestvalue) in RealmValueCollectionTestValues.Select((value, index) => (index, value))) + for (int index = 0; index < RealmTestValuesWithCollections.Length; index++) { realm1.Write(() => { obj1.RealmValueProperty.AsDictionary().Remove($"{index}"); }); await realm1.SyncSession.WaitForUploadAsync(); - await realm2.SyncSession.WaitForDownloadAsync(); - var obj2 = await WaitForObjectAsync(obj1, realm2); var expectedValues = obj1.RealmValueProperty.AsDictionary(); var actualValues = obj2.RealmValueProperty.AsDictionary(); Assert.That(expectedValues, Is.EqualTo(actualValues).Using(_rvComparer)); @@ -839,8 +864,6 @@ public void DictionaryManipulations() [Ignore("Crashes until https://github.com/realm/realm-core/issues/7488 is fixed")] public void CollectionMerge() { - Logger.LogLevel = LogLevel.All; - Logger.Default = Logger.File("sync.log"); SyncTestHelpers.RunBaasTestAsync(async () => { var realm1 = await GetFLXIntegrationRealmAsync(); @@ -902,11 +925,11 @@ public void CollectionMerge() var list2 = obj1.RealmValueProperty.AsDictionary()["list"].AsList(); var dictionary2 = obj1.RealmValueProperty.AsDictionary()["dictionary"].AsDictionary(); - CollectionAssert.DoesNotContain(list1, (RealmValue)1); - CollectionAssert.Contains(list1, (RealmValue)2); - CollectionAssert.Contains(list1, (RealmValue)3); - CollectionAssert.Contains(list1, (RealmValue)4); - CollectionAssert.Contains(list1, (RealmValue)5); + Assert.That(list1, new NotConstraint(Contains.Item((RealmValue)1))); + Assert.That(list1, Contains.Item((RealmValue)2)); + Assert.That(list1, Contains.Item((RealmValue)3)); + Assert.That(list1, Contains.Item((RealmValue)4)); + Assert.That(list1, Contains.Item((RealmValue)5)); Assert.That(list1, Is.EqualTo(list2).Using(_rvComparer)); Assert.That(dictionary1, new NotConstraint(Contains.Key("key1"))); From e8b90e15933331b9e6215976c5cc965e49857bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Thu, 4 Apr 2024 15:46:49 +0200 Subject: [PATCH 18/20] Re-add RealmValue casts --- Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 6b2ac03bff..0187ec618a 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -287,10 +287,10 @@ public class DataTypeSynchronizationTests : SyncTestBase public static readonly object[] RealmTestValuesWithCollections = RealmTestPrimitiveValues.Concat(new[] { - new object[] { 12.5f, 15d }, + new object[] { (RealmValue)12.5f, (RealmValue)15d }, new object[] { - new List + (RealmValue)new List { RealmValue.Null, 1, @@ -312,11 +312,11 @@ public class DataTypeSynchronizationTests : SyncTestBase { "key4", "string" }, } }, - 15d + (RealmValue)15d }, new object[] { - new Dictionary + (RealmValue)new Dictionary { { "key1", RealmValue.Null }, { "key2", 1 }, @@ -340,7 +340,7 @@ public class DataTypeSynchronizationTests : SyncTestBase } }, }, - 15d + (RealmValue)15d }, }).ToArray(); From 0df7b75b51ae6d161b748a29ef5ebb9101279685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Apr 2024 13:09:58 +0200 Subject: [PATCH 19/20] Another round for linting --- Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 0187ec618a..7e40bf4f55 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -658,7 +658,7 @@ await TestHelpers.WaitForConditionAsync(() => realm2.FindCore realm2.FindCore(childId), o => o != null); - var syncedValues = syncedParent.RealmValueProperty.AsList(); + var syncedValues = syncedParent!.RealmValueProperty.AsList(); Assert.AreEqual(valuesValue[0], syncedValues[0]); Assert.AreEqual(valuesValue[1], syncedValues[1]); Assert.AreEqual(childId, syncedValues[2].AsRealmObject().Id); @@ -735,7 +735,6 @@ await TestHelpers.WaitForConditionAsync(() => realm2.FindCore }, }; - [Test] public void ListManipulations() { From 257214480f31f8f1d807d3e73758d4bf07faa4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 8 Apr 2024 11:15:43 +0200 Subject: [PATCH 20/20] Remove unused imports --- Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs index 7e40bf4f55..4d55c43431 100644 --- a/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeSynchronizationTests.cs @@ -22,17 +22,11 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Baas; using MongoDB.Bson; -using Nito.AsyncEx; using NUnit.Framework; using NUnit.Framework.Constraints; -using Realms.Exceptions; using Realms.Extensions; using Realms.Helpers; -using Realms.Logging; -using Realms.Sync; -using Realms.Sync.Exceptions; using Realms.Tests.Database; namespace Realms.Tests.Sync