Skip to content

Commit

Permalink
Add collection in mixed sync roundtrip tests (#1783)
Browse files Browse the repository at this point in the history
  • Loading branch information
rorbech authored Jun 24, 2024
1 parent 71fbef9 commit f1c0935
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import io.realm.kotlin.types.RealmAny
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PersistedName
import io.realm.kotlin.types.annotations.PrimaryKey
import org.mongodb.kbson.ObjectId

class JsonStyleRealmObject(id: String) : RealmObject {
constructor() : this("JsonStyleRealmObject")
class JsonStyleRealmObject : RealmObject {
@PrimaryKey
@PersistedName("_id")
var id: String = id
var id: String = ObjectId().toHexString()
var selector: String = "DEFAULT"
var value: RealmAny? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.entities.JsonStyleRealmObject
import io.realm.kotlin.ext.asFlow
import io.realm.kotlin.ext.realmAnyDictionaryOf
import io.realm.kotlin.ext.realmAnyListOf
import io.realm.kotlin.internal.platform.runBlocking
import io.realm.kotlin.notifications.DeletedObject
Expand All @@ -34,6 +35,7 @@ import kotlinx.coroutines.channels.Channel
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
Expand Down Expand Up @@ -96,6 +98,61 @@ class RealmAnyNestedCollectionNotificationTest {
assertEquals(listOf(1, 4, 3), nestedList.map { it!!.asInt() })
}

// List operations
realm.write {
findLatest(o)!!.value = realmAnyListOf(1, 2, 3)
}
channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertContentEquals(realmAnyListOf(1, 2, 3).asList(), this.obj!!.value!!.asList())
}

// List add
realm.write {
findLatest(o)!!.value!!.asList().add(RealmAny.create("Realm"))
}
channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertContentEquals(realmAnyListOf(1, 2, 3, "Realm").asList(), this.obj!!.value!!.asList())
}

// List remove
realm.write {
findLatest(o)!!.value!!.asList().remove(RealmAny.create(2))
}
channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertContentEquals(realmAnyListOf(1, 3, "Realm").asList(), this.obj!!.value!!.asList())
}

// Dictionary operations
realm.write {
findLatest(o)!!.value = realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3)
}

channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertEquals(realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3).asDictionary(), this.obj!!.value!!.asDictionary())
}

realm.write {
findLatest(o)!!.value!!.asDictionary()["key4"] = RealmAny.create("Realm")
}

channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertEquals(realmAnyDictionaryOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to "Realm").asDictionary(), this.obj!!.value!!.asDictionary())
}

realm.write {
findLatest(o)!!.value!!.asDictionary().remove("key2")
}

channel.receiveOrFail().apply {
assertTrue { this is UpdatedObject<JsonStyleRealmObject> }
assertEquals(realmAnyDictionaryOf("key1" to 1, "key3" to 3, "key4" to "Realm").asDictionary(), this.obj!!.value!!.asDictionary())
}

realm.write {
delete(findLatest(o)!!)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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

import io.realm.kotlin.entities.JsonStyleRealmObject
import io.realm.kotlin.entities.Location
import io.realm.kotlin.entities.sync.BinaryObject
import io.realm.kotlin.entities.sync.COLLECTION_SCHEMAS
Expand Down Expand Up @@ -50,11 +51,12 @@ private val DEFAULT_SCHEMAS = setOf(
SyncPerson::class,
SyncRestaurant::class,
Location::class,
JsonStyleRealmObject::class,
)

val PARTITION_BASED_SCHEMA = DEFAULT_SCHEMAS
// Amount of schema classes that should be created on the server. EmbeddedRealmObjects and
// AsymmetricRealmObjects are not included in this count.
// Run FlexibleSyncSchemaTests.flexibleSyncSchemaCount for verification.
const val FLEXIBLE_SYNC_SCHEMA_COUNT = 14
const val FLEXIBLE_SYNC_SCHEMA_COUNT = 15
val FLEXIBLE_SYNC_SCHEMA = DEFAULT_SCHEMAS + ASYMMETRIC_SCHEMAS + COLLECTION_SCHEMAS
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ package io.realm.kotlin.test.mongodb.common
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.VersionId
import io.realm.kotlin.entities.JsonStyleRealmObject
import io.realm.kotlin.entities.sync.BinaryObject
import io.realm.kotlin.entities.sync.ChildPk
import io.realm.kotlin.entities.sync.ParentPk
import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes
import io.realm.kotlin.entities.sync.flx.FlexChildObject
import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject
import io.realm.kotlin.entities.sync.flx.FlexParentObject
import io.realm.kotlin.ext.asFlow
import io.realm.kotlin.ext.asRealmObject
import io.realm.kotlin.ext.query
import io.realm.kotlin.ext.realmAnyDictionaryOf
import io.realm.kotlin.ext.realmAnyListOf
import io.realm.kotlin.internal.interop.ErrorCode
import io.realm.kotlin.internal.interop.RealmInterop
import io.realm.kotlin.internal.platform.fileExists
Expand Down Expand Up @@ -56,6 +61,7 @@ import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.schema.RealmClass
import io.realm.kotlin.schema.RealmSchema
import io.realm.kotlin.schema.ValuePropertyType
import io.realm.kotlin.test.mongodb.TEST_APP_FLEX
import io.realm.kotlin.test.mongodb.TestApp
import io.realm.kotlin.test.mongodb.asTestApp
import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage
Expand All @@ -70,6 +76,9 @@ import io.realm.kotlin.test.util.receiveOrFail
import io.realm.kotlin.test.util.trySendOrFail
import io.realm.kotlin.test.util.use
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmAny
import io.realm.kotlin.types.RealmDictionary
import io.realm.kotlin.types.RealmList
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
Expand Down Expand Up @@ -707,6 +716,213 @@ class SyncedRealmTests {
}
}

