From e00f393845f8d40b81ab3eb2db0311e65fa53ef8 Mon Sep 17 00:00:00 2001 From: Oguzhan Soykan Date: Wed, 27 Nov 2024 11:48:32 +0100 Subject: [PATCH] feat(ser/de): focus on creating an abstraction --- .../e2e/{TestSystemConfig.kt => Stove.kt} | 2 +- .../e2e/{TestSystemConfig.kt => Stove.kt} | 2 +- .../e2e/{TestSystemConfig.kt => Stove.kt} | 2 +- .../e2e/{TestSystemConfig.kt => Stove.kt} | 2 +- gradle/libs.versions.toml | 7 + .../testing/e2e/couchbase/CouchbaseSystem.kt | 51 +++-- .../stove/testing/e2e/couchbase/Options.kt | 7 +- .../e2e/elasticsearch/ElasticsearchSystem.kt | 3 +- .../testing/e2e/elasticsearch/Extensions.kt | 35 ++-- .../testing/e2e/elasticsearch/Options.kt | 7 +- .../ElasticsearchExposedCertificateTest.kt | 5 +- .../stove/testing/e2e/http/HttpSystem.kt | 16 +- .../stove/testing/e2e/http/HttpSystemTests.kt | 10 +- .../e2e/standalone/kafka/KafkaSystem.kt | 4 +- .../build.gradle.kts | 1 + .../testing/e2e/mongodb/MongodbOptionsDsl.kt | 5 - .../testing/e2e/mongodb/MongodbSystem.kt | 23 +-- .../e2e/mongodb/MongodbSystemOptions.kt | 17 +- .../stove/testing/e2e/mongodb/PojoRegistry.kt | 7 +- .../e2e/mongodb/MongodbTestSystemTests.kt | 57 +++--- .../stove/testing/e2e/wiremock/Options.kt | 9 +- .../testing/e2e/wiremock/WireMockSystem.kt | 44 ++--- .../stove/testing/e2e/wiremock/stubbing.kt | 8 +- .../{TestSystemConfig.kt => Stove.kt} | 2 +- lib/stove-testing-e2e/build.gradle.kts | 4 + .../serialization/E2eObjectMapperConfig.kt | 35 ---- .../serialization/IsoInstantDeserializer.kt | 46 ----- .../e2e/serialization/StoveObjectMapper.kt | 11 -- .../stove/testing/e2e/serialization/gson.kt | 31 +++ .../testing/e2e/serialization/jackson.kt | 99 ++++++++++ .../testing/e2e/serialization/kotlinx.kt | 47 +++++ .../e2e/serialization/serialization.kt | 42 +++++ .../e2e/system/abstractions/StateStorage.kt | 4 +- .../e2e/serialization/SerializationTests.kt | 177 ++++++++++++++++++ .../stove/testing/e2e/kafka/Options.kt | 4 +- .../testing/e2e/kafka/KafkaSystemTests.kt | 4 +- .../stove/testing/e2e/BridgeSystemTests.kt | 4 +- 37 files changed, 564 insertions(+), 270 deletions(-) rename examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/{TestSystemConfig.kt => Stove.kt} (97%) rename examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/{TestSystemConfig.kt => Stove.kt} (97%) rename examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/{TestSystemConfig.kt => Stove.kt} (98%) rename examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/{TestSystemConfig.kt => Stove.kt} (97%) rename lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/{TestSystemConfig.kt => Stove.kt} (93%) delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt create mode 100644 lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt diff --git a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/TestSystemConfig.kt b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt similarity index 97% rename from examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/TestSystemConfig.kt rename to examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt index 39914ebf..691e1b3f 100644 --- a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/TestSystemConfig.kt +++ b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt @@ -10,7 +10,7 @@ import io.kotest.core.extensions.Extension import io.kotest.extensions.system.SystemEnvironmentProjectListener import stove.ktor.example.app.objectMapperRef -class TestSystemConfig : AbstractProjectConfig() { +class Stove : AbstractProjectConfig() { companion object { init { stoveKafkaBridgePortDefault = "50053" diff --git a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt similarity index 97% rename from examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt rename to examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt index ab3fd474..b4a5ff43 100644 --- a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt +++ b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt @@ -9,7 +9,7 @@ import com.trendyol.stove.testing.e2e.wiremock.* import io.kotest.core.config.AbstractProjectConfig import org.slf4j.* -class TestSystemConfig : AbstractProjectConfig() { +class Stove : AbstractProjectConfig() { private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor") @Suppress("LongMethod") diff --git a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt similarity index 98% rename from examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt rename to examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt index 910da1ff..7507b0c9 100644 --- a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt +++ b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt @@ -10,7 +10,7 @@ import io.kotest.core.config.AbstractProjectConfig import org.slf4j.* import stove.spring.standalone.example.infrastructure.ObjectMapperConfig -class TestSystemConfig : AbstractProjectConfig() { +class Stove : AbstractProjectConfig() { private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor") @Suppress("LongMethod") diff --git a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt similarity index 97% rename from examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt rename to examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt index 7c23ff0e..a609e59c 100644 --- a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt +++ b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt @@ -10,7 +10,7 @@ import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.config.AbstractProjectConfig import org.apache.kafka.clients.admin.NewTopic -class TestSystemConfig : AbstractProjectConfig() { +class Stove : AbstractProjectConfig() { @Suppress("LongMethod") override suspend fun beforeProject(): Unit = TestSystem() .also { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e2d6c4c..8b6eb96f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,7 @@ kotlinx-slf4j = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-slf4j", ver kotlinx-knit = { module = "org.jetbrains.kotlinx:kotlinx-knit", version.ref = "knit" } kotlinx-io-reactor = { module = "io.projectreactor:reactor-core", version.ref = "io-reactor" } kotlinx-io-reactor-extensions = { module = "io.projectreactor.kotlin:reactor-kotlin-extensions", version.ref = "io-reactor-extensions" } +kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version = "1.7.3" } # Arrow arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } @@ -131,10 +132,13 @@ elastic = { module = "co.elastic.clients:elasticsearch-java", version.ref = "ela # mongo mongodb-kotlin-coroutine = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb" } +mongojack = { module = "org.mongojack:mongojack", version = "5.0.0" } # misc lettuce-core = { module = "io.lettuce:lettuce-core", version = "6.5.0.RELEASE" } logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.5.12" } +logback-core = { module = "ch.qos.logback:logback-core", version = "1.5.12" } +log4j-slf4j2-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version = "2.24.2" } r2dbc-mssql = { module = "io.r2dbc:r2dbc-mssql", version.ref = "r2dbc-mssql" } microsoft-sqlserver-jdbc = { module = "com.microsoft.sqlserver:mssql-jdbc", version = "12.8.1.jre11" } exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } @@ -186,6 +190,8 @@ google-protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", versi protoc = { module = "com.google.protobuf:protoc", version.ref = "google-protobuf" } hoplite = { module = "com.sksamuel.hoplite:hoplite-core", version.ref = "hoplite" } hoplite-yaml = { module = "com.sksamuel.hoplite:hoplite-yaml", version.ref = "hoplite" } +google-gson = { module = "com.google.code.gson:gson", version = "2.11.0" } + caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version = "3.1.8" } pprint = { module = "io.exoquery:pprint-kotlin", version = "2.0.2" } @@ -237,5 +243,6 @@ gradle-release = { id = "net.researchgate.release", version.ref = "gradle-releas nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPublish" } testLogger = { id = "com.adarshr.test-logger", version = "4.0.0" } protobuf = { id = "com.google.protobuf", version = "0.9.4" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem.kt b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem.kt index 553c5da8..950f152f 100644 --- a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem.kt +++ b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem.kt @@ -1,13 +1,12 @@ package com.trendyol.stove.testing.e2e.couchbase import com.couchbase.client.kotlin.* -import com.couchbase.client.kotlin.Collection -import com.couchbase.client.kotlin.codec.* +import com.couchbase.client.kotlin.codec.typeRef import com.couchbase.client.kotlin.query.* -import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.functional.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* +import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import org.slf4j.* @@ -20,10 +19,7 @@ class CouchbaseSystem internal constructor( internal lateinit var cluster: Cluster @PublishedApi - internal lateinit var collection: Collection - - @PublishedApi - internal val objectMapper: ObjectMapper = context.options.objectMapper + internal lateinit var collection: com.couchbase.client.kotlin.Collection private lateinit var exposedConfiguration: CouchbaseExposedConfiguration private val logger: Logger = LoggerFactory.getLogger(javaClass) @@ -55,21 +51,19 @@ class CouchbaseSystem internal constructor( override fun configuration(): List = context.options.configureExposedConfiguration(exposedConfiguration) @CouchbaseDsl - suspend inline fun shouldQuery( + suspend inline fun shouldQuery( query: String, - assertion: (List) -> Unit + crossinline assertion: (List) -> Unit ): CouchbaseSystem { - val result = cluster.query( - statement = query, - metrics = false, - consistency = QueryScanConsistency.requestPlus() - ).execute().rows.map { it.contentAs() } - val objects = result - .map { objectMapper.writeValueAsString(it) } - .map { objectMapper.readValue(it, T::class.java) } - - assertion(objects) - return this + val typeRef = typeRef() + return flow { + cluster.query( + statement = query, + metrics = false, + consistency = QueryScanConsistency.requestPlus(), + serializer = context.options.clusterSerDe + ).execute { row -> emit(context.options.clusterSerDe.deserialize(row.content, typeRef)) } + }.toList().also(assertion).let { this } } @CouchbaseDsl @@ -174,16 +168,13 @@ class CouchbaseSystem internal constructor( } } - private fun createCluster(exposedConfiguration: CouchbaseExposedConfiguration): Cluster { - val jackson = JacksonJsonSerializer(objectMapper) - return Cluster.connect( - exposedConfiguration.hostsWithPort, - exposedConfiguration.username, - exposedConfiguration.password - ) { - jsonSerializer = jackson - transcoder = JsonTranscoder(jackson) - } + private fun createCluster(exposedConfiguration: CouchbaseExposedConfiguration): Cluster = Cluster.connect( + exposedConfiguration.hostsWithPort, + exposedConfiguration.username, + exposedConfiguration.password + ) { + jsonSerializer = context.options.clusterSerDe + transcoder = context.options.clusterTranscoder } companion object { diff --git a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/Options.kt b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/Options.kt index eb261a7f..affe53f8 100644 --- a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/Options.kt +++ b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/Options.kt @@ -2,10 +2,10 @@ package com.trendyol.stove.testing.e2e.couchbase import arrow.core.getOrElse import com.couchbase.client.kotlin.Cluster -import com.fasterxml.jackson.databind.ObjectMapper +import com.couchbase.client.kotlin.codec.* import com.trendyol.stove.testing.e2e.containers.* import com.trendyol.stove.testing.e2e.database.migrations.* -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.E2eObjectMapperConfig import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -22,7 +22,8 @@ data class CouchbaseExposedConfiguration( data class CouchbaseSystemOptions( val defaultBucket: String, val containerOptions: CouchbaseContainerOptions = CouchbaseContainerOptions(), - val objectMapper: ObjectMapper = StoveObjectMapper.Default, + val clusterSerDe: JsonSerializer = JacksonJsonSerializer(E2eObjectMapperConfig.createObjectMapperWithDefaults()), + val clusterTranscoder: Transcoder = JsonTranscoder(clusterSerDe), override val configureExposedConfiguration: (CouchbaseExposedConfiguration) -> List ) : SystemOptions, ConfiguresExposedConfiguration { internal val migrationCollection: MigrationCollection = MigrationCollection() diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt index 7db81f24..3dda29e4 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt @@ -5,7 +5,6 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient import co.elastic.clients.elasticsearch._types.Refresh import co.elastic.clients.elasticsearch._types.query_dsl.Query import co.elastic.clients.elasticsearch.core.* -import co.elastic.clients.json.jackson.JacksonJsonpMapper import co.elastic.clients.transport.rest_client.RestClientTransport import com.trendyol.stove.functional.* import com.trendyol.stove.testing.e2e.system.TestSystem @@ -177,7 +176,7 @@ class ElasticsearchSystem internal constructor( private fun createEsClient(exposedConfiguration: ElasticSearchExposedConfiguration): ElasticsearchClient = context.options.clientConfigurer.restClientOverrideFn .getOrElse { { cfg -> restClient(cfg) } } - .let { RestClientTransport(it(exposedConfiguration), JacksonJsonpMapper(context.options.objectMapper)) } + .let { RestClientTransport(it(exposedConfiguration), context.options.jsonpMapper) } .let { ElasticsearchClient(it) } private fun restClient(cfg: ElasticSearchExposedConfiguration): RestClient = diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt index e6213f83..d7d4716b 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt @@ -1,7 +1,6 @@ package com.trendyol.stove.testing.e2e.elasticsearch import arrow.core.getOrElse -import arrow.integrations.jackson.module.registerArrowModule import com.trendyol.stove.testing.e2e.containers.withProvidedRegistry import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.SystemNotRegisteredException @@ -13,26 +12,22 @@ import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl * Provides an [options] class to configure the Elasticsearch container. * You can configure it by changing the implementation of migrator. */ -internal fun TestSystem.withElasticsearch(options: ElasticsearchSystemOptions): TestSystem { - options.objectMapper.registerArrowModule() - - return withProvidedRegistry( - imageName = options.container.imageWithTag, - registry = options.container.registry, - compatibleSubstitute = options.container.compatibleSubstitute - ) { StoveElasticSearchContainer(it) } - .apply { - addExposedPorts(*options.container.exposedPorts.toIntArray()) - withPassword(options.container.password) - if (options.container.disableSecurity) { - withEnv("xpack.security.enabled", "false") - } - withReuse(this@withElasticsearch.options.keepDependenciesRunning) - options.container.containerFn(this) +internal fun TestSystem.withElasticsearch(options: ElasticsearchSystemOptions): TestSystem = withProvidedRegistry( + imageName = options.container.imageWithTag, + registry = options.container.registry, + compatibleSubstitute = options.container.compatibleSubstitute +) { StoveElasticSearchContainer(it) } + .apply { + addExposedPorts(*options.container.exposedPorts.toIntArray()) + withPassword(options.container.password) + if (options.container.disableSecurity) { + withEnv("xpack.security.enabled", "false") } - .let { getOrRegister(ElasticsearchSystem(this, ElasticsearchContext(it, options))) } - .let { this } -} + withReuse(this@withElasticsearch.options.keepDependenciesRunning) + options.container.containerFn(this) + } + .let { getOrRegister(ElasticsearchSystem(this, ElasticsearchContext(it, options))) } + .let { this } internal fun TestSystem.elasticsearch(): ElasticsearchSystem = getOrNone().getOrElse { diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt index 9dc6eb18..e044650e 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt @@ -2,10 +2,11 @@ package com.trendyol.stove.testing.e2e.elasticsearch import arrow.core.* import co.elastic.clients.elasticsearch.ElasticsearchClient -import com.fasterxml.jackson.databind.ObjectMapper +import co.elastic.clients.json.JsonpMapper +import co.elastic.clients.json.jackson.JacksonJsonpMapper import com.trendyol.stove.testing.e2e.containers.* import com.trendyol.stove.testing.e2e.database.migrations.* -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl import org.apache.http.client.config.RequestConfig @@ -19,7 +20,7 @@ import kotlin.time.Duration.Companion.minutes data class ElasticsearchSystemOptions( val clientConfigurer: ElasticClientConfigurer = ElasticClientConfigurer(), val container: ElasticContainerOptions = ElasticContainerOptions(), - val objectMapper: ObjectMapper = StoveObjectMapper.Default, + val jsonpMapper: JsonpMapper = JacksonJsonpMapper(StoveSerde.jackson.default), override val configureExposedConfiguration: (ElasticSearchExposedConfiguration) -> List ) : SystemOptions, ConfiguresExposedConfiguration { internal val migrationCollection: MigrationCollection = MigrationCollection() diff --git a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt index 4d91d3a9..e4a36ad5 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt @@ -1,9 +1,8 @@ package com.trendyol.stove.testing.e2e.elasticsearch -import arrow.integrations.jackson.module.registerArrowModule import com.fasterxml.jackson.module.kotlin.readValue import com.trendyol.stove.functional.get -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.system.abstractions.StateWithProcess import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.* @@ -25,7 +24,7 @@ class ElasticsearchExposedCertificateTest : FunSpec({ "processId": 10496 } """.trimIndent() - val j = StoveObjectMapper.byConfiguring { this.registerArrowModule() } + val j = StoveSerde.jackson.default val stateWithProcess = j.readValue>(state) val serialize = j.writeValueAsString(stateWithProcess) val stateWithProcess2 = j.readValue>(serialize) diff --git a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystem.kt b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystem.kt index 6617d7a4..894f5c50 100644 --- a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystem.kt +++ b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystem.kt @@ -3,8 +3,7 @@ package com.trendyol.stove.testing.e2e.http import arrow.core.* -import com.fasterxml.jackson.databind.ObjectMapper -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -17,6 +16,7 @@ import io.ktor.client.plugins.logging.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.http.* +import io.ktor.serialization.* import io.ktor.serialization.jackson.* import io.ktor.util.* import org.slf4j.LoggerFactory @@ -29,14 +29,14 @@ private val httpSystemLogger = LoggerFactory.getLogger(HttpSystem::class.java) @HttpDsl data class HttpClientSystemOptions( val baseUrl: String, - val objectMapper: ObjectMapper = StoveObjectMapper.Default, + val contentConverter: ContentConverter = JacksonConverter(StoveSerde.jackson.default), val timeout: Duration = 30.seconds, - val createClient: () -> io.ktor.client.HttpClient = { jsonHttpClient(timeout, objectMapper) } + val createClient: () -> io.ktor.client.HttpClient = { jsonHttpClient(timeout, contentConverter) } ) : SystemOptions { companion object { internal fun jsonHttpClient( timeout: Duration, - objectMapper: ObjectMapper + converter: ContentConverter ): io.ktor.client.HttpClient = HttpClient(OkHttp) { engine { config { @@ -57,9 +57,9 @@ data class HttpClientSystemOptions( } install(ContentNegotiation) { - register(ContentType.Application.Json, JacksonConverter(objectMapper)) - register(ContentType.Application.ProblemJson, JacksonConverter(objectMapper)) - register(ContentType.parse("application/x-ndjson"), JacksonConverter(objectMapper)) + register(ContentType.Application.Json, converter) + register(ContentType.Application.ProblemJson, converter) + register(ContentType.parse("application/x-ndjson"), converter) } defaultRequest { diff --git a/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt b/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt index 1b48d566..01b288b9 100644 --- a/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt +++ b/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt @@ -6,7 +6,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.matching.MultipartValuePattern import com.trendyol.stove.ConsoleSpec import com.trendyol.stove.testing.e2e.http.HttpSystem.Companion.client -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.ApplicationUnderTest import com.trendyol.stove.testing.e2e.wiremock.* @@ -36,10 +35,7 @@ class TestConfig : AbstractProjectConfig() { .with { httpClient { HttpClientSystemOptions( - baseUrl = "http://localhost:8086", - objectMapper = StoveObjectMapper.byConfiguring { - findAndRegisterModules() - } + baseUrl = "http://localhost:8086" ) } @@ -343,7 +339,7 @@ class HttpSystemTests : FunSpec({ aResponse() .withHeader("Content-Type", "application/json") .withStatus(200) - .withBody(it.writeValueAsString(TestDto(expectedGetDtoName))) + .withBody(it.serialize(TestDto(expectedGetDtoName))) } } } @@ -369,7 +365,7 @@ class HttpSystemTests : FunSpec({ aResponse() .withHeader("Content-Type", "application/json") .withStatus(200) - .withBody(it.writeValueAsString(TestDto(UUID.randomUUID().toString()))) + .withBody(it.serialize(TestDto(UUID.randomUUID().toString()))) } } } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem.kt index 2ee3cc01..68d03a98 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem.kt @@ -4,7 +4,7 @@ import arrow.core.* import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.functional.* import com.trendyol.stove.testing.e2e.messaging.* -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.standalone.kafka.intercepting.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* @@ -24,7 +24,7 @@ import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -var stoveKafkaObjectMapperRef: ObjectMapper = StoveObjectMapper.Default +var stoveKafkaObjectMapperRef: ObjectMapper = StoveSerde.jackson.default var stoveKafkaBridgePortDefault = "50051" const val STOVE_KAFKA_BRIDGE_PORT = "STOVE_KAFKA_BRIDGE_PORT" internal val StoveKafkaCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) diff --git a/lib/stove-testing-e2e-mongodb/build.gradle.kts b/lib/stove-testing-e2e-mongodb/build.gradle.kts index 899d56dc..07832f61 100644 --- a/lib/stove-testing-e2e-mongodb/build.gradle.kts +++ b/lib/stove-testing-e2e-mongodb/build.gradle.kts @@ -1,6 +1,7 @@ dependencies { api(projects.lib.stoveTestingE2e) api(libs.testcontainers.mongodb) + api(libs.mongojack) implementation(libs.mongodb.kotlin.coroutine) implementation(libs.kotlinx.io.reactor.extensions) implementation(libs.kotlinx.reactive) diff --git a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt index 80406c42..b82a4ded 100644 --- a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt @@ -1,6 +1,5 @@ package com.trendyol.stove.testing.e2e.mongodb -import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @StoveDsl @@ -34,10 +33,6 @@ class MongodbOptionsDsl internal constructor(private val init: MongodbOptionsDsl options = options.copy(configureExposedConfiguration = configureExposedConfiguration) } - fun objectMapper(objectMapper: ObjectMapper) { - options = options.copy(objectMapper = objectMapper) - } - internal operator fun invoke(): MongodbSystemOptions { return init().let { options } } diff --git a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystem.kt b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystem.kt index f8672c47..c27eac13 100644 --- a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystem.kt +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystem.kt @@ -1,6 +1,5 @@ package com.trendyol.stove.testing.e2e.mongodb -import com.fasterxml.jackson.module.kotlin.convertValue import com.mongodb.* import com.mongodb.client.model.Filters.eq import com.mongodb.kotlin.client.coroutine.MongoClient @@ -11,10 +10,10 @@ import com.trendyol.stove.testing.e2e.system.abstractions.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import org.bson.* +import org.bson.codecs.EncoderContext import org.bson.conversions.Bson import org.bson.types.ObjectId import org.slf4j.* -import kotlin.collections.set @MongoDsl class MongodbSystem internal constructor( @@ -53,8 +52,8 @@ class MongodbSystem internal constructor( ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(T::class).build()) } .getCollection(collection) + .withDocumentClass(T::class.java) .find(BsonDocument.parse(query)) - .map { context.options.objectMapper.convertValue(it, T::class.java) } .toList() .also(assertion) .let { this } @@ -66,9 +65,9 @@ class MongodbSystem internal constructor( assertion: (T) -> Unit ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) .getCollection(collection) + .withDocumentClass() .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(T::class).build()) } .find(filterById(objectId)) - .map { context.options.objectMapper.convertValue(it, T::class.java) } .first() .also(assertion) .let { this } @@ -100,17 +99,19 @@ class MongodbSystem internal constructor( * Saves the [instance] with given [objectId] to the [collection] */ @MongoDsl - suspend fun save( + suspend inline fun save( instance: T, objectId: String = ObjectId().toHexString(), collection: String = context.options.databaseOptions.default.collection ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) - .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(instance::class).build()) } + .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(T::class).build()) } .getCollection(collection) - .let { - val map = context.options.objectMapper.convertValue>(instance) - map[RESERVED_ID] = ObjectId(objectId) - it.insertOne(Document(map)) + .also { coll -> + val bson = BsonDocument() + context.options.codecRegistry.get(T::class.java) + .encode(BsonDocumentWriter(bson), instance, EncoderContext.builder().build()) + val doc = Document(bson).append(RESERVED_ID, ObjectId(objectId)) + coll.insertOne(doc) } .let { this } @@ -150,7 +151,7 @@ class MongodbSystem internal constructor( .build().let { MongoClient.create(it) } companion object { - private const val RESERVED_ID = "_id" + const val RESERVED_ID = "_id" @PublishedApi internal fun filterById(key: String): Bson = eq(RESERVED_ID, ObjectId(key)) diff --git a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions.kt b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions.kt index 08fe83a1..d023e37d 100644 --- a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions.kt +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions.kt @@ -1,15 +1,22 @@ package com.trendyol.stove.testing.e2e.mongodb -import com.fasterxml.jackson.databind.ObjectMapper -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper -import com.trendyol.stove.testing.e2e.system.abstractions.ConfiguresExposedConfiguration -import com.trendyol.stove.testing.e2e.system.abstractions.SystemOptions +import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import org.bson.UuidRepresentation +import org.bson.codecs.configuration.CodecRegistry +import org.bson.codecs.pojo.PojoCodecProvider +import org.bson.internal.ProvidersCodecRegistry +import org.mongojack.JacksonCodecRegistry @StoveDsl data class MongodbSystemOptions( val databaseOptions: DatabaseOptions = DatabaseOptions(), val container: MongoContainerOptions = MongoContainerOptions(), override val configureExposedConfiguration: (MongodbExposedConfiguration) -> List, - val objectMapper: ObjectMapper = StoveObjectMapper.byConfiguring { registerModule(ObjectIdModule()) } + val codecRegistry: CodecRegistry = JacksonCodecRegistry( + StoveSerde.jackson.byConfiguring { addModule(ObjectIdModule()) }, + ProvidersCodecRegistry(listOf(PojoCodecProvider.builder().automatic(true).build())), + UuidRepresentation.STANDARD + ) ) : SystemOptions, ConfiguresExposedConfiguration diff --git a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt index 78af08e4..97e36a73 100644 --- a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt @@ -11,12 +11,7 @@ import kotlin.reflect.KClass data class PojoRegistry( val registry: CodecRegistry = fromRegistries() ) { - private var builder: PojoCodecProvider.Builder = - PojoCodecProvider.builder().conventions( - Conventions.DEFAULT_CONVENTIONS - ) - - inline fun register(): PojoRegistry = register(T::class) + private var builder: PojoCodecProvider.Builder = PojoCodecProvider.builder().conventions(Conventions.DEFAULT_CONVENTIONS) fun register(clazz: KClass): PojoRegistry = builder.register(clazz.java).let { this } diff --git a/lib/stove-testing-e2e-mongodb/src/test/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbTestSystemTests.kt b/lib/stove-testing-e2e-mongodb/src/test/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbTestSystemTests.kt index 180db471..2aaa237b 100644 --- a/lib/stove-testing-e2e-mongodb/src/test/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbTestSystemTests.kt +++ b/lib/stove-testing-e2e-mongodb/src/test/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbTestSystemTests.kt @@ -51,31 +51,24 @@ class NoOpApplication : ApplicationUnderTest { } class MongodbTestSystemTests : FunSpec({ - - data class ExampleInstanceWithObjectId - @BsonCreator - constructor( - @BsonId - @JsonAlias("_id") - val id: ObjectId, - @BsonProperty("aggregateId") val aggregateId: String, - @BsonProperty("description") val description: String - ) - - data class ExampleInstanceWithStringObjectId - @BsonCreator - constructor( - @BsonId - @JsonAlias("_id") - val id: String, - @BsonProperty("aggregateId") val aggregateId: String, - @BsonProperty("description") val description: String - ) + data class ExampleInstanceWithObjectId( + @BsonId + @JsonAlias("_id") + val id: ObjectId, + @BsonProperty("aggregateId") val aggregateId: String, + @BsonProperty("description") val description: String + ) + + data class ExampleInstanceWithStringObjectId( + @JsonAlias("_id") + val id: String, + @BsonProperty("aggregateId") val aggregateId: String, + @BsonProperty("description") val description: String + ) test("should save and get with objectId") { - val id = ObjectId() - TestSystem.validate { + validate { mongodb { save( ExampleInstanceWithObjectId( @@ -95,7 +88,7 @@ class MongodbTestSystemTests : FunSpec({ test("should save and get with string objectId") { val id = ObjectId() - TestSystem.validate { + validate { mongodb { save( ExampleInstanceWithStringObjectId( @@ -113,13 +106,17 @@ class MongodbTestSystemTests : FunSpec({ } } + data class ExampleInstanceWithObjectIdForQuery( + val id: String, + val description: String + ) test("Get with query should work") { val id1 = ObjectId() val id2 = ObjectId() val id3 = ObjectId() val firstDesc = "same description" val secondDesc = "different description" - TestSystem.validate { + validate { mongodb { save( ExampleInstanceWithObjectId( @@ -145,10 +142,10 @@ class MongodbTestSystemTests : FunSpec({ ), id3.toHexString() ) - shouldQuery("{\"description\": \"$secondDesc\"}") { actual -> + shouldQuery("{\"description\": \"$secondDesc\"}") { actual -> actual.count() shouldBe 2 - actual.forAny { it.id shouldBe id2 } - actual.forAny { it.id shouldBe id3 } + actual.forAny { it.id shouldBe id2.toHexString() } + actual.forAny { it.id shouldBe id3.toHexString() } } shouldQuery("{\"description\": \"$firstDesc\"}") { actual -> actual.count() shouldBe 1 @@ -160,7 +157,7 @@ class MongodbTestSystemTests : FunSpec({ test("should throw assertion error when document does exist") { val id1 = ObjectId() - TestSystem.validate { + validate { mongodb { save( ExampleInstanceWithObjectId( @@ -180,7 +177,7 @@ class MongodbTestSystemTests : FunSpec({ test("should not throw exception when given does not exist id") { val notExistDocId = ObjectId() - TestSystem.validate { + validate { mongodb { shouldNotExist(notExistDocId.toHexString()) } @@ -189,7 +186,7 @@ class MongodbTestSystemTests : FunSpec({ test("should delete") { val id = ObjectId() - TestSystem.validate { + validate { mongodb { save( ExampleInstanceWithObjectId( diff --git a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/Options.kt b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/Options.kt index e0a5a1a2..9b5661c5 100644 --- a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/Options.kt +++ b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/Options.kt @@ -1,10 +1,9 @@ package com.trendyol.stove.testing.e2e.wiremock import arrow.core.getOrElse -import com.fasterxml.jackson.databind.ObjectMapper import com.github.tomakehurst.wiremock.common.ConsoleNotifier import com.github.tomakehurst.wiremock.core.WireMockConfiguration -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -34,7 +33,7 @@ data class WireMockSystemOptions( /** * ObjectMapper for serialization/deserialization */ - val objectMapper: ObjectMapper = StoveObjectMapper.Default + val serde: StoveSerde = StoveSerde.jackson.anyByteArraySerde() ) : SystemOptions data class WireMockContext( @@ -42,7 +41,7 @@ data class WireMockContext( val removeStubAfterRequestMatched: Boolean, val afterStubRemoved: AfterStubRemoved, val afterRequest: AfterRequestHandler, - val objectMapper: ObjectMapper, + val serde: StoveSerde, val configure: WireMockConfiguration.() -> WireMockConfiguration ) @@ -54,7 +53,7 @@ internal fun TestSystem.withWireMock(options: WireMockSystemOptions = WireMockSy options.removeStubAfterRequestMatched, options.afterStubRemoved, options.afterRequest, - options.objectMapper, + options.serde, options.configure ) ).also { getOrRegister(it) } diff --git a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt index f19397a7..272044b3 100644 --- a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt +++ b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt @@ -1,7 +1,8 @@ +@file:Suppress("unused") + package com.trendyol.stove.testing.e2e.wiremock import arrow.core.* -import com.fasterxml.jackson.databind.ObjectMapper import com.github.benmanes.caffeine.cache.* import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.* @@ -11,6 +12,7 @@ import com.github.tomakehurst.wiremock.extension.Extension import com.github.tomakehurst.wiremock.matching.ContainsPattern import com.github.tomakehurst.wiremock.stubbing.* import com.trendyol.stove.functional.* +import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* import kotlinx.coroutines.runBlocking @@ -27,7 +29,7 @@ class WireMockSystem( ) : PluggedSystem, ValidatedSystem, RunAware { private val stubLog: Cache = Caffeine.newBuilder().build() private var wireMock: WireMockServer - private val json: ObjectMapper = ctx.objectMapper + private val serde: StoveSerde = ctx.serde private val logger: Logger = LoggerFactory.getLogger(javaClass) init { @@ -88,7 +90,7 @@ class WireMockSystem( aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { res.withBody(json.writeValueAsBytes(it)) } + responseBody.map { res.withBody(serde.serialize(it)) } val req = put(urlEqualTo(url)) configureBodyAndMetadata(req, metadata, requestBody) val stub = wireMock.stubFor(req.willReturn(res).withId(UUID.randomUUID())) @@ -108,7 +110,7 @@ class WireMockSystem( aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { res.withBody(json.writeValueAsBytes(it)) } + responseBody.map { res.withBody(serde.serialize(it)) } val req = patch(urlEqualTo(url)) configureBodyAndMetadata(req, metadata, requestBody) val stub = wireMock.stubFor(req.willReturn(res).withId(UUID.randomUUID())) @@ -149,10 +151,10 @@ class WireMockSystem( @WiremockDsl fun mockPutConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = put(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -160,10 +162,10 @@ class WireMockSystem( @WiremockDsl fun mockPatchConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = patch(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -171,10 +173,10 @@ class WireMockSystem( @WiremockDsl fun mockGetConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = get(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -182,10 +184,10 @@ class WireMockSystem( @WiremockDsl fun mockHeadConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = head(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -193,10 +195,10 @@ class WireMockSystem( @WiremockDsl fun mockDeleteConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = delete(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -204,10 +206,10 @@ class WireMockSystem( @WiremockDsl fun mockPostConfigure( url: String, - configure: (MappingBuilder, ObjectMapper) -> MappingBuilder + configure: (MappingBuilder, StoveSerde) -> MappingBuilder ): WireMockSystem { val req = post(urlEqualTo(url)) - val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) + val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -216,9 +218,9 @@ class WireMockSystem( fun behaviourFor( url: String, method: (String) -> MappingBuilder, - block: StubBehaviourBuilder.(ObjectMapper) -> Unit + block: StubBehaviourBuilder.(StoveSerde) -> Unit ) { - stubBehaviour(wireMock, objectMapper = json, url, method, block) + stubBehaviour(wireMock, serde = serde, url, method, block) } @WiremockDsl @@ -240,7 +242,7 @@ class WireMockSystem( ValidationResult( "${it.method.value()} ${it.url}", it.bodyAsString, - json.writeValueAsString(it.queryParams) + serde.serialize(it.queryParams).decodeToString() ).toString() } throw AssertionError( @@ -264,7 +266,7 @@ class WireMockSystem( body.map { request.withRequestBody( equalToJson( - json.writeValueAsString(it), + serde.serialize(it).decodeToString(), true, false ) @@ -279,7 +281,7 @@ class WireMockSystem( val mockResponse = aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { mockResponse.withBody(json.writeValueAsBytes(it)) } + responseBody.map { mockResponse.withBody(serde.serialize(it)) } return mockResponse } diff --git a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/stubbing.kt b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/stubbing.kt index 2d843112..f7e18e46 100644 --- a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/stubbing.kt +++ b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/stubbing.kt @@ -1,19 +1,19 @@ package com.trendyol.stove.testing.e2e.wiremock -import com.fasterxml.jackson.databind.ObjectMapper import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.* import com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED +import com.trendyol.stove.testing.e2e.serialization.StoveSerde internal fun stubBehaviour( wireMockServer: WireMockServer, - objectMapper: ObjectMapper, + serde: StoveSerde, url: String, method: (String) -> MappingBuilder, - block: StubBehaviourBuilder.(ObjectMapper) -> Unit + block: StubBehaviourBuilder.(StoveSerde) -> Unit ) { val builder = StubBehaviourBuilder(wireMockServer, url, method) - builder.block(objectMapper) + builder.block(serde) } class StubBehaviourBuilder( diff --git a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.kt b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt similarity index 93% rename from lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.kt rename to lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt index b568330f..8cb263d5 100644 --- a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.kt +++ b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt @@ -4,7 +4,7 @@ import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.ApplicationUnderTest import io.kotest.core.config.AbstractProjectConfig -class TestSystemConfig : AbstractProjectConfig() { +class Stove : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { diff --git a/lib/stove-testing-e2e/build.gradle.kts b/lib/stove-testing-e2e/build.gradle.kts index 2fb9a37f..479e391f 100644 --- a/lib/stove-testing-e2e/build.gradle.kts +++ b/lib/stove-testing-e2e/build.gradle.kts @@ -1,10 +1,14 @@ plugins { `java-test-fixtures` + alias(libs.plugins.kotlinx.serialization) } dependencies { api(libs.kotlinx.core) api(libs.jackson.kotlin) + api(libs.jackson.arrow) + api(libs.google.gson) + api(libs.kotlinx.serialization) api(libs.testcontainers) { version { require(libs.testcontainers.asProvider().get().version!!) diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt deleted file mode 100644 index ab3ff4c0..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.json.JsonMapper -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.module.kotlin.registerKotlinModule -import java.time.Instant - -/** - * This class is used to create an object mapper with default configurations. - * This object mapper is used to serialize and deserialize request and response bodies. - */ -object E2eObjectMapperConfig { - /** - * Creates an object mapper with default configurations. - * This object mapper is used to serialize and deserialize request and response bodies. - */ - fun createObjectMapperWithDefaults(): ObjectMapper { - val isoInstantModule = - SimpleModule() - .addSerializer(Instant::class.java, IsoInstantSerializer()) - .addDeserializer(Instant::class.java, IsoInstantDeserializer()) - - return JsonMapper.builder() - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .serializationInclusion(JsonInclude.Include.NON_NULL) - .build() - .registerKotlinModule() - .registerModule(isoInstantModule) - } -} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt deleted file mode 100644 index e793dba0..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.trendyol.stove.functional.Try -import com.trendyol.stove.functional.recover -import java.io.IOException -import java.time.Instant -import java.time.format.DateTimeFormatter -import java.time.temporal.TemporalAccessor - -/** - * Instant serializer deserializer for jackson - */ -class IsoInstantDeserializer : JsonDeserializer() { - override fun deserialize( - parser: JsonParser, - context: DeserializationContext - ): Instant { - val string: String = parser.text.trim() - return Try { - DateTimeFormatter.ISO_INSTANT.parse(string) { temporal: TemporalAccessor -> - Instant.from(temporal) - } as Instant - }.recover { Instant.ofEpochSecond(string.toLong()) }.get() - } -} - -/** - * Instant serializer for jackson - */ -class IsoInstantSerializer : JsonSerializer() { - @Throws(IOException::class, JsonProcessingException::class) - override fun serialize( - value: Instant, - gen: JsonGenerator, - serializers: SerializerProvider? - ) { - gen.writeString(value.toString()) - } -} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt deleted file mode 100644 index a6d7cdf2..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper - -object StoveObjectMapper { - val Default: ObjectMapper = jacksonObjectMapper().disable(FAIL_ON_EMPTY_BEANS) - - fun byConfiguring(configurer: ObjectMapper.() -> ObjectMapper): ObjectMapper = configurer(Default) -} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt new file mode 100644 index 00000000..325138e9 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt @@ -0,0 +1,31 @@ +package com.trendyol.stove.testing.e2e.serialization + +import com.google.gson.Gson + +object StoveGson { + val default: Gson = com.google.gson.GsonBuilder().create() + + fun byConfiguring( + configurer: com.google.gson.GsonBuilder.() -> com.google.gson.GsonBuilder + ): Gson = configurer(com.google.gson.GsonBuilder()).create() + + fun anyJsonStringSerde(gson: Gson = default): StoveSerde = StoveGsonStringSerializer(gson) + + fun anyByteArraySerde(gson: Gson = default): StoveSerde = StoveGsonByteArraySerializer(gson) +} + +class StoveGsonStringSerializer( + private val gson: Gson +) : StoveSerde { + override fun serialize(value: TIn): String = gson.toJson(value) + + override fun deserialize(value: String, clazz: Class): T = gson.fromJson(value, clazz) +} + +class StoveGsonByteArraySerializer( + private val gson: Gson +) : StoveSerde { + override fun serialize(value: TIn): ByteArray = gson.toJson(value).toByteArray() + + override fun deserialize(value: ByteArray, clazz: Class): T = gson.fromJson(value.toString(Charsets.UTF_8), clazz) +} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt new file mode 100644 index 00000000..cb09c2e2 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt @@ -0,0 +1,99 @@ +package com.trendyol.stove.testing.e2e.serialization + +import arrow.integrations.jackson.module.registerArrowModule +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.core.* +import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.* +import com.trendyol.stove.functional.* +import java.time.Instant +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAccessor + +object StoveJackson { + val default: ObjectMapper = jacksonObjectMapper().disable(FAIL_ON_EMPTY_BEANS).apply { + findAndRegisterModules() + registerArrowModule() + } + + fun byConfiguring( + configurer: JsonMapper.Builder.() -> Unit + ): ObjectMapper = JsonMapper.builder(default.factory).apply(configurer).build() + + fun anyByteArraySerde(objectMapper: ObjectMapper = default): StoveSerde = StoveJacksonByteArraySerializer(objectMapper) + + fun anyJsonStringSerde(objectMapper: ObjectMapper = default): StoveSerde = StoveJacksonStringSerializer(objectMapper) +} + +class StoveJacksonStringSerializer( + private val objectMapper: ObjectMapper +) : StoveSerde { + override fun serialize(value: TIn): String = objectMapper.writeValueAsString(value) as String + + override fun deserialize(value: String, clazz: Class): T = objectMapper.readValue(value, clazz) +} + +class StoveJacksonByteArraySerializer( + private val objectMapper: ObjectMapper +) : StoveSerde { + override fun serialize(value: TIn): ByteArray = objectMapper.writeValueAsBytes(value) + + override fun deserialize(value: ByteArray, clazz: Class): T = objectMapper.readValue(value, clazz) +} + +/** + * This class is used to create an object mapper with default configurations. + * This object mapper is used to serialize and deserialize request and response bodies. + */ +object E2eObjectMapperConfig { + /** + * Creates an object mapper with default configurations. + * This object mapper is used to serialize and deserialize request and response bodies. + */ + fun createObjectMapperWithDefaults(): ObjectMapper { + val isoInstantModule = SimpleModule() + .addSerializer(Instant::class.java, IsoInstantSerializer()) + .addDeserializer(Instant::class.java, IsoInstantDeserializer()) + + return JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build() + .registerKotlinModule() + .registerModule(isoInstantModule) + } +} + +/** + * Instant serializer deserializer for jackson + */ +class IsoInstantDeserializer : JsonDeserializer() { + override fun deserialize( + parser: JsonParser, + context: DeserializationContext + ): Instant { + val string: String = parser.text.trim() + return Try { + DateTimeFormatter.ISO_INSTANT.parse(string) { temporal: TemporalAccessor -> + Instant.from(temporal) + } as Instant + }.recover { Instant.ofEpochSecond(string.toLong()) }.get() + } +} + +/** + * Instant serializer for jackson + */ +class IsoInstantSerializer : JsonSerializer() { + override fun serialize( + value: Instant, + gen: JsonGenerator, + serializers: SerializerProvider? + ) { + gen.writeString(value.toString()) + } +} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt new file mode 100644 index 00000000..cb964d40 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt @@ -0,0 +1,47 @@ +package com.trendyol.stove.testing.e2e.serialization + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.io.ByteArrayOutputStream + +object StoveKotlinx { + val default: Json = Json { + ignoreUnknownKeys = true + encodeDefaults = true + isLenient = true + explicitNulls = false + } + + fun byConfiguring(configurer: JsonBuilder.() -> Unit): Json = Json(default) { configurer() } + + fun anyJsonStringSerde(json: Json = default): StoveSerde = StoveKotlinxStringSerializer(json) + + fun anyByteArraySerde(json: Json = default): StoveSerde = StoveKotlinxByteArraySerializer(json) +} + +@Suppress("UNCHECKED_CAST") +class StoveKotlinxStringSerializer( + private val json: Json +) : StoveSerde { + override fun serialize(value: TIn): String { + value as Any + return json.encodeToString(serializer(value::class.java), value) + } + + override fun deserialize(value: String, clazz: Class): T = json.decodeFromString(serializer(clazz), value) as T +} + +class StoveKotlinxByteArraySerializer( + private val json: Json +) : StoveSerde { + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(value: Any): ByteArray = ByteArrayOutputStream().use { stream -> + json.encodeToStream(serializer(value::class.java), value, stream) + stream.toByteArray() + } + + @OptIn(ExperimentalSerializationApi::class) + @Suppress("UNCHECKED_CAST") + override fun deserialize(value: ByteArray, clazz: Class): T = + json.decodeFromStream(serializer(clazz), value.inputStream()) as T +} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt new file mode 100644 index 00000000..4c10e688 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt @@ -0,0 +1,42 @@ +package com.trendyol.stove.testing.e2e.serialization + +/** + * Generic interface for serialization and deserialization operations. + * + * @param TIn The type of input object to be serialized + * @param TOut The type of output format after serialization + */ +interface StoveSerde { + /** + * Serializes an input object into the target format. + * + * @param value The input object to serialize + * @return The serialized output + */ + + fun serialize(value: TIn): TOut + + /** + * Deserializes data from the target format into the specified type. + * + * @param value The serialized data to deserialize + * @param clazz The target class to deserialize into + * @return The deserialized object + */ + fun deserialize(value: TOut, clazz: Class): T + + /** + * Companion object containing default configurations and utility functions + */ + companion object { + val jackson = StoveJackson + + val gson = StoveGson + + val kotlinx = StoveKotlinx + + inline fun StoveSerde.deserialize( + value: TOut + ): T = deserialize(value, T::class.java) + } +} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/StateStorage.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/StateStorage.kt index 4a3474d2..a8dd321c 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/StateStorage.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/StateStorage.kt @@ -3,7 +3,7 @@ package com.trendyol.stove.testing.e2e.system.abstractions import com.fasterxml.jackson.module.kotlin.readValue -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.system.* import org.slf4j.* import java.nio.file.* @@ -59,7 +59,7 @@ internal class FileSystemStorage( ) private val pathForSystem: Path = folderForSystem.resolve("stove-e2e-${system.simpleName!!.lowercase(Locale.getDefault())}.lock") - private val j = StoveObjectMapper.Default + private val j = StoveSerde.jackson.default private val l: Logger = LoggerFactory.getLogger(javaClass) init { diff --git a/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt b/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt new file mode 100644 index 00000000..a37f014f --- /dev/null +++ b/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt @@ -0,0 +1,177 @@ +package com.trendyol.stove.testing.e2e.serialization + +import com.fasterxml.jackson.core.JsonParseException +import com.fasterxml.jackson.databind.MapperFeature +import com.google.gson.JsonSyntaxException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.* +import kotlinx.serialization.* + +class SerializerTest : FunSpec({ + + @Serializable + data class TestData( + val id: Int, + val name: String, + val tags: List = listOf(), + @SerialName("created_at") + val createdAt: String? = null + ) + + val testData = TestData( + id = 1, + name = "Test Item", + tags = listOf("tag1", "tag2"), + createdAt = "2024-01-01" + ) + + context("StoveJacksonStringSerializer") { + val serializer = StoveSerde.jackson.anyJsonStringSerde() + + test("should serialize and deserialize object correctly") { + val serialized = serializer.serialize(testData) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe testData + serialized shouldContain "\"id\":1" + serialized shouldContain "\"name\":\"Test Item\"" + } + + test("should handle null values") { + val dataWithNull = testData.copy(createdAt = null) + val serialized = serializer.serialize(dataWithNull) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe dataWithNull + serialized shouldNotContain "created_at" + } + + test("should throw exception for invalid JSON") { + shouldThrow { + serializer.deserialize("invalid json", TestData::class.java) + } + } + } + + context("StoveGsonStringSerializer") { + val serializer = StoveSerde.gson.anyJsonStringSerde() + + test("should serialize and deserialize object correctly") { + val serialized = serializer.serialize(testData) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe testData + serialized shouldContain "\"id\":1" + serialized shouldContain "\"name\":\"Test Item\"" + } + + test("should handle null values") { + val dataWithNull = testData.copy(createdAt = null) + val serialized = serializer.serialize(dataWithNull) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe dataWithNull + serialized shouldNotContain "created_at" + } + + test("should throw exception for invalid JSON") { + shouldThrow { + serializer.deserialize("invalid json", TestData::class.java) + } + } + } + + context("StoveKotlinxStringSerializer") { + val serializer = StoveSerde.kotlinx.anyJsonStringSerde() + + test("should serialize and deserialize object correctly") { + val serialized = serializer.serialize(testData) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe testData + serialized shouldContain "\"id\":1" + serialized shouldContain "\"name\":\"Test Item\"" + } + + test("should handle null values") { + val dataWithNull = testData.copy(createdAt = null) + val serialized = serializer.serialize(dataWithNull) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + + deserialized shouldBe dataWithNull + serialized shouldNotContain "created_at" + } + + test("should throw exception for invalid JSON") { + shouldThrow { + serializer.deserialize("invalid json", TestData::class.java) + } + } + } + + context("Edge cases for all serializers") { + val jacksonSerializer = StoveSerde.jackson.anyJsonStringSerde() + val gsonSerializer = StoveSerde.gson.anyJsonStringSerde() + val kotlinxSerializer = StoveSerde.kotlinx.anyJsonStringSerde() + + test("should handle empty lists") { + val dataWithEmptyList = testData.copy(tags = emptyList()) + + listOf(jacksonSerializer, gsonSerializer, kotlinxSerializer).forEach { serializer -> + val serialized = serializer.serialize(dataWithEmptyList) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + deserialized shouldBe dataWithEmptyList + serialized shouldContain "\"tags\":[]" + } + } + + test("should handle special characters") { + val dataWithSpecialChars = testData.copy(name = "Test \"Item\" with \\special/ chars") + + listOf(jacksonSerializer, gsonSerializer, kotlinxSerializer).forEach { serializer -> + val serialized = serializer.serialize(dataWithSpecialChars) + val deserialized = serializer.deserialize(serialized, TestData::class.java) + deserialized shouldBe dataWithSpecialChars + } + } + } + + context("configuring tests") { + test("should configure StoveGson") { + val gson = StoveGson.byConfiguring { + setPrettyPrinting() + } + + val serializer = StoveGsonStringSerializer(gson) + val serialized = serializer.serialize(testData) + + serialized shouldContain "\n" + } + + test("should configure StoveKotlinx") { + val json = StoveKotlinx.byConfiguring { + ignoreUnknownKeys = false + prettyPrint = true + } + + val serializer = StoveKotlinxStringSerializer(json) + val serialized = serializer.serialize(testData) + + serialized shouldContain "\n" + } + + test("should configure StoveJackson") { + val objectMapper = StoveSerde.jackson.byConfiguring { + enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + enable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + enable(com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT) + } + val serializer = StoveJacksonStringSerializer(objectMapper) + val serialized = serializer.serialize(testData) + + serialized shouldContain "\n" + } + } +}) diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Options.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Options.kt index 4f4979f7..2a5d6094 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Options.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Options.kt @@ -3,7 +3,7 @@ package com.trendyol.stove.testing.e2e.kafka import arrow.core.getOrElse import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.containers.* -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.* import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -42,7 +42,7 @@ data class KafkaOps( data class KafkaSystemOptions( val registry: String = DEFAULT_REGISTRY, val ports: List = DEFAULT_KAFKA_PORTS, - val objectMapper: ObjectMapper = StoveObjectMapper.Default, + val objectMapper: ObjectMapper = StoveSerde.jackson.default, val containerOptions: KafkaContainerOptions = KafkaContainerOptions(), val ops: KafkaOps = KafkaOps(), override val configureExposedConfiguration: (KafkaExposedConfiguration) -> List diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt index 2fa29077..a13d7562 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt @@ -1,6 +1,6 @@ package com.trendyol.stove.testing.e2e.kafka -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.springBoot import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate @@ -139,7 +139,7 @@ class Setup : AbstractProjectConfig() { addInitializers( beans { bean>() - bean { StoveObjectMapper.Default } + bean { StoveSerde.jackson.default } } ) } diff --git a/starters/spring/stove-spring-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/BridgeSystemTests.kt b/starters/spring/stove-spring-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/BridgeSystemTests.kt index 9b7bccc3..df5494d0 100644 --- a/starters/spring/stove-spring-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/BridgeSystemTests.kt +++ b/starters/spring/stove-spring-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/BridgeSystemTests.kt @@ -1,7 +1,7 @@ package com.trendyol.stove.testing.e2e import com.fasterxml.jackson.databind.ObjectMapper -import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.config.AbstractProjectConfig @@ -41,7 +41,7 @@ class SystemTimeGetUtcNow : GetUtcNow { } class TestAppInitializers : BaseApplicationContextInitializer({ - bean { StoveObjectMapper.Default } + bean { StoveSerde.jackson.default } bean { SystemTimeGetUtcNow() } }) { var onEvent: Boolean = false