From b53d16a4de16bc823a356b50585e27c0e23ad02f Mon Sep 17 00:00:00 2001 From: Clemente Date: Tue, 18 Jun 2024 09:56:15 +0200 Subject: [PATCH 1/7] Disable workaround --- .../io/realm/kotlin/test/mongodb/TestApp.kt | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt index 4af35452e4..ff92ad92bf 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.test.mongodb -import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.SynchronizableObject @@ -31,8 +30,6 @@ import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.util.AppAdmin import io.realm.kotlin.test.mongodb.util.AppAdminImpl import io.realm.kotlin.test.mongodb.util.AppServicesClient @@ -41,7 +38,6 @@ import io.realm.kotlin.test.mongodb.util.Service import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use import kotlinx.coroutines.CloseableCoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import org.mongodb.kbson.ExperimentalKBsonSerializerApi @@ -118,28 +114,6 @@ open class TestApp private constructor( ) ) - init { - // For apps with Flexible Sync, we need to bootstrap all the schemas to work around - // https://github.com/realm/realm-core/issues/7297. - // So we create a dummy Realm, upload all the schemas and close the Realm again. - if (app.configuration.appId.startsWith(TEST_APP_FLEX, ignoreCase = false)) { - runBlocking { - val user = app.login(Credentials.anonymous()) - val config = SyncConfiguration.create(user, FLEXIBLE_SYNC_SCHEMA) - try { - Realm.open(config).use { - // Using syncSession.uploadAllLocalChanges() seems to just hang forever. - // This is tracked by the above Core issue. Instead use the Sync Progress - // endpoint to signal when the schemas are ready. - pairAdminApp.second.waitForSyncBootstrap() - } - } finally { - user.delete() - } - } - } - } - fun createUserAndLogin(): User = runBlocking { val (email, password) = TestHelper.randomEmail() to "password1234" emailPasswordAuth.registerUser(email, password).run { From c086bdaf010c26d7632591382d693d525d9191b8 Mon Sep 17 00:00:00 2001 From: Clemente Date: Tue, 25 Jun 2024 12:23:00 +0200 Subject: [PATCH 2/7] Working --- buildSrc/src/main/kotlin/Config.kt | 4 +- .../test/mongodb/util/AppServicesClient.kt | 264 ++++++++++++++++-- .../test/mongodb/util/SchemaProcessor.kt | 208 ++++++++++++++ .../test/mongodb/util/TestAppInitializer.kt | 123 +++----- .../kotlin/test/mongodb/common/BaasTests.kt | 92 ++++++ .../common/PBSProgressListenerTests.kt | 5 + 6 files changed, 591 insertions(+), 105 deletions(-) create mode 100644 packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index d1d056e0a0..e771bc3a91 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -128,12 +128,12 @@ object Versions { const val latestKotlin = "2.0.0" // https://kotlinlang.org/docs/eap.html#build-details const val kotlinCompileTesting = "0.5.0-alpha07" // https://github.com/zacsweers/kotlin-compile-testing const val ktlint = "0.45.2" // https://github.com/pinterest/ktlint - const val ktor = "2.3.7" // https://github.com/ktorio/ktor + const val ktor = "2.3.12" // https://github.com/ktorio/ktor const val multidex = "2.0.1" // https://developer.android.com/jetpack/androidx/releases/multidex const val nexusPublishPlugin = "1.1.0" // https://github.com/gradle-nexus/publish-plugin const val okio = "3.2.0" // https://square.github.io/okio/#releases const val relinker = "1.4.5" // https://github.com/KeepSafe/ReLinker - const val serialization = "1.6.0" // https://kotlinlang.org/docs/releases.html#release-details + const val serialization = "1.7.0" // https://kotlinlang.org/docs/releases.html#release-details const val shadowJar = "6.1.0" // https://mvnrepository.com/artifact/com.github.johnrengelman.shadow/com.github.johnrengelman.shadow.gradle.plugin?repo=gradle-plugins const val snakeYaml = "1.33" // https://github.com/snakeyaml/snakeyaml val sourceCompatibilityVersion = JavaVersion.VERSION_1_8 // Language level of any Java source code. diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 366cb6dfaf..4d8e124ca4 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("invisible_member", "invisible_reference") package io.realm.kotlin.test.mongodb.util @@ -35,11 +36,15 @@ import io.ktor.http.HttpMethod.Companion.Post import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json +import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.schema.RealmClassImpl import io.realm.kotlin.mongodb.sync.SyncMode +import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.test.mongodb.SyncServerConfig import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA_COUNT +import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import kotlinx.serialization.InternalSerializationApi @@ -57,16 +62,20 @@ import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlinx.serialization.serializer +import kotlin.reflect.KClass private const val ADMIN_PATH = "/api/admin/v3.0" private const val PRIVATE_PATH = "/api/private/v1.0" +private val JsonDefaults: Json = Json { encodeDefaults = true } + data class SyncPermissions( val read: Boolean, val write: Boolean @@ -125,6 +134,160 @@ data class BaasApp( get() = client.baseUrl + PRIVATE_PATH + "/groups/${this.groupId}/apps/${this._id}" } +@Serializable +class SchemaRequest( + @Transient val database: String = "", + val schema: Schema, + val relationships: Map = emptyMap() +) { + private val metadata: SchemaMetadata = SchemaMetadata( + database = database, + collection = schema.title + ) +} + +@Serializable +class SchemaMetadata( + var database: String = "", + @SerialName("data_source") + var dataSource: String = "BackingDB", + var collection: String = "SyncDog", +) + +@Serializable +data class SchemaRelationship( + @Transient val target: String = "", + @Transient val database: String = "", + @SerialName("source_key") + val sourceKey: String, + @SerialName("foreign_key") + val foreignKey: String, + @SerialName("is_list") + val isList: Boolean +) { + val ref: String = "#/relationship/BackingDB/$database/$target" +} + +@Serializable +sealed interface SchemaPropertyType { + @Transient val isRequired: Boolean +} + +@Serializable +class ObjectReferenceType( + @Transient val sourceKey: String = "", + @Transient val targetKey: String = "", + @Transient val target: String = "", + @Transient val isList: Boolean = false, + val bsonType: PrimitivePropertyType.Type, +): SchemaPropertyType { + constructor(sourceKey: String, targetSchema: RealmClassImpl, isCollection: Boolean) : this( + sourceKey = sourceKey, + targetKey = targetSchema.cinteropClass.primaryKey, + target = targetSchema.name, + bsonType = targetSchema.cinteropProperties + .first { it.name == targetSchema.cinteropClass.primaryKey } + .type + .toSchemaType(), + isList = isCollection + ) + + @Transient override val isRequired: Boolean = false +} + +@Serializable +data class Schema( + var title: String = "", + var properties: Map = mutableMapOf(), + val required: List = mutableListOf(), + @Transient val kind: RealmClassKind = RealmClassKind.STANDARD, + val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, +): SchemaPropertyType { + @Transient override val isRequired: Boolean = false +} + +@Serializable +data class CollectionPropertyType( + val items: SchemaPropertyType, + val uniqueItems: Boolean = false +): SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.ARRAY + @Transient override val isRequired: Boolean = false +} + +@Serializable +data class MapPropertyType( + val additionalProperties: SchemaPropertyType, +): SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.OBJECT + @Transient override val isRequired: Boolean = false +} + +@Serializable +open class PrimitivePropertyType( + val bsonType: Type, + @Transient override val isRequired: Boolean = false, +) : SchemaPropertyType { + + enum class Type { + @SerialName("string") + STRING, + @SerialName("object") + OBJECT, + @SerialName("array") + ARRAY, + @SerialName("objectId") + OBJECT_ID, + @SerialName("boolean") + BOOLEAN, + @SerialName("bool") + BOOL, + @SerialName("null") + NULL, + @SerialName("regex") + REGEX, + @SerialName("date") + DATE, + @SerialName("timestamp") + TIMESTAMP, + @SerialName("int") + INT, + @SerialName("long") + LONG, + @SerialName("decimal") + DECIMAL, + @SerialName("double") + DOUBLE, + @SerialName("number") + NUMBER, + @SerialName("binData") + BIN_DATA, + @SerialName("uuid") + UUID, + @SerialName("mixed") + MIXED, + @SerialName("float") + FLOAT; + } +} + +fun PropertyType.toSchemaType() = + when (this) { + PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL + PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT + PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING + PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA + PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE + PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE + PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID + PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID + PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED + else -> throw IllegalArgumentException("Unsupported type") + } + /** * Client to interact with App Services Server. It allows to create Applications and tweak their * configurations. @@ -200,7 +363,21 @@ class AppServicesClient( } } - suspend fun BaasApp.addSchema(schema: String): JsonObject = + suspend fun BaasApp.updateSchema( + id: String, + schema: String, + ): HttpResponse = + withContext(dispatcher) { + httpClient.request( + "$url/schemas/$id"//?bypass_service_change=SyncSchemaVersionIncrease" + ) { + this.method = HttpMethod.Put + setBody(Json.parseToJsonElement(schema)) + contentType(ContentType.Application.Json) + } + } + + suspend fun BaasApp.addSchema(schema: String): String = withContext(dispatcher) { httpClient.typedRequest( Post, @@ -209,8 +386,71 @@ class AppServicesClient( setBody(Json.parseToJsonElement(schema)) contentType(ContentType.Application.Json) } + }.let { jsonObject: JsonObject -> + jsonObject["_id"]!!.jsonPrimitive.content + } + + suspend fun BaasApp.addRelationship(relationship: String): JsonObject = + withContext(dispatcher) { + httpClient.typedRequest( + Post, + "$url/relationships" + ) { + setBody(Json.parseToJsonElement(relationship)) + contentType(ContentType.Application.Json) + } } + suspend fun BaasApp.addSchema( + database: String, + classes: Set>, + block: SchemaRequest.()->Unit = {}, + ): Map { + val realmSchemas = classes.associate { clazz -> + val companion = clazz.realmObjectCompanionOrNull()!! + val realmSchema = companion.io_realm_kotlin_schema() + realmSchema.cinteropClass.name to realmSchema + } + + val processedSchemas: MutableMap = mutableMapOf() + + realmSchemas.entries.forEach { (name: String, realmSchema: RealmClassImpl) -> + val primaryKey = realmSchema.cinteropClass.name + + val properties = realmSchema.cinteropProperties.associate { + if (it.type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) + it.name to realmSchemas[it.linkTarget]!!.let { targetSchema -> + if (targetSchema.cinteropClass.isEmbedded) + null + else + targetSchema.cinteropProperties.find { it.name == targetSchema.primaryKey!!.name }!!.type + } + else + it.name to it.type + } + println(properties) + } + val schemas = mapOf, Schema>() + +// classes.forEach { clazz -> +// val companion = clazz.realmObjectCompanionOrNull()!! +// val realmSchema = companion.io_realm_kotlin_schema() +// +// val name = realmSchema.cinteropClass.name +// val primaryKey = realmSchema.cinteropClass.name +// val properties = realmSchema.cinteropProperties.associate { +// if (it.type.storageType == RealmStorageType.OBJECT){ +// it.name to it.type +// } +// else +// it.name to it.type +// } +// } + + return mapOf() + } + + suspend fun BaasApp.addService(service: String): Service = withContext(dispatcher) { httpClient.typedRequest( @@ -316,6 +556,8 @@ class AppServicesClient( setBody(Json.parseToJsonElement(config)) contentType(ContentType.Application.Json) } + }.also { it: HttpResponse -> + println(it) } suspend fun Service.addDefaultRule(rule: String): JsonObject = @@ -555,22 +797,8 @@ class AppServicesClient( Get, "$url/sync/progress" ).let { obj: JsonObject -> - val statuses: JsonElement = obj["progress"]!! - when (statuses) { - is JsonObject -> { - if (statuses.keys.isEmpty()) { - // It might take a few seconds to register the Schemas, so treat - // "empty" progress as initial sync not being complete (as we always - // have at least one pre-defined schema). - false - } - val bootstrapComplete: List = statuses.keys.map { schemaClass -> - statuses[schemaClass]!!.jsonObject["complete"]?.jsonPrimitive?.boolean == true - } - bootstrapComplete.all { it } && statuses.size == FLEXIBLE_SYNC_SCHEMA_COUNT - } - else -> false - } + println(obj) + obj["accepting_clients"]?.jsonPrimitive?.boolean ?: false } } catch (ex: IllegalStateException) { if (ex.message!!.contains("there are no mongodb/atlas services with provided sync state")) { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt new file mode 100644 index 0000000000..186bd7ae13 --- /dev/null +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -0,0 +1,208 @@ +@file:Suppress("invisible_member", "invisible_reference") + +package io.realm.kotlin.test.mongodb.util + +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.internal.interop.PropertyInfo +import io.realm.kotlin.internal.interop.PropertyType +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.schema.RealmClassImpl +import io.realm.kotlin.schema.RealmClassKind +import io.realm.kotlin.types.BaseRealmObject +//import kotlinx.serialization.json.ClassDiscriminatorMode +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.encodeToJsonElement +import kotlin.reflect.KClass + +// TODO REname methods and classes +class SchemaProcessor private constructor( + val classes: Set>, + val databaseName: String, + val extraProperties: Map, +) { + companion object { + private val json = Json { +// classDiscriminatorMode = ClassDiscriminatorMode.NONE + classDiscriminator = "" + prettyPrint = true + encodeDefaults = true + } + + fun process( + databaseName: String, + classes: Set>, + extraProperties: Map = emptyMap(), + ): Pair, Map> { + val processor = SchemaProcessor(classes, databaseName, extraProperties) + + val jsonSchemas: Map = processor.processedSchemas + .entries + .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } + .associate { (name, schema) -> + // add metadata + name to json.encodeToJsonElement(SchemaRequest(databaseName, schema)) + } + + val jsonSchemasWithRelationship = processor.processedSchemas + .entries + .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } + .associate { (name, schema) -> + // add metadata + name to json.encodeToJsonElement(SchemaRequest(databaseName, schema, processor.processedRelationships[name]!!)) + } + + return jsonSchemas to jsonSchemasWithRelationship + } + } + + val processedSchemas: MutableMap = mutableMapOf() + val processedRelationships: MutableMap> = mutableMapOf() + + val realmSchemas: Map = classes.associate { clazz -> + val companion = clazz.realmObjectCompanionOrNull()!! + val realmSchema = companion.io_realm_kotlin_schema() + realmSchema.cinteropClass.name to realmSchema + } + + init { + // TODO CHECK embedded CYCLES + generateSchemas() + generateRelationships() + } + + private fun generateRelationships() { + processedSchemas.values.forEach { schema -> + processedRelationships[schema.title] = analyze(schema.properties).associateBy { it.sourceKey } + } + } + + private fun analyze( + properties: Map, + path: String = "", + ): List { + return properties.entries + .filterNot { (_, value) -> + value is PrimitivePropertyType + } + .flatMap { (key, value: SchemaPropertyType) -> + value.toSchemaRelationships(key, path) + } + } + + private fun SchemaPropertyType.toSchemaRelationships( + key: String, + path: String = "", + ): List { + return when (this) { + is ObjectReferenceType -> toSchemaRelationships(path) + is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") + is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") + is Schema -> analyze(properties, "$path${key}.") + else -> emptyList() + } + } + + private fun ObjectReferenceType.toSchemaRelationships(path: String = "") = + listOf( + SchemaRelationship( + database = databaseName, + target = target, + sourceKey = "${path}${sourceKey}", + foreignKey = targetKey, + isList = isList + ) + ) + + private fun generateSchemas() { + realmSchemas.forEach { entry -> + if (entry.key !in processedSchemas) + entry.value.toSchema() + } + } + + private fun RealmClassImpl.toSchema() { + val name = cinteropClass.name + + val properties: Map = cinteropProperties + .filterNot { + it.isComputed + } + .associate { property: PropertyInfo -> + property.name to property.toSchemaProperty() + } + + when (kind) { + RealmClassKind.STANDARD -> extraProperties.entries + .associate { + it.key to PrimitivePropertyType( + bsonType = it.value, + isRequired = false, + ) + } + + RealmClassKind.EMBEDDED -> emptyMap() + RealmClassKind.ASYMMETRIC -> emptyMap() + } + + val required: List = properties.entries + .filter { (_, value) -> + value.isRequired + } + .map { (name, _) -> name } + + processedSchemas[name] = Schema( + title = name, + properties = properties, + required = required, + kind = kind + ) + } + + private fun PropertyInfo.toSchemaProperty(): SchemaPropertyType = + when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> propertyValueType() + CollectionType.RLM_COLLECTION_TYPE_LIST -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = false + ) + + CollectionType.RLM_COLLECTION_TYPE_SET -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = true + ) + + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> MapPropertyType( + additionalProperties = propertyValueType(isCollection = true) + ) + + else -> throw IllegalStateException("Unsupported $collectionType") + } + + private fun PropertyInfo.propertyValueType(isCollection: Boolean = false): SchemaPropertyType = + if (type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) + realmSchemas[linkTarget]!! + .let { targetSchema: RealmClassImpl -> + when (targetSchema.kind) { + RealmClassKind.STANDARD -> ObjectReferenceType( + name, + targetSchema, + isCollection + ) + + RealmClassKind.EMBEDDED -> getSchema(targetSchema.name) + RealmClassKind.ASYMMETRIC -> TODO() + } + } + else + PrimitivePropertyType( + bsonType = type.toSchemaType(), + isRequired = !isNullable + ) + + private fun getSchema(name: String): Schema { + if (name !in processedSchemas) + realmSchemas[name]!!.toSchema() + + return processedSchemas[name]!! + } +} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index b017e0539f..776aba056c 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -15,10 +15,21 @@ */ package io.realm.kotlin.test.mongodb.util +import io.ktor.client.call.body +import io.realm.kotlin.entities.sync.ChildPk +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.test.mongodb.TEST_APP_CLUSTER_NAME import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION +import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlin.random.Random interface AppInitializer { val name: String @@ -115,7 +126,7 @@ open class BaseAppInitializer( } object DefaultPartitionBasedAppInitializer : - BaseAppInitializer(TEST_APP_PARTITION, { initializePartitionSync(it) }) + BaseAppInitializer(TEST_APP_PARTITION + Random.nextInt(), { initializePartitionSync(it) }) object DefaultFlexibleSyncAppInitializer : BaseAppInitializer(TEST_APP_FLEX, { initializeFlexibleSync(it) }) @@ -146,13 +157,38 @@ suspend fun AppServicesClient.initializeFlexibleSync( @Suppress("LongMethod") suspend fun AppServicesClient.initializePartitionSync( app: BaasApp, - recoveryDisabled: Boolean = false, // TODO + recoveryDisabled: Boolean = false, ) { val databaseName = app.clientAppId app.addFunction(canReadPartition) app.addFunction(canWritePartition) + + val (schemas, relationships) = SchemaProcessor.process( + databaseName = databaseName, + classes = PARTITION_BASED_SCHEMA, + extraProperties = mapOf("realm_id" to PrimitivePropertyType.Type.STRING) + ) + + // add schemas + val ids: Map = schemas.entries + .associate { (name, schema) -> + name to app.addSchema(Json.encodeToString(schema)) + } + + relationships + .forEach { (name, schema) -> + println( + app.updateSchema( + id = ids[name]!!, + Json.encodeToString(schema) + ).let { + "${it.status} ${it.body()}" + } + ) + } + app.mongodbService.setSyncConfig( """ { @@ -190,89 +226,6 @@ suspend fun AppServicesClient.initializePartitionSync( } """.trimIndent() ) - - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncDog" - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "breed": { - "bsonType": "string" - }, - "name": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "name" - ], - "title": "SyncDog" - } - } - """.trimIndent() - ) - - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncPerson" - }, - "relationships": { - "dogs": { - "ref": "#/relationship/BackingDB/$databaseName/SyncDog", - "source_key": "dogs", - "foreign_key": "_id", - "is_list": true - } - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "age": { - "bsonType": "int" - }, - "dogs": { - "bsonType": "array", - "items": { - "bsonType": "objectId" - } - }, - "firstName": { - "bsonType": "string" - }, - "lastName": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "firstName", - "lastName", - "age" - ], - "title": "SyncPerson" - } - } - """.trimIndent() - ) } suspend fun AppServicesClient.addEmailProvider( diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt new file mode 100644 index 0000000000..faa9a0f3b5 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("invisible_member", "invisible_reference") + +package io.realm.kotlin.test.mongodb.common + +import io.realm.kotlin.entities.sync.ChildPk +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.realmDictionaryOf +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.test.mongodb.util.SchemaProcessor +import io.realm.kotlin.types.EmbeddedRealmObject +import io.realm.kotlin.types.RealmDictionary +import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.mongodb.kbson.BsonObjectId +import org.mongodb.kbson.ObjectId +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class RegularObject : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var name: String = "hello world1" + var dog: BsonObjectId = ObjectId() + var cycle: RegularObject? = null + var dogs: RealmList = realmListOf() + var dogMap: RealmDictionary = realmDictionaryOf() + var strings: RealmList = realmListOf() + var nullableStrings: RealmList = realmListOf() +} + +class Doggy: RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() +} + +class EmbeddedObject : EmbeddedRealmObject { + var nameEmb: String = "hello world2" +} + +class BaasTests { + + @BeforeTest + fun setup() { + } + + @AfterTest + fun tearDown() { + } + + @Test + fun testing() { + val classes = setOf(RegularObject::class, EmbeddedObject::class, Doggy::class) + + val (schemas, relationships) = SchemaProcessor.process("database", classes) + println(Json { prettyPrint = true }.encodeToString(schemas)) + } + + @Test + fun moreTesting() { + val classes = setOf( + ChildPk::class, + FlexChildObject::class, + FlexEmbeddedObject::class, + FlexParentObject::class, + ) + + val processor = SchemaProcessor.process("databaseName", classes) + } + + +} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt index 3b65a396e3..7c612e98f9 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt @@ -81,6 +81,11 @@ class PBSProgressListenerTests { } } + @Test + fun testing() { + + } + @Test @Ignore // https://github.com/realm/realm-core/issues/7627 fun downloadProgressListener_changesOnly() = runBlocking { From 13ad08128d73425f870177bae45e20b923bd77d1 Mon Sep 17 00:00:00 2001 From: Clemente Date: Tue, 25 Jun 2024 13:37:41 +0200 Subject: [PATCH 3/7] Some improvements --- .../test/mongodb/util/AppServicesClient.kt | 72 ++++++++++++++----- .../test/mongodb/util/SchemaProcessor.kt | 71 +++++++----------- .../test/mongodb/util/TestAppInitializer.kt | 54 ++++++-------- .../kotlin/test/mongodb/common/BaasTests.kt | 2 +- 4 files changed, 103 insertions(+), 96 deletions(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 4d8e124ca4..28d2617662 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -135,19 +135,32 @@ data class BaasApp( } @Serializable -class SchemaRequest( - @Transient val database: String = "", - val schema: Schema, - val relationships: Map = emptyMap() +data class Schema( + val metadata: SchemaMetadata = SchemaMetadata( + database = "database", + collection = "title" + ), + val schema: SchemaData, + val relationships: Map = emptyMap(), ) { - private val metadata: SchemaMetadata = SchemaMetadata( - database = database, - collection = schema.title - ) + companion object { + fun create( + database: String, + schema: SchemaData, + relationships: Map + ) = Schema( + metadata = SchemaMetadata( + database = database, + collection = schema.title + ), + schema = schema, + relationships = relationships + ) + } } @Serializable -class SchemaMetadata( +data class SchemaMetadata( var database: String = "", @SerialName("data_source") var dataSource: String = "BackingDB", @@ -196,7 +209,7 @@ class ObjectReferenceType( } @Serializable -data class Schema( +data class SchemaData( var title: String = "", var properties: Map = mutableMapOf(), val required: List = mutableListOf(), @@ -352,6 +365,31 @@ class AppServicesClient( } } + suspend fun BaasApp.setSchema( + schema: Set>, + extraProperties: Map + ) { + val schemas = SchemaProcessor.process( + databaseName = clientAppId, + classes = schema, + extraProperties = extraProperties + ) + + // First we create the schemas without the relationships + val ids: Map = schemas.entries + .associate { (name, schema: Schema) -> + name to addSchema(schema = schema.copy(relationships = emptyMap())) + } + + // then we update the schema to add the relationships + schemas.forEach { (name, schema) -> + updateSchema( + id = ids[name]!!, + schema = schema + ) + } + } + suspend fun BaasApp.addFunction(function: Function): Function = withContext(dispatcher) { httpClient.typedRequest( @@ -365,25 +403,25 @@ class AppServicesClient( suspend fun BaasApp.updateSchema( id: String, - schema: String, + schema: Schema, ): HttpResponse = withContext(dispatcher) { httpClient.request( "$url/schemas/$id"//?bypass_service_change=SyncSchemaVersionIncrease" ) { this.method = HttpMethod.Put - setBody(Json.parseToJsonElement(schema)) + setBody(json.encodeToJsonElement(schema)) contentType(ContentType.Application.Json) } } - suspend fun BaasApp.addSchema(schema: String): String = + suspend fun BaasApp.addSchema(schema: Schema): String = withContext(dispatcher) { httpClient.typedRequest( Post, "$url/schemas" ) { - setBody(Json.parseToJsonElement(schema)) + setBody(json.encodeToJsonElement(schema)) contentType(ContentType.Application.Json) } }.let { jsonObject: JsonObject -> @@ -404,7 +442,7 @@ class AppServicesClient( suspend fun BaasApp.addSchema( database: String, classes: Set>, - block: SchemaRequest.()->Unit = {}, + block: Schema.()->Unit = {}, ): Map { val realmSchemas = classes.associate { clazz -> val companion = clazz.realmObjectCompanionOrNull()!! @@ -412,7 +450,7 @@ class AppServicesClient( realmSchema.cinteropClass.name to realmSchema } - val processedSchemas: MutableMap = mutableMapOf() + val processedSchemas: MutableMap = mutableMapOf() realmSchemas.entries.forEach { (name: String, realmSchema: RealmClassImpl) -> val primaryKey = realmSchema.cinteropClass.name @@ -430,7 +468,7 @@ class AppServicesClient( } println(properties) } - val schemas = mapOf, Schema>() + val schemas = mapOf, SchemaData>() // classes.forEach { clazz -> // val companion = clazz.realmObjectCompanionOrNull()!! diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt index 186bd7ae13..202103193c 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -11,60 +11,42 @@ import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.types.BaseRealmObject //import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.encodeToJsonElement import kotlin.reflect.KClass // TODO REname methods and classes class SchemaProcessor private constructor( - val classes: Set>, - val databaseName: String, - val extraProperties: Map, + classes: Set>, + private val databaseName: String, + private val extraProperties: Map, ) { companion object { - private val json = Json { -// classDiscriminatorMode = ClassDiscriminatorMode.NONE - classDiscriminator = "" - prettyPrint = true - encodeDefaults = true - } fun process( databaseName: String, classes: Set>, extraProperties: Map = emptyMap(), - ): Pair, Map> { + ): Map { val processor = SchemaProcessor(classes, databaseName, extraProperties) - val jsonSchemas: Map = processor.processedSchemas - .entries - .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } - .associate { (name, schema) -> - // add metadata - name to json.encodeToJsonElement(SchemaRequest(databaseName, schema)) - } - - val jsonSchemasWithRelationship = processor.processedSchemas + return processor.processedSchemas .entries .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } .associate { (name, schema) -> // add metadata - name to json.encodeToJsonElement(SchemaRequest(databaseName, schema, processor.processedRelationships[name]!!)) + name to Schema.create(databaseName, schema, processor.processedRelationships[name]!!) } - - return jsonSchemas to jsonSchemasWithRelationship } } - val processedSchemas: MutableMap = mutableMapOf() - val processedRelationships: MutableMap> = mutableMapOf() - - val realmSchemas: Map = classes.associate { clazz -> + private val realmSchemas: Map = classes.associate { clazz -> val companion = clazz.realmObjectCompanionOrNull()!! val realmSchema = companion.io_realm_kotlin_schema() realmSchema.cinteropClass.name to realmSchema } + val processedSchemas: MutableMap = mutableMapOf() + val processedRelationships: MutableMap> = mutableMapOf() + init { // TODO CHECK embedded CYCLES generateSchemas() @@ -73,45 +55,42 @@ class SchemaProcessor private constructor( private fun generateRelationships() { processedSchemas.values.forEach { schema -> - processedRelationships[schema.title] = analyze(schema.properties).associateBy { it.sourceKey } + processedRelationships[schema.title] = findRelationships(schema.properties).associateBy { it.sourceKey } } } - private fun analyze( + private fun findRelationships( properties: Map, path: String = "", - ): List { - return properties.entries + ): List = + properties.entries .filterNot { (_, value) -> value is PrimitivePropertyType } .flatMap { (key, value: SchemaPropertyType) -> value.toSchemaRelationships(key, path) } - } private fun SchemaPropertyType.toSchemaRelationships( key: String, path: String = "", ): List { return when (this) { - is ObjectReferenceType -> toSchemaRelationships(path) + is ObjectReferenceType -> listOf(toSchemaRelationship(path)) is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") - is Schema -> analyze(properties, "$path${key}.") + is SchemaData -> findRelationships(properties, "$path${key}.") else -> emptyList() } } - private fun ObjectReferenceType.toSchemaRelationships(path: String = "") = - listOf( - SchemaRelationship( - database = databaseName, - target = target, - sourceKey = "${path}${sourceKey}", - foreignKey = targetKey, - isList = isList - ) + private fun ObjectReferenceType.toSchemaRelationship(path: String = "") = + SchemaRelationship( + database = databaseName, + target = target, + sourceKey = "${path}${sourceKey}", + foreignKey = targetKey, + isList = isList ) private fun generateSchemas() { @@ -150,7 +129,7 @@ class SchemaProcessor private constructor( } .map { (name, _) -> name } - processedSchemas[name] = Schema( + processedSchemas[name] = SchemaData( title = name, properties = properties, required = required, @@ -199,7 +178,7 @@ class SchemaProcessor private constructor( isRequired = !isNullable ) - private fun getSchema(name: String): Schema { + private fun getSchema(name: String): SchemaData { if (name !in processedSchemas) realmSchemas[name]!!.toSchema() diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 776aba056c..2548b8798e 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -15,20 +15,13 @@ */ package io.realm.kotlin.test.mongodb.util -import io.ktor.client.call.body -import io.realm.kotlin.entities.sync.ChildPk -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.test.mongodb.TEST_APP_CLUSTER_NAME import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION +import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA -import kotlinx.serialization.encodeToString +import kotlinx.coroutines.delay import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive import kotlin.random.Random interface AppInitializer { @@ -118,8 +111,12 @@ open class BaseAppInitializer( with(client) { block?.invoke(this, app) } - app.setDevelopmentMode(true) +// app.setDevelopmentMode(true) addEmailProvider(app) + + while (!app.initialSyncComplete()) { + delay(500) + } } } } @@ -137,6 +134,12 @@ suspend fun AppServicesClient.initializeFlexibleSync( recoveryDisabled: Boolean = false, // TODO ) { val databaseName = app.clientAppId + + app.setSchema( + schema = FLEXIBLE_SYNC_SCHEMA, + extraProperties = mapOf("realm_id" to PrimitivePropertyType.Type.STRING) + ) + app.mongodbService.setSyncConfig( """ { @@ -154,6 +157,13 @@ suspend fun AppServicesClient.initializeFlexibleSync( ) } + +val json = Json { +// classDiscriminatorMode = ClassDiscriminatorMode.NONE + classDiscriminator = "" + encodeDefaults = true +} + @Suppress("LongMethod") suspend fun AppServicesClient.initializePartitionSync( app: BaasApp, @@ -164,31 +174,11 @@ suspend fun AppServicesClient.initializePartitionSync( app.addFunction(canReadPartition) app.addFunction(canWritePartition) - - val (schemas, relationships) = SchemaProcessor.process( - databaseName = databaseName, - classes = PARTITION_BASED_SCHEMA, + app.setSchema( + schema = PARTITION_BASED_SCHEMA, extraProperties = mapOf("realm_id" to PrimitivePropertyType.Type.STRING) ) - // add schemas - val ids: Map = schemas.entries - .associate { (name, schema) -> - name to app.addSchema(Json.encodeToString(schema)) - } - - relationships - .forEach { (name, schema) -> - println( - app.updateSchema( - id = ids[name]!!, - Json.encodeToString(schema) - ).let { - "${it.status} ${it.body()}" - } - ) - } - app.mongodbService.setSyncConfig( """ { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt index faa9a0f3b5..7bff29c4db 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt @@ -72,7 +72,7 @@ class BaasTests { fun testing() { val classes = setOf(RegularObject::class, EmbeddedObject::class, Doggy::class) - val (schemas, relationships) = SchemaProcessor.process("database", classes) + val schemas = SchemaProcessor.process("database", classes) println(Json { prettyPrint = true }.encodeToString(schemas)) } From c779eb7a942cdb8941d1aef3783e47684c38903e Mon Sep 17 00:00:00 2001 From: Clemente Date: Tue, 25 Jun 2024 23:52:12 +0200 Subject: [PATCH 4/7] Passing --- .../test/mongodb/util/AppServicesClient.kt | 4 ++- .../test/mongodb/util/TestAppInitializer.kt | 25 +++++++++++-------- .../test/mongodb/common/FunctionsTests.kt | 2 ++ .../mongodb/common/HttpLogObfuscatorTests.kt | 3 +++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 28d2617662..eded316584 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -33,6 +33,7 @@ import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod.Companion.Get import io.ktor.http.HttpMethod.Companion.Post +import io.ktor.http.HttpMethod.Companion.Put import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json @@ -367,7 +368,7 @@ class AppServicesClient( suspend fun BaasApp.setSchema( schema: Set>, - extraProperties: Map + extraProperties: Map = emptyMap() ) { val schemas = SchemaProcessor.process( databaseName = clientAppId, @@ -919,6 +920,7 @@ class AppServicesClient( unauthorizedClient.close() val httpClient = defaultClient("realm-baas-authorized", debug) { + expectSuccess = true defaultRequest { headers { append("Authorization", "Bearer $accessToken") diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 2548b8798e..de6116ad35 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -112,7 +112,6 @@ open class BaseAppInitializer( block?.invoke(this, app) } // app.setDevelopmentMode(true) - addEmailProvider(app) while (!app.initialSyncComplete()) { delay(500) @@ -123,7 +122,7 @@ open class BaseAppInitializer( } object DefaultPartitionBasedAppInitializer : - BaseAppInitializer(TEST_APP_PARTITION + Random.nextInt(), { initializePartitionSync(it) }) + BaseAppInitializer(TEST_APP_PARTITION, { initializePartitionSync(it) }) object DefaultFlexibleSyncAppInitializer : BaseAppInitializer(TEST_APP_FLEX, { initializeFlexibleSync(it) }) @@ -131,14 +130,13 @@ object DefaultFlexibleSyncAppInitializer : @Suppress("LongMethod") suspend fun AppServicesClient.initializeFlexibleSync( app: BaasApp, - recoveryDisabled: Boolean = false, // TODO + recoveryDisabled: Boolean = false, ) { val databaseName = app.clientAppId - app.setSchema( - schema = FLEXIBLE_SYNC_SCHEMA, - extraProperties = mapOf("realm_id" to PrimitivePropertyType.Type.STRING) - ) + addEmailProvider(app) + + app.setSchema(FLEXIBLE_SYNC_SCHEMA) app.mongodbService.setSyncConfig( """ @@ -149,7 +147,13 @@ suspend fun AppServicesClient.initializeFlexibleSync( "is_recovery_mode_disabled": $recoveryDisabled, "queryable_fields_names": [ "name", - "section" + "section", + "stringField", + "location" + ], + "asymmetric_tables": [ + "AsymmetricA", + "Measurement" ] } } @@ -169,6 +173,8 @@ suspend fun AppServicesClient.initializePartitionSync( app: BaasApp, recoveryDisabled: Boolean = false, ) { + addEmailProvider(app) + val databaseName = app.clientAppId app.addFunction(canReadPartition) @@ -243,8 +249,7 @@ suspend fun AppServicesClient.addEmailProvider( "runResetFunction": false } } - """.trimIndent() - ) + """.trimIndent()) } private val insertDocument = Function( diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt index 70bfdc3ed6..c1f7b71748 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt @@ -47,6 +47,7 @@ import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION import io.realm.kotlin.test.mongodb.util.VOID_FUNCTION +import io.realm.kotlin.test.mongodb.util.addEmailProvider import io.realm.kotlin.test.util.TypeDescriptor import io.realm.kotlin.types.MutableRealmInt import io.realm.kotlin.types.RealmAny @@ -202,6 +203,7 @@ class FunctionsTests { object : BaseAppInitializer( syncServerAppName("funcs"), { + addEmailProvider(it) it.addFunction(FIRST_ARG_FUNCTION) it.addFunction(NULL_FUNCTION) it.addFunction(SUM_FUNCTION) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt index 4f9558acc5..b74a8e8d19 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt @@ -33,6 +33,7 @@ import io.realm.kotlin.test.mongodb.util.BaseAppInitializer import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION +import io.realm.kotlin.test.mongodb.util.addEmailProvider import io.realm.kotlin.test.util.receiveOrFail import kotlinx.coroutines.CancellationException import kotlinx.coroutines.async @@ -125,6 +126,7 @@ class HttpLogObfuscatorTests { return TestApp( this::class.simpleName, object : BaseAppInitializer(syncServerAppName("obfsctr"), { + addEmailProvider(it) it.addFunction(FIRST_ARG_FUNCTION) it.addFunction(SUM_FUNCTION) it.addFunction(NULL_FUNCTION) @@ -151,6 +153,7 @@ class HttpLogObfuscatorTests { object : BaseAppInitializer( syncServerAppName("null-obf"), { + addEmailProvider(it) it.addFunction(FIRST_ARG_FUNCTION) it.addFunction(SUM_FUNCTION) it.addFunction(NULL_FUNCTION) From aa00ccb136d5c502e8a58fbd853cce6ce233383b Mon Sep 17 00:00:00 2001 From: Clemente Date: Wed, 26 Jun 2024 11:46:33 +0200 Subject: [PATCH 5/7] Clean up --- .../test/mongodb/util/AppServicesClient.kt | 139 ++++++------------ .../test/mongodb/util/SchemaProcessor.kt | 38 ++--- .../test/mongodb/util/TestAppInitializer.kt | 9 +- .../kotlin/test/mongodb/common/BaasTests.kt | 92 ------------ .../common/PBSProgressListenerTests.kt | 5 - .../test/mongodb/common/SyncedRealmTests.kt | 8 + 6 files changed, 80 insertions(+), 211 deletions(-) delete mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index eded316584..68221e34af 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -33,13 +33,11 @@ import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod.Companion.Get import io.ktor.http.HttpMethod.Companion.Post -import io.ktor.http.HttpMethod.Companion.Put import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.realmObjectCompanionOrNull import io.realm.kotlin.internal.schema.RealmClassImpl import io.realm.kotlin.mongodb.sync.SyncMode import io.realm.kotlin.schema.RealmClassKind @@ -194,7 +192,7 @@ class ObjectReferenceType( @Transient val target: String = "", @Transient val isList: Boolean = false, val bsonType: PrimitivePropertyType.Type, -): SchemaPropertyType { +) : SchemaPropertyType { constructor(sourceKey: String, targetSchema: RealmClassImpl, isCollection: Boolean) : this( sourceKey = sourceKey, targetKey = targetSchema.cinteropClass.primaryKey, @@ -206,7 +204,8 @@ class ObjectReferenceType( isList = isCollection ) - @Transient override val isRequired: Boolean = false + @Transient + override val isRequired: Boolean = false } @Serializable @@ -215,26 +214,29 @@ data class SchemaData( var properties: Map = mutableMapOf(), val required: List = mutableListOf(), @Transient val kind: RealmClassKind = RealmClassKind.STANDARD, - val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, -): SchemaPropertyType { - @Transient override val isRequired: Boolean = false + val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, +) : SchemaPropertyType { + @Transient + override val isRequired: Boolean = false } @Serializable data class CollectionPropertyType( val items: SchemaPropertyType, - val uniqueItems: Boolean = false -): SchemaPropertyType { + val uniqueItems: Boolean = false, +) : SchemaPropertyType { val bsonType = PrimitivePropertyType.Type.ARRAY - @Transient override val isRequired: Boolean = false + @Transient + override val isRequired: Boolean = false } @Serializable data class MapPropertyType( val additionalProperties: SchemaPropertyType, -): SchemaPropertyType { +) : SchemaPropertyType { val bsonType = PrimitivePropertyType.Type.OBJECT - @Transient override val isRequired: Boolean = false + @Transient + override val isRequired: Boolean = false } @Serializable @@ -246,61 +248,79 @@ open class PrimitivePropertyType( enum class Type { @SerialName("string") STRING, + @SerialName("object") OBJECT, + @SerialName("array") ARRAY, + @SerialName("objectId") OBJECT_ID, + @SerialName("boolean") BOOLEAN, + @SerialName("bool") BOOL, + @SerialName("null") NULL, + @SerialName("regex") REGEX, + @SerialName("date") DATE, + @SerialName("timestamp") TIMESTAMP, + @SerialName("int") INT, + @SerialName("long") LONG, + @SerialName("decimal") DECIMAL, + @SerialName("double") DOUBLE, + @SerialName("number") NUMBER, + @SerialName("binData") BIN_DATA, + @SerialName("uuid") UUID, + @SerialName("mixed") MIXED, + @SerialName("float") FLOAT; } } fun PropertyType.toSchemaType() = - when (this) { - PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL - PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT - PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING - PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA - PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT - PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT - PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE - PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL - PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE - PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID - PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID - PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED - else -> throw IllegalArgumentException("Unsupported type") - } + when (this) { + PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL + PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT + PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING + PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA + PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE + PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE + PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID + PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID + PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED + else -> throw IllegalArgumentException("Unsupported type") + } /** * Client to interact with App Services Server. It allows to create Applications and tweak their @@ -408,7 +428,7 @@ class AppServicesClient( ): HttpResponse = withContext(dispatcher) { httpClient.request( - "$url/schemas/$id"//?bypass_service_change=SyncSchemaVersionIncrease" + "$url/schemas/$id" ) { this.method = HttpMethod.Put setBody(json.encodeToJsonElement(schema)) @@ -429,67 +449,6 @@ class AppServicesClient( jsonObject["_id"]!!.jsonPrimitive.content } - suspend fun BaasApp.addRelationship(relationship: String): JsonObject = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/relationships" - ) { - setBody(Json.parseToJsonElement(relationship)) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.addSchema( - database: String, - classes: Set>, - block: Schema.()->Unit = {}, - ): Map { - val realmSchemas = classes.associate { clazz -> - val companion = clazz.realmObjectCompanionOrNull()!! - val realmSchema = companion.io_realm_kotlin_schema() - realmSchema.cinteropClass.name to realmSchema - } - - val processedSchemas: MutableMap = mutableMapOf() - - realmSchemas.entries.forEach { (name: String, realmSchema: RealmClassImpl) -> - val primaryKey = realmSchema.cinteropClass.name - - val properties = realmSchema.cinteropProperties.associate { - if (it.type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) - it.name to realmSchemas[it.linkTarget]!!.let { targetSchema -> - if (targetSchema.cinteropClass.isEmbedded) - null - else - targetSchema.cinteropProperties.find { it.name == targetSchema.primaryKey!!.name }!!.type - } - else - it.name to it.type - } - println(properties) - } - val schemas = mapOf, SchemaData>() - -// classes.forEach { clazz -> -// val companion = clazz.realmObjectCompanionOrNull()!! -// val realmSchema = companion.io_realm_kotlin_schema() -// -// val name = realmSchema.cinteropClass.name -// val primaryKey = realmSchema.cinteropClass.name -// val properties = realmSchema.cinteropProperties.associate { -// if (it.type.storageType == RealmStorageType.OBJECT){ -// it.name to it.type -// } -// else -// it.name to it.type -// } -// } - - return mapOf() - } - - suspend fun BaasApp.addService(service: String): Service = withContext(dispatcher) { httpClient.typedRequest( @@ -595,8 +554,6 @@ class AppServicesClient( setBody(Json.parseToJsonElement(config)) contentType(ContentType.Application.Json) } - }.also { it: HttpResponse -> - println(it) } suspend fun Service.addDefaultRule(rule: String): JsonObject = diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt index 202103193c..4a1d5164fa 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -9,8 +9,6 @@ import io.realm.kotlin.internal.realmObjectCompanionOrNull import io.realm.kotlin.internal.schema.RealmClassImpl import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.types.BaseRealmObject -//import kotlinx.serialization.json.ClassDiscriminatorMode -import kotlinx.serialization.json.Json import kotlin.reflect.KClass // TODO REname methods and classes @@ -33,7 +31,11 @@ class SchemaProcessor private constructor( .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } .associate { (name, schema) -> // add metadata - name to Schema.create(databaseName, schema, processor.processedRelationships[name]!!) + name to Schema.create( + databaseName, + schema, + processor.processedRelationships[name]!! + ) } } } @@ -55,7 +57,8 @@ class SchemaProcessor private constructor( private fun generateRelationships() { processedSchemas.values.forEach { schema -> - processedRelationships[schema.title] = findRelationships(schema.properties).associateBy { it.sourceKey } + processedRelationships[schema.title] = + findRelationships(schema.properties).associateBy { it.sourceKey } } } @@ -79,7 +82,7 @@ class SchemaProcessor private constructor( is ObjectReferenceType -> listOf(toSchemaRelationship(path)) is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") - is SchemaData -> findRelationships(properties, "$path${key}.") + is SchemaData -> findRelationships(properties, "$path$key.") else -> emptyList() } } @@ -88,7 +91,7 @@ class SchemaProcessor private constructor( SchemaRelationship( database = databaseName, target = target, - sourceKey = "${path}${sourceKey}", + sourceKey = "$path$sourceKey", foreignKey = targetKey, isList = isList ) @@ -109,20 +112,19 @@ class SchemaProcessor private constructor( } .associate { property: PropertyInfo -> property.name to property.toSchemaProperty() - } + - when (kind) { - RealmClassKind.STANDARD -> extraProperties.entries - .associate { - it.key to PrimitivePropertyType( - bsonType = it.value, - isRequired = false, - ) - } - - RealmClassKind.EMBEDDED -> emptyMap() - RealmClassKind.ASYMMETRIC -> emptyMap() + } + when (kind) { + RealmClassKind.STANDARD -> + extraProperties.entries.associate { + it.key to PrimitivePropertyType( + bsonType = it.value, + isRequired = false, + ) } + RealmClassKind.EMBEDDED -> emptyMap() + RealmClassKind.ASYMMETRIC -> emptyMap() + } + val required: List = properties.entries .filter { (_, value) -> value.isRequired diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index de6116ad35..30a5c1a0a7 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -21,8 +21,8 @@ import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import kotlinx.coroutines.delay +import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json -import kotlin.random.Random interface AppInitializer { val name: String @@ -161,10 +161,8 @@ suspend fun AppServicesClient.initializeFlexibleSync( ) } - val json = Json { -// classDiscriminatorMode = ClassDiscriminatorMode.NONE - classDiscriminator = "" + classDiscriminatorMode = ClassDiscriminatorMode.NONE encodeDefaults = true } @@ -249,7 +247,8 @@ suspend fun AppServicesClient.addEmailProvider( "runResetFunction": false } } - """.trimIndent()) + """.trimIndent() + ) } private val insertDocument = Function( diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt deleted file mode 100644 index 7bff29c4db..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/BaasTests.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:Suppress("invisible_member", "invisible_reference") - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.entities.sync.ChildPk -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.realmDictionaryOf -import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.test.mongodb.util.SchemaProcessor -import io.realm.kotlin.types.EmbeddedRealmObject -import io.realm.kotlin.types.RealmDictionary -import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.ObjectId -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test - -class RegularObject : RealmObject { - @PrimaryKey - var _id: ObjectId = ObjectId() - var name: String = "hello world1" - var dog: BsonObjectId = ObjectId() - var cycle: RegularObject? = null - var dogs: RealmList = realmListOf() - var dogMap: RealmDictionary = realmDictionaryOf() - var strings: RealmList = realmListOf() - var nullableStrings: RealmList = realmListOf() -} - -class Doggy: RealmObject { - @PrimaryKey - var _id: ObjectId = ObjectId() -} - -class EmbeddedObject : EmbeddedRealmObject { - var nameEmb: String = "hello world2" -} - -class BaasTests { - - @BeforeTest - fun setup() { - } - - @AfterTest - fun tearDown() { - } - - @Test - fun testing() { - val classes = setOf(RegularObject::class, EmbeddedObject::class, Doggy::class) - - val schemas = SchemaProcessor.process("database", classes) - println(Json { prettyPrint = true }.encodeToString(schemas)) - } - - @Test - fun moreTesting() { - val classes = setOf( - ChildPk::class, - FlexChildObject::class, - FlexEmbeddedObject::class, - FlexParentObject::class, - ) - - val processor = SchemaProcessor.process("databaseName", classes) - } - - -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt index 7c612e98f9..3b65a396e3 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt @@ -81,11 +81,6 @@ class PBSProgressListenerTests { } } - @Test - fun testing() { - - } - @Test @Ignore // https://github.com/realm/realm-core/issues/7627 fun downloadProgressListener_changesOnly() = runBlocking { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 3796be8bd4..017a95c271 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -1363,10 +1363,18 @@ class SyncedRealmTests { // key of the objects from asset-pbs.realm will not be unique on secondary runs. @Test fun initialRealm_partitionBasedSync() { + // Delete any document from previous runs + with(app.asTestApp) { + runBlocking { + deleteDocuments(clientAppId, ParentPk::class.simpleName!!, "{}") + } + } + val (email, password) = randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) } + val config1 = createPartitionSyncConfig( user = user, partitionValue = partitionValue, name = "db1", errorHandler = object : SyncSession.ErrorHandler { From f83118fddfde1c3e6414d150e0f5ac525ac9d428 Mon Sep 17 00:00:00 2001 From: Clemente Date: Thu, 27 Jun 2024 10:30:42 +0200 Subject: [PATCH 6/7] Clean up --- .../test/mongodb/util/AppServicesClient.kt | 52 ++++++++++++------- .../test/mongodb/util/SchemaProcessor.kt | 2 +- .../test/mongodb/util/TestAppInitializer.kt | 9 +--- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 68221e34af..a754eb792b 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -46,11 +46,13 @@ import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement @@ -73,7 +75,11 @@ import kotlin.reflect.KClass private const val ADMIN_PATH = "/api/admin/v3.0" private const val PRIVATE_PATH = "/api/private/v1.0" -private val JsonDefaults: Json = Json { encodeDefaults = true } +@OptIn(ExperimentalSerializationApi::class) +private val json = Json { + classDiscriminatorMode = ClassDiscriminatorMode.NONE + encodeDefaults = true +} data class SyncPermissions( val read: Boolean, @@ -142,20 +148,18 @@ data class Schema( val schema: SchemaData, val relationships: Map = emptyMap(), ) { - companion object { - fun create( - database: String, - schema: SchemaData, - relationships: Map - ) = Schema( - metadata = SchemaMetadata( - database = database, - collection = schema.title - ), - schema = schema, - relationships = relationships - ) - } + constructor( + database: String, + schema: SchemaData, + relationships: Map, + ) : this( + metadata = SchemaMetadata( + database = database, + collection = schema.title + ), + schema = schema, + relationships = relationships + ) } @Serializable @@ -168,16 +172,26 @@ data class SchemaMetadata( @Serializable data class SchemaRelationship( - @Transient val target: String = "", - @Transient val database: String = "", @SerialName("source_key") val sourceKey: String, @SerialName("foreign_key") val foreignKey: String, @SerialName("is_list") - val isList: Boolean + val isList: Boolean, + val ref: String = "", ) { - val ref: String = "#/relationship/BackingDB/$database/$target" + constructor( + target: String, + database: String, + sourceKey: String, + foreignKey: String, + isList: Boolean, + ) : this( + sourceKey = sourceKey, + foreignKey = foreignKey, + isList = isList, + ref = "#/relationship/BackingDB/$database/$target" + ) } @Serializable diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt index 4a1d5164fa..1e8b9fc8ae 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -31,7 +31,7 @@ class SchemaProcessor private constructor( .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } .associate { (name, schema) -> // add metadata - name to Schema.create( + name to Schema( databaseName, schema, processor.processedRelationships[name]!! diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 538e63efe8..3064bf410c 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -21,7 +21,6 @@ import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import kotlinx.coroutines.delay -import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json interface AppInitializer { @@ -158,7 +157,8 @@ suspend fun AppServicesClient.initializeFlexibleSync( "name", "section", "stringField", - "location" + "location", + "selector" ], "asymmetric_tables": [ "AsymmetricA", @@ -170,11 +170,6 @@ suspend fun AppServicesClient.initializeFlexibleSync( ) } -val json = Json { - classDiscriminatorMode = ClassDiscriminatorMode.NONE - encodeDefaults = true -} - @Suppress("LongMethod") suspend fun AppServicesClient.initializePartitionSync( app: BaasApp, From ce040e7fe635c7dc2afd2942ef092579a7695e20 Mon Sep 17 00:00:00 2001 From: Clemente Date: Thu, 27 Jun 2024 15:21:30 +0200 Subject: [PATCH 7/7] Add missing dependency --- buildSrc/src/main/kotlin/Config.kt | 2 +- packages/test-sync/build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 7029d7c97e..749866c7c2 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -133,7 +133,7 @@ object Versions { const val nexusPublishPlugin = "1.1.0" // https://github.com/gradle-nexus/publish-plugin const val okio = "3.2.0" // https://square.github.io/okio/#releases const val relinker = "1.4.5" // https://github.com/KeepSafe/ReLinker - const val serialization = "1.7.0" // https://kotlinlang.org/docs/releases.html#release-details + const val serialization = "1.7.1" // https://kotlinlang.org/docs/releases.html#release-details const val shadowJar = "6.1.0" // https://mvnrepository.com/artifact/com.github.johnrengelman.shadow/com.github.johnrengelman.shadow.gradle.plugin?repo=gradle-plugins const val snakeYaml = "1.33" // https://github.com/snakeyaml/snakeyaml val sourceCompatibilityVersion = JavaVersion.VERSION_1_8 // Language level of any Java source code. diff --git a/packages/test-sync/build.gradle.kts b/packages/test-sync/build.gradle.kts index 8bf974864c..a374182607 100644 --- a/packages/test-sync/build.gradle.kts +++ b/packages/test-sync/build.gradle.kts @@ -101,6 +101,7 @@ kotlin { implementation("io.ktor:ktor-client-logging:${Versions.ktor}") implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") implementation("com.squareup.okio:okio:${Versions.okio}") }