@Test
fun roundtripCollectionsInMixed() = runBlocking {
val (email1, password1) = randomEmail() to "password1234"
val (email2, password2) = randomEmail() to "password1234"
val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX)
val user1 = app.createUserAndLogIn(email1, password1)
val user2 = app.createUserAndLogIn(email2, password2)

// Create object with all types
val selector = ObjectId().toString()

createFlexibleSyncConfig(
user = user1,
initialSubscriptions = { realm ->
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
}
).let { config ->
Realm.open(config).use { realm ->
realm.write {
val child = (
JsonStyleRealmObject().apply {
this.id = "CHILD"
this.selector = selector
}
)

copyToRealm(
JsonStyleRealmObject().apply {
this.id = "PARENT"
this.selector = selector
value = realmAnyDictionaryOf(
"primitive" to 1,
// List with nested dictionary
"list" to realmAnyListOf(1, "Realm", child, realmAnyDictionaryOf("listkey1" to 1, "listkey2" to "Realm", "listkey3" to child)),
"dictionary" to realmAnyDictionaryOf("dictkey1" to 1, "dictkey2" to "Realm", "dictkey3" to child, "dictkey4" to realmAnyListOf(1, 2, 3))
)
}
)
}
realm.syncSession.uploadAllLocalChangesOrFail()
}
}
createFlexibleSyncConfig(
user = user2,
initialSubscriptions = { realm ->
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
}
).let { config ->
Realm.open(config).use { realm ->
realm.syncSession.downloadAllServerChanges(10.seconds)
val flow = realm.query<JsonStyleRealmObject>("_id = $0", "PARENT").asFlow()
val parent = withTimeout(10.seconds) {
flow.first {
it.list.size >= 1
}.list[0]
}
parent.let {
val value = it.value!!.asDictionary()
assertEquals(RealmAny.Companion.create(1), value["primitive"])
value["list"]!!.asList().let {
assertEquals(1, it[0]!!.asInt())
assertEquals("Realm", it[1]!!.asString())
assertEquals("CHILD", it[2]!!.asRealmObject<JsonStyleRealmObject>().id)
it[3]!!.asDictionary().let { dict ->
assertEquals(1, dict["listkey1"]!!.asInt())
assertEquals("Realm", dict["listkey2"]!!.asString())
assertEquals("CHILD", dict["listkey3"]!!.asRealmObject<JsonStyleRealmObject>().id)
}
assertEquals("CHILD", it[2]!!.asRealmObject<JsonStyleRealmObject>().id)
}
value["dictionary"]!!.asDictionary().let {
assertEquals(1, it["dictkey1"]!!.asInt())
assertEquals("Realm", it["dictkey2"]!!.asString())
assertEquals("CHILD", it["dictkey3"]!!.asRealmObject<JsonStyleRealmObject>().id)
it["dictkey4"]!!.asList().let {
assertEquals(realmAnyListOf(1, 2, 3).asList(), it)
}
}
}
}
}
}

