Skip to content

Commit f1c0935

Browse files
authored
Add collection in mixed sync roundtrip tests (#1783)
1 parent 71fbef9 commit f1c0935

File tree

4 files changed

+280
-4
lines changed

4 files changed

+280
-4
lines changed

packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import io.realm.kotlin.types.RealmAny
2020
import io.realm.kotlin.types.RealmObject
2121
import io.realm.kotlin.types.annotations.PersistedName
2222
import io.realm.kotlin.types.annotations.PrimaryKey
23+
import org.mongodb.kbson.ObjectId
2324

24-
class JsonStyleRealmObject(id: String) : RealmObject {
25-
constructor() : this("JsonStyleRealmObject")
25+
class JsonStyleRealmObject : RealmObject {
2626
@PrimaryKey
2727
@PersistedName("_id")
28-
var id: String = id
28+
var id: String = ObjectId().toHexString()
29+
var selector: String = "DEFAULT"
2930
var value: RealmAny? = null
3031
}

packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.realm.kotlin.Realm
2020
import io.realm.kotlin.RealmConfiguration
2121
import io.realm.kotlin.entities.JsonStyleRealmObject
2222
import io.realm.kotlin.ext.asFlow
23+
import io.realm.kotlin.ext.realmAnyDictionaryOf
2324
import io.realm.kotlin.ext.realmAnyListOf
2425
import io.realm.kotlin.internal.platform.runBlocking
2526
import io.realm.kotlin.notifications.DeletedObject
@@ -34,6 +35,7 @@ import kotlinx.coroutines.channels.Channel
3435
import kotlin.test.AfterTest
3536
import kotlin.test.BeforeTest
3637
import kotlin.test.Test
38+
import kotlin.test.assertContentEquals
3739
import kotlin.test.assertEquals
3840
import kotlin.test.assertIs
3941
import kotlin.test.assertTrue
@@ -96,6 +98,61 @@ class RealmAnyNestedCollectionNotificationTest {
9698
assertEquals(listOf(1, 4, 3), nestedList.map { it!!.asInt() })
9799
}
98100

101+
// List operations
102+
realm.write {
103+
findLatest(o)!!.value = realmAnyListOf(1, 2, 3)
104+
}
105+
channel.receiveOrFail().apply {
106+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
107+
assertContentEquals(realmAnyListOf(1, 2, 3).asList(), this.obj!!.value!!.asList())
108+
}
109+
110+
// List add
111+
realm.write {
112+
findLatest(o)!!.value!!.asList().add(RealmAny.create("Realm"))
113+
}
114+
channel.receiveOrFail().apply {
115+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
116+
assertContentEquals(realmAnyListOf(1, 2, 3, "Realm").asList(), this.obj!!.value!!.asList())
117+
}
118+
119+
// List remove
120+
realm.write {
121+
findLatest(o)!!.value!!.asList().remove(RealmAny.create(2))
122+
}
123+
channel.receiveOrFail().apply {
124+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
125+
assertContentEquals(realmAnyListOf(1, 3, "Realm").asList(), this.obj!!.value!!.asList())
126+
}
127+
128+
// Dictionary operations
129+
realm.write {
130+
findLatest(o)!!.value = realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3)
131+
}
132+
133+
channel.receiveOrFail().apply {
134+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
135+
assertEquals(realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3).asDictionary(), this.obj!!.value!!.asDictionary())
136+
}
137+
138+
realm.write {
139+
findLatest(o)!!.value!!.asDictionary()["key4"] = RealmAny.create("Realm")
140+
}
141+
142+
channel.receiveOrFail().apply {
143+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
144+
assertEquals(realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to "Realm").asDictionary(), this.obj!!.value!!.asDictionary())
145+
}
146+
147+
realm.write {
148+
findLatest(o)!!.value!!.asDictionary().remove("key2")
149+
}
150+
151+
channel.receiveOrFail().apply {
152+
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
153+
assertEquals(realmAnyDictionaryOf("key1" to 1, "key3" to 3, "key4" to "Realm").asDictionary(), this.obj!!.value!!.asDictionary())
154+
}
155+
99156
realm.write {
100157
delete(findLatest(o)!!)
101158
}

packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/Schema.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.realm.kotlin.test.mongodb.common
1818

19+
import io.realm.kotlin.entities.JsonStyleRealmObject
1920
import io.realm.kotlin.entities.Location
2021
import io.realm.kotlin.entities.sync.BinaryObject
2122
import io.realm.kotlin.entities.sync.COLLECTION_SCHEMAS
@@ -50,11 +51,12 @@ private val DEFAULT_SCHEMAS = setOf(
5051
SyncPerson::class,
5152
SyncRestaurant::class,
5253
Location::class,
54+
JsonStyleRealmObject::class,
5355
)
5456

5557
val PARTITION_BASED_SCHEMA = DEFAULT_SCHEMAS
5658
// Amount of schema classes that should be created on the server. EmbeddedRealmObjects and
5759
// AsymmetricRealmObjects are not included in this count.
5860
// Run FlexibleSyncSchemaTests.flexibleSyncSchemaCount for verification.
59-
const val FLEXIBLE_SYNC_SCHEMA_COUNT = 14
61+
const val FLEXIBLE_SYNC_SCHEMA_COUNT = 15
6062
val FLEXIBLE_SYNC_SCHEMA = DEFAULT_SCHEMAS + ASYMMETRIC_SCHEMAS + COLLECTION_SCHEMAS

packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ package io.realm.kotlin.test.mongodb.common
2020
import io.realm.kotlin.Realm
2121
import io.realm.kotlin.RealmConfiguration
2222
import io.realm.kotlin.VersionId
23+
import io.realm.kotlin.entities.JsonStyleRealmObject
2324
import io.realm.kotlin.entities.sync.BinaryObject
2425
import io.realm.kotlin.entities.sync.ChildPk
2526
import io.realm.kotlin.entities.sync.ParentPk
2627
import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes
2728
import io.realm.kotlin.entities.sync.flx.FlexChildObject
2829
import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject
2930
import io.realm.kotlin.entities.sync.flx.FlexParentObject
31+
import io.realm.kotlin.ext.asFlow
32+
import io.realm.kotlin.ext.asRealmObject
3033
import io.realm.kotlin.ext.query
34+
import io.realm.kotlin.ext.realmAnyDictionaryOf
35+
import io.realm.kotlin.ext.realmAnyListOf
3136
import io.realm.kotlin.internal.interop.ErrorCode
3237
import io.realm.kotlin.internal.interop.RealmInterop
3338
import io.realm.kotlin.internal.platform.fileExists
@@ -56,6 +61,7 @@ import io.realm.kotlin.query.RealmResults
5661
import io.realm.kotlin.schema.RealmClass
5762
import io.realm.kotlin.schema.RealmSchema
5863
import io.realm.kotlin.schema.ValuePropertyType
64+
import io.realm.kotlin.test.mongodb.TEST_APP_FLEX
5965
import io.realm.kotlin.test.mongodb.TestApp
6066
import io.realm.kotlin.test.mongodb.asTestApp
6167
import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage
@@ -70,6 +76,9 @@ import io.realm.kotlin.test.util.receiveOrFail
7076
import io.realm.kotlin.test.util.trySendOrFail
7177
import io.realm.kotlin.test.util.use
7278
import io.realm.kotlin.types.BaseRealmObject
79+
import io.realm.kotlin.types.RealmAny
80+
import io.realm.kotlin.types.RealmDictionary
81+
import io.realm.kotlin.types.RealmList
7382
import kotlinx.atomicfu.atomic
7483
import kotlinx.coroutines.Dispatchers
7584
import kotlinx.coroutines.async
@@ -707,6 +716,213 @@ class SyncedRealmTests {
707716
}
708717
}
709718