@Test
fun collectionsInMixed_asFlow() = runBlocking {
val (email1, password1) = randomEmail() to "password1234"
val (email2, password2) = randomEmail() to "password1234"
val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX)
val user1 = app.createUserAndLogIn(email1, password1)
val user2 = app.createUserAndLogIn(email2, password2)

// Create object with all types
val selector = ObjectId().toString()

val updateChannel = TestChannel<JsonStyleRealmObject>()

val configWriter = createFlexibleSyncConfig(
user = user1,
initialSubscriptions = { realm ->
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
}
)
val configReader = createFlexibleSyncConfig(
user = user2,
initialSubscriptions = { realm ->
realm.query<JsonStyleRealmObject>("selector = $0", selector).subscribe()
},
) {
this.waitForInitialRemoteData(10.seconds)
}
Realm.open(configWriter).use { writerRealm ->
val source = writerRealm.write {
copyToRealm(
JsonStyleRealmObject().apply {
this.selector = selector
value = realmAnyDictionaryOf(
// List with nested dictionary
"list" to realmAnyListOf(
1,
"Realm",
realmAnyDictionaryOf("listkey1" to 1)
),
// Dictionary with nested list
"dictionary" to realmAnyDictionaryOf(
"dictkey1" to 1,
"dictkey2" to "Realm",
"dictkey3" to realmAnyListOf(1, 2, 3)
)
)
}
)
}
writerRealm.syncSession.uploadAllLocalChangesOrFail()

Realm.open(configReader).use { readerRealm ->
val reader = readerRealm.query<JsonStyleRealmObject>("selector = $0", selector).find().single()
val listener = async {
reader.asFlow().collect {
updateChannel.trySendOrFail(it.obj!!)
}
}
// Flush initial event from channel
updateChannel.receiveOrFail()

// List add
writerRealm.write {
findLatest(source)!!.run {
value!!.asDictionary()["list"]!!.asList().add(RealmAny.Companion.create(6))
}
}
updateChannel.receiveOrFail().run {
val updatedList = value!!.asDictionary()["list"]!!.asList()
assertEquals(4, updatedList.size)
assertEquals(1, updatedList[0]!!.asInt())
assertEquals("Realm", updatedList[1]!!.asString())
assertIs<RealmDictionary<RealmAny>>(updatedList[2]!!.asDictionary())
assertEquals(6, updatedList[3]!!.asInt())
}

// List removal
writerRealm.write {
findLatest(source)!!.run {
value!!.asDictionary()["list"]!!.asList().removeAt(1)
}
}
updateChannel.receiveOrFail().run {
val updatedList = value!!.asDictionary()["list"]!!.asList()
assertEquals(3, updatedList.size)
assertEquals(1, updatedList[0]!!.asInt())
assertIs<RealmDictionary<RealmAny>>(updatedList[1]!!.asDictionary())
assertEquals(6, updatedList[2]!!.asInt())
}

// Dictionary add
writerRealm.write {
findLatest(source)!!.run {
value!!.asDictionary()["dictionary"]!!.asDictionary()["dictkey4"] = RealmAny.Companion.create(6)
}
}
updateChannel.receiveOrFail().run {
val updatedDictionary = value!!.asDictionary()["dictionary"]!!.asDictionary()
assertEquals(4, updatedDictionary.size)
assertEquals(1, updatedDictionary["dictkey1"]!!.asInt())
assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString())
assertIs<RealmList<RealmAny>>(updatedDictionary["dictkey3"]!!.asList())
assertEquals(6, updatedDictionary["dictkey4"]!!.asInt())
}

// Dictionary removal
writerRealm.write {
findLatest(source)!!.run {
value!!.asDictionary()["dictionary"]!!.asDictionary().remove("dictkey3")
}
}
updateChannel.receiveOrFail().run {
val updatedDictionary = value!!.asDictionary()["dictionary"]!!.asDictionary()
assertEquals(3, updatedDictionary.size)
assertEquals(1, updatedDictionary["dictkey1"]!!.asInt())
assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString())
assertEquals(6, updatedDictionary["dictkey4"]!!.asInt())
}

listener.cancel()
}
}
}

// After https://github.com/realm/realm-core/pull/5784 was merged, ObjectStore will now
// return the full on-disk schema from ObjectStore, but for typed Realms the user visible schema
// should still only return classes and properties that was defined by the user.
Expand Down

0 comments on commit f1c0935

Please sign in to comment.