719+
@Test
720+
fun roundtripCollectionsInMixed() = runBlocking {
721+
val (email1, password1) = randomEmail() to "password1234"
722+
val (email2, password2) = randomEmail() to "password1234"
723+
val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX)
724+
val user1 = app.createUserAndLogIn(email1, password1)
725+
val user2 = app.createUserAndLogIn(email2, password2)
726+
727+
// Create object with all types
728+
val selector = ObjectId().toString()
729+
730+
createFlexibleSyncConfig(
731+
user = user1,
732+
initialSubscriptions = { realm ->
733+
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
734+
}
735+
).let { config ->
736+
Realm.open(config).use { realm ->
737+
realm.write {
738+
val child = (
739+
JsonStyleRealmObject().apply {
740+
this.id = "CHILD"
741+
this.selector = selector
742+
}
743+
)
744+
745+
copyToRealm(
746+
JsonStyleRealmObject().apply {
747+
this.id = "PARENT"
748+
this.selector = selector
749+
value = realmAnyDictionaryOf(
750+
"primitive" to 1,
751+
// List with nested dictionary
752+
"list" to realmAnyListOf(1, "Realm", child, realmAnyDictionaryOf("listkey1" to 1, "listkey2" to "Realm", "listkey3" to child)),
753+
"dictionary" to realmAnyDictionaryOf("dictkey1" to 1, "dictkey2" to "Realm", "dictkey3" to child, "dictkey4" to realmAnyListOf(1, 2, 3))
754+
)
755+
}
756+
)
757+
}
758+
realm.syncSession.uploadAllLocalChangesOrFail()
759+
}
760+
}
761+
createFlexibleSyncConfig(
762+
user = user2,
763+
initialSubscriptions = { realm ->
764+
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
765+
}
766+
).let { config ->
767+
Realm.open(config).use { realm ->
768+
realm.syncSession.downloadAllServerChanges(10.seconds)
769+
val flow = realm.query<JsonStyleRealmObject>("_id = $0", "PARENT").asFlow()
770+
val parent = withTimeout(10.seconds) {
771+
flow.first {
772+
it.list.size >= 1
773+
}.list[0]
774+
}
775+
parent.let {
776+
val value = it.value!!.asDictionary()
777+
assertEquals(RealmAny.Companion.create(1), value["primitive"])
778+
value["list"]!!.asList().let {
779+
assertEquals(1, it[0]!!.asInt())
780+
assertEquals("Realm", it[1]!!.asString())
781+
assertEquals("CHILD", it[2]!!.asRealmObject<JsonStyleRealmObject>().id)
782+
it[3]!!.asDictionary().let { dict ->
783+
assertEquals(1, dict["listkey1"]!!.asInt())
784+
assertEquals("Realm", dict["listkey2"]!!.asString())
785+
assertEquals("CHILD", dict["listkey3"]!!.asRealmObject<JsonStyleRealmObject>().id)
786+
}
787+
assertEquals("CHILD", it[2]!!.asRealmObject<JsonStyleRealmObject>().id)
788+
}
789+
value["dictionary"]!!.asDictionary().let {
790+
assertEquals(1, it["dictkey1"]!!.asInt())
791+
assertEquals("Realm", it["dictkey2"]!!.asString())
792+
assertEquals("CHILD", it["dictkey3"]!!.asRealmObject<JsonStyleRealmObject>().id)
793+
it["dictkey4"]!!.asList().let {
794+
assertEquals(realmAnyListOf(1, 2, 3).asList(), it)
795+
}
796+
}
797+
}
798+
}
799+
}
800+
}
801+
802+
@Test
803+
fun collectionsInMixed_asFlow() = runBlocking {
804+
val (email1, password1) = randomEmail() to "password1234"
805+
val (email2, password2) = randomEmail() to "password1234"
806+
val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX)
807+
val user1 = app.createUserAndLogIn(email1, password1)
808+
val user2 = app.createUserAndLogIn(email2, password2)
809+
810+
// Create object with all types
811+
val selector = ObjectId().toString()
812+
813+
val updateChannel = TestChannel<JsonStyleRealmObject>()
814+
815+
val configWriter = createFlexibleSyncConfig(
816+
user = user1,
817+
initialSubscriptions = { realm ->
818+
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
819+
}
820+
)
821+
val configReader = createFlexibleSyncConfig(
822+
user = user2,
823+
initialSubscriptions = { realm ->
824+
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
825+
},
826+
) {
827+
this.waitForInitialRemoteData(10.seconds)
828+
}
829+
Realm.open(configWriter).use { writerRealm ->
830+
val source = writerRealm.write {
831+
copyToRealm(
832+
JsonStyleRealmObject().apply {
833+
this.selector = selector
834+
value = realmAnyDictionaryOf(
835+
// List with nested dictionary
836+
"list" to realmAnyListOf(
837+
1,
838+
"Realm",
839+
realmAnyDictionaryOf("listkey1" to 1)
840+
),
841+
// Dictionary with nested list
842+
"dictionary" to realmAnyDictionaryOf(
843+
"dictkey1" to 1,
844+
"dictkey2" to "Realm",
845+
"dictkey3" to realmAnyListOf(1, 2, 3)
846+
)
847+
)
848+
}
849+
)
850+
}
851+
writerRealm.syncSession.uploadAllLocalChangesOrFail()
852+
853+
Realm.open(configReader).use { readerRealm ->
854+
val reader = readerRealm.query<JsonStyleRealmObject>("selector = $0", selector).find().single()
855+
val listener = async {
856+
reader.asFlow().collect {
857+
updateChannel.trySendOrFail(it.obj!!)
858+
}
859+
}
860+
// Flush initial event from channel
861+
updateChannel.receiveOrFail()
862+
863+
// List add
864+
writerRealm.write {
865+
findLatest(source)!!.run {
866+
value!!.asDictionary()["list"]!!.asList().add(RealmAny.Companion.create(6))
867+
}
868+
}
869+
updateChannel.receiveOrFail().run {
870+
val updatedList = value!!.asDictionary()["list"]!!.asList()
871+
assertEquals(4, updatedList.size)
872+
assertEquals(1, updatedList[0]!!.asInt())
873+
assertEquals("Realm", updatedList[1]!!.asString())
874+
assertIs<RealmDictionary<RealmAny>>(updatedList[2]!!.asDictionary())
875+
assertEquals(6, updatedList[3]!!.asInt())
876+
}
877+
878+
// List removal
879+
writerRealm.write {
880+
findLatest(source)!!.run {
881+
value!!.asDictionary()["list"]!!.asList().removeAt(1)
882+
}
883+
}
884+
updateChannel.receiveOrFail().run {
885+
val updatedList = value!!.asDictionary()["list"]!!.asList()
886+
assertEquals(3, updatedList.size)
887+
assertEquals(1, updatedList[0]!!.asInt())
888+
assertIs<RealmDictionary<RealmAny>>(updatedList[1]!!.asDictionary())
889+
assertEquals(6, updatedList[2]!!.asInt())
890+
}
891+
892+
// Dictionary add
893+
writerRealm.write {
894+
findLatest(source)!!.run {
895+
value!!.asDictionary()["dictionary"]!!.asDictionary()["dictkey4"] = RealmAny.Companion.create(6)
896+
}
897+
}
898+
updateChannel.receiveOrFail().run {
899+
val updatedDictionary = value!!.asDictionary()["dictionary"]!!.asDictionary()
900+
assertEquals(4, updatedDictionary.size)
901+
assertEquals(1, updatedDictionary["dictkey1"]!!.asInt())
902+
assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString())
903+
assertIs<RealmList<RealmAny>>(updatedDictionary["dictkey3"]!!.asList())
904+
assertEquals(6, updatedDictionary["dictkey4"]!!.asInt())
905+
}
906+
907+
// Dictionary removal
908+
writerRealm.write {
909+
findLatest(source)!!.run {
910+
value!!.asDictionary()["dictionary"]!!.asDictionary().remove("dictkey3")
911+
}
912+
}
913+
updateChannel.receiveOrFail().run {
914+
val updatedDictionary = value!!.asDictionary()["dictionary"]!!.asDictionary()
915+
assertEquals(3, updatedDictionary.size)
916+
assertEquals(1, updatedDictionary["dictkey1"]!!.asInt())
917+
assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString())
918+
assertEquals(6, updatedDictionary["dictkey4"]!!.asInt())
919+
}
920+
921+
listener.cancel()
922+
}
923+
}
924+
}
925+
710926
// After https://github.com/realm/realm-core/pull/5784 was merged, ObjectStore will now
711927
// return the full on-disk schema from ObjectStore, but for typed Realms the user visible schema
712928
// should still only return classes and properties that was defined by the user.

0 commit comments

Comments
 (0)