From da33f06f6c81d24c0d604bb2573aa430ae5b4af8 Mon Sep 17 00:00:00 2001 From: "anilcan.gul" Date: Mon, 9 Dec 2024 09:21:04 +0300 Subject: [PATCH] micronaut framework implemented. --- README.md | 369 +++++----- api/stove.api | 0 build.gradle.kts | 54 +- buildSrc/src/main/kotlin/CI.kt | 20 - docs/Components/01-couchbase.md | 42 +- docs/Components/02-kafka.md | 1 - docs/Components/03-elasticsearch.md | 20 - docs/Components/04-wiremock.md | 17 - docs/Components/05-http.md | 51 -- docs/Components/06-postgresql.md | 41 -- docs/Components/07-mongodb.md | 42 -- docs/Components/08-mssql.md | 54 -- docs/Components/09-redis.md | 50 -- docs/Components/index.md | 3 - examples/ktor-example/api/ktor-example.api | 218 ------ examples/ktor-example/build.gradle.kts | 6 +- .../stove/ktor/example/app/configuration.kt | 18 +- .../example/application/ExampleAppConsumer.kt | 3 +- .../ktor/example/application/LockProvider.kt | 4 +- .../application/UpdateProductRequest.kt | 4 +- .../stove/ktor/example/domain/Product.kt | 5 +- .../ktor/example/domain/ProductRepository.kt | 16 +- .../com/stove/ktor/example/e2e/ExampleTest.kt | 95 ++- .../com/stove/ktor/example/e2e/Stove.kt | 69 -- .../ktor/example/e2e/TestSystemConfig.kt | 66 ++ examples/micronaut-example/.gitignore | 15 + examples/micronaut-example/build.gradle.kts | 81 +++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + .../stove/micronaut/example/Application.kt | 28 + .../example/application/domain/Product.kt | 26 + .../repository/ProductRepository.kt | 8 + .../application/services/ProductService.kt | 19 + .../application/services/SupplierService.kt | 13 + .../infrastructure/api/ProductController.kt | 25 + .../api/model/request/CreateProductRequest.kt | 10 + .../couchbase/CouchbaseConfiguration.kt | 64 ++ .../couchbase/CouchbaseProperties.kt | 15 + .../couchbase/ObjectMapperConfig.kt | 29 + .../http/SupplierHttpService.kt | 28 + .../persistence/ProductCBRepository.kt | 18 + .../src/main/resources/application.yml | 28 + .../src/main/resources/logback.xml | 14 + .../example/e2e/ProductControllerTest.kt | 59 ++ .../micronaut/example/e2e/TestSystemConfig.kt | 66 ++ .../spring-example/api/spring-example.api | 417 ----------- examples/spring-example/build.gradle.kts | 4 - .../application/handlers/ProductCreator.kt | 22 +- .../example/infrastructure/Constants.kt | 14 +- .../infrastructure/ObjectMapperConfig.kt | 34 +- .../infrastructure/api/ProductController.kt | 15 +- .../couchbase/CouchbaseConfiguration.kt | 17 +- .../http/SupplierHttpService.kt | 29 +- .../http/WebClientConfiguration.kt | 70 +- .../messaging/kafka/KafkaProducer.kt | 16 +- .../kafka/configuration/ConsumerSettings.kt | 9 +- .../KafkaConsumerConfiguration.kt | 45 +- .../KafkaProducerConfiguration.kt | 4 +- .../kafka/configuration/ProducerSettings.kt | 7 +- .../consumers/FailingProductCreateConsumer.kt | 18 +- .../kafka/consumers/ProductCreateConsumers.kt | 23 +- .../interceptors/CustomConsumerInterceptor.kt | 3 +- .../stove/spring/example/e2e/ExampleTest.kt | 282 ++++---- .../e2e/{Stove.kt => TestSystemConfig.kt} | 5 +- .../example/e2e/TestSystemInitializer.kt | 10 +- .../api/spring-standalone-example.api | 409 ----------- .../build.gradle.kts | 4 - .../application/handlers/ProductCreator.kt | 2 +- .../example/infrastructure/Constants.kt | 14 +- .../infrastructure/ObjectMapperConfig.kt | 25 +- .../infrastructure/api/ProductController.kt | 11 +- .../couchbase/CouchbaseConfiguration.kt | 18 +- .../http/SupplierHttpService.kt | 4 +- .../http/WebClientConfiguration.kt | 72 +- .../messaging/kafka/KafkaProducer.kt | 8 +- .../kafka/configuration/ConsumerSettings.kt | 10 +- .../KafkaConsumerConfiguration.kt | 12 +- .../KafkaProducerConfiguration.kt | 4 +- .../kafka/configuration/ProducerSettings.kt | 7 +- .../consumers/FailingProductCreateConsumer.kt | 9 +- .../kafka/consumers/ProductCreateConsumers.kt | 11 +- .../interceptors/CustomConsumerInterceptor.kt | 3 +- .../standalone/example/e2e/ExampleTest.kt | 287 ++++---- .../e2e/{Stove.kt => TestSystemConfig.kt} | 9 +- .../api/spring-streams-example.api | 537 -------------- .../spring-streams-example/build.gradle.kts | 6 +- .../streams/example/kafka/CustomSerDe.kt | 35 +- .../application/processor/ExampleJoin.kt | 35 +- .../spring/streams/example/e2e/ExampleTest.kt | 112 +-- .../spring/streams/example/e2e/TestHelper.kt | 66 +- .../e2e/{Stove.kt => TestSystemConfig.kt} | 9 +- gradle.properties | 7 +- gradle/libs.versions.toml | 44 +- .../api/stove-testing-e2e-couchbase.api | 133 ---- .../build.gradle.kts | 4 - .../testing/e2e/couchbase/CouchbaseSystem.kt | 64 +- .../stove/testing/e2e/couchbase/Options.kt | 13 +- .../e2e/couchbase/StoveCouchbaseContainer.kt | 3 +- .../e2e/couchbase/CouchbaseTestSystemTests.kt | 239 ++++--- .../api/stove-testing-e2e-elasticsearch.api | 159 ----- .../build.gradle.kts | 15 +- .../testing/e2e/elasticsearch/CouchbaseDsl.kt | 5 + .../e2e/elasticsearch/ElasticsearchSystem.kt | 68 +- .../testing/e2e/elasticsearch/Extensions.kt | 34 +- .../testing/e2e/elasticsearch/Options.kt | 22 +- .../ElasticsearchExposedCertificateTest.kt | 34 +- .../ElasticsearchTestSystemTests.kt | 132 ++-- .../api/stove-testing-e2e-http.api | 117 ---- lib/stove-testing-e2e-http/build.gradle.kts | 4 - .../stove/testing/e2e/http/HttpSystem.kt | 213 +++--- .../testing/e2e/http/StoveMultiPartContent.kt | 10 +- .../stove/testing/e2e/http/streaming.kt | 30 +- .../stove/testing/e2e/http/HttpSystemTests.kt | 553 +++++++-------- .../api/stove-testing-e2e-kafka.api | 564 --------------- lib/stove-testing-e2e-kafka/build.gradle.kts | 6 +- .../standalone/kafka/KafkaContainerOptions.kt | 3 +- .../e2e/standalone/kafka/KafkaContext.kt | 3 +- .../e2e/standalone/kafka/KafkaSystem.kt | 39 +- .../standalone/kafka/KafkaSystemOptions.kt | 20 +- .../testing/e2e/standalone/kafka/SerDe.kt | 5 +- .../e2e/standalone/kafka/coroutines.kt | 16 +- .../kafka/intercepting/CommonOps.kt | 43 +- .../kafka/intercepting/GrpcUtils.kt | 6 +- .../kafka/intercepting/MessageSinkOps.kt | 45 +- .../intercepting/MessageSinkPublishOps.kt | 2 +- .../kafka/intercepting/StoveKafkaBridge.kt | 19 +- .../intercepting/TestSystemMessageSink.kt | 7 +- .../testing/e2e/standalone/kafka/messages.kt | 10 +- .../src/main/proto/messages.proto | 4 +- .../setup/{Stove.kt => ProjectConfig.kt} | 9 +- .../kafka/setup/example/DomainEvents.kt | 8 +- .../kafka/setup/example/StoveListener.kt | 3 +- .../consumers/ProductFailingConsumer.kt | 8 +- .../kafka/tests/KafkaSystemTests.kt | 241 ++++--- .../api/stove-testing-e2e-mongodb.api | 171 ----- .../build.gradle.kts | 6 +- .../testing/e2e/mongodb/MongodbOptionsDsl.kt | 44 ++ .../testing/e2e/mongodb/MongodbSystem.kt | 72 +- .../e2e/mongodb/MongodbSystemOptions.kt | 33 +- .../stove/testing/e2e/mongodb/Options.kt | 11 +- .../stove/testing/e2e/mongodb/PojoRegistry.kt | 24 + .../e2e/mongodb/MongodbTestSystemTests.kt | 298 ++++---- .../src/test/resources/logback-test.xml | 20 - .../api/stove-testing-e2e-rdbms-mssql.api | 126 ---- .../build.gradle.kts | 4 - .../testing/e2e/rdbms/mssql/MsSqlOptions.kt | 17 +- .../e2e/rdbms/mssql/MssqlSystemTest.kt | 108 +-- .../api/stove-testing-e2e-rdbms-postgres.api | 83 --- .../build.gradle.kts | 4 - .../testing/e2e/rdbms/postgres/Options.kt | 9 +- .../e2e/rdbms/postgres/PostgresqlSystem.kt | 15 +- .../rdbms/postgres/PostgresqlSystemTest.kt | 111 ++- .../api/stove-testing-e2e-rdbms.api | 60 -- .../testing/e2e/rdbms/NativeSqlOperations.kt | 7 +- .../e2e/rdbms/RelationalDatabaseSystem.kt | 4 +- .../api/stove-testing-e2e-redis.api | 107 --- lib/stove-testing-e2e-redis/build.gradle.kts | 5 - .../stove/testing/e2e/redis/RedisOptions.kt | 9 +- .../stove/testing/e2e/redis/RedisSystem.kt | 23 +- .../testing/e2e/redis/RedisSystemTests.kt | 21 +- .../api/stove-testing-e2e-wiremock.api | 116 ---- .../build.gradle.kts | 4 - .../stove/testing/e2e/wiremock/Options.kt | 9 +- .../testing/e2e/wiremock/WireMockSystem.kt | 81 +-- .../stove/testing/e2e/wiremock/stubbing.kt | 8 +- .../{Stove.kt => TestSystemConfig.kt} | 2 +- .../e2e/wiremock/WireMockDeletionTest.kt | 151 ++-- .../e2e/wiremock/WireMockOperationsTest.kt | 548 --------------- .../e2e/wiremock/WireMockSystemTests.kt | 121 ++-- .../api/stove-testing-e2e.api | 655 ------------------ lib/stove-testing-e2e/build.gradle.kts | 6 - .../com/trendyol/stove/functional/Reflect.kt | 4 +- .../com/trendyol/stove/functional/Try.kt | 8 +- .../testing/e2e/containers/StoveContainer.kt | 29 +- .../database/migrations/DatabaseMigration.kt | 4 +- .../migrations/MigrationCollection.kt | 11 +- .../testing/e2e/messaging/Observation.kt | 6 + .../serialization/E2eObjectMapperConfig.kt | 35 + .../serialization/IsoInstantDeserializer.kt | 46 ++ .../e2e/serialization/StoveObjectMapper.kt | 11 + .../stove/testing/e2e/serialization/gson.kt | 33 - .../testing/e2e/serialization/jackson.kt | 98 --- .../testing/e2e/serialization/kotlinx.kt | 47 -- .../e2e/serialization/serialization.kt | 100 --- .../stove/testing/e2e/system/BridgeSystem.kt | 14 +- .../testing/e2e/system/PropertiesFile.kt | 6 +- .../stove/testing/e2e/system/TestSystem.kt | 5 +- .../stove/testing/e2e/system/ValidationDsl.kt | 4 +- .../stove/testing/e2e/system/WithDsl.kt | 4 +- .../e2e/system/abstractions/PluggedSystem.kt | 4 +- .../abstractions/RunnableSystemWithContext.kt | 6 +- .../e2e/system/abstractions/StateStorage.kt | 4 +- .../e2e/serialization/SerializationTests.kt | 198 ------ .../e2e/system/TestSystemOptionsDslTest.kt | 97 ++- .../com/trendyol/stove/CapturedOutput.kt | 42 +- recipes/build.gradle.kts | 13 +- recipes/gradle/libs.versions.toml | 33 +- .../quarkus-recipe/build.gradle.kts | 5 - .../recipes/quarkus/e2e/tests/IndexTests.kt | 29 +- .../spring-boot-recipe/build.gradle.kts | 4 - .../kafka/KafkaBeanConfiguration.java | 13 +- .../kafka/KafkaDomainEventPublisher.java | 20 +- .../example/java/spring/e2e/setup/Stove.kt | 112 ++- .../java/spring/e2e/tests/IndexTests.kt | 17 +- .../spring/e2e/tests/product/CreateTests.kt | 128 ++-- .../ktor-recipe/build.gradle.kts | 3 - .../external/CategoryHttpApiImpl.kt | 19 +- .../boilerplate/kafka/ConsumerSupervisor.kt | 3 +- .../kafka/KafkaDomainEventPublisher.kt | 9 +- .../infra/boilerplate/kafka/TopicResolver.kt | 4 +- .../ktor/infra/boilerplate/kafka/kafka.kt | 5 +- .../ktor/infra/boilerplate/mongo/mongo.kt | 23 +- .../serialization/JacksonConfiguration.kt | 3 +- .../kotlin/ktor/infra/boilerplate/util.kt | 22 +- .../infra/components/product/api/routing.kt | 6 +- .../persistency/MongoProductRepository.kt | 13 +- .../examples/kotlin/ktor/e2e/setup/Stove.kt | 99 ++- .../kotlin/ktor/e2e/tests/IndexTests.kt | 19 +- .../tests/configuration/ConfigurationTests.kt | 15 +- .../ktor/e2e/tests/product/CreateTests.kt | 139 ++-- .../spring-boot-recipe/build.gradle.kts | 4 - .../spring/ExampleStoveSpringBootApp.kt | 5 +- .../examples/kotlin/spring/e2e/setup/Stove.kt | 30 +- .../kotlin/spring/e2e/tests/StreamingTests.kt | 42 +- recipes/scala-recipes/build.gradle.kts | 10 +- .../spring-boot-recipe/build.gradle.kts | 4 - .../recipes/scala/spring/e2e/setup/Stove.kt | 29 +- .../scala/spring/e2e/tests/IndexTests.kt | 29 +- recipes/settings.gradle.kts | 1 + .../stove/examples/domain/ProductTests.kt | 99 ++- .../aggregateroot/AggregateRootAssertion.kt | 16 +- settings.gradle.kts | 10 +- .../api/stove-ktor-testing-e2e.api | 21 - .../stove/testing/e2e/KtorBridgeSystem.kt | 4 +- .../.idea/.gitignore | 8 + .../.idea/gradle.xml | 16 + .../.idea/misc.xml | 4 + .../stove-micronaut-testing-e2e/.idea/vcs.xml | 6 + .../build.gradle.kts | 13 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + .../BaseApplicationContextInitializer.kt | 38 + .../testing/MicronautApplicationUnderTest.kt | 56 ++ .../stove/testing/MicronautBridgeSystem.kt | 16 + .../src/main/resources/application.properties | 2 + .../src/main/resources/logback.xml | 14 + .../stove/testing/BridgeSystemKtTest.kt | 100 +++ .../io/kotest/provided/ProjectConfig.kt | 8 + .../api/stove-spring-testing-e2e-kafka.api | 176 ----- .../build.gradle.kts | 30 +- .../stove/testing/e2e/kafka/Extensions.kt | 66 +- .../stove/testing/e2e/kafka/KafkaSystem.kt | 238 +++---- .../stove/testing/e2e/kafka/MessageStore.kt | 28 +- .../stove/testing/e2e/kafka/Options.kt | 46 +- .../stove/testing/e2e/kafka/StoveMessage.kt | 122 +--- .../e2e/kafka/TestSystemInterceptor.kt | 98 ++- .../testing/e2e/kafka/KafkaSystemTests.kt | 190 +++++ .../ProtobufSerdeKafkaSystemTest.kt | 122 ---- .../testing/e2e/kafka/protobufserde/app.kt | 162 ----- .../stove/testing/e2e/kafka/shared.kt | 13 - .../stringserde/StringSerdeKafkaSystemTest.kt | 128 ---- .../testing/e2e/kafka/stringserde/app.kt | 114 --- .../src/test/proto/example.proto | 18 - .../api/stove-spring-testing-e2e.api | 36 - .../stove-spring-testing-e2e/build.gradle.kts | 3 - .../e2e/BaseApplicationContextInitializer.kt | 5 +- .../stove/testing/e2e/BridgeSystem.kt | 4 +- .../stove/testing/e2e/BridgeSystemTests.kt | 132 ++-- 268 files changed, 4491 insertions(+), 10045 deletions(-) delete mode 100644 api/stove.api delete mode 100644 buildSrc/src/main/kotlin/CI.kt delete mode 100644 examples/ktor-example/api/ktor-example.api delete mode 100644 examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt create mode 100644 examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/TestSystemConfig.kt create mode 100644 examples/micronaut-example/.gitignore create mode 100644 examples/micronaut-example/build.gradle.kts create mode 100644 examples/micronaut-example/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/micronaut-example/gradle/wrapper/gradle-wrapper.properties create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/Application.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/domain/Product.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/repository/ProductRepository.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/ProductService.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/SupplierService.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/ProductController.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/model/request/CreateProductRequest.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseConfiguration.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseProperties.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/ObjectMapperConfig.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/http/SupplierHttpService.kt create mode 100644 examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/persistence/ProductCBRepository.kt create mode 100644 examples/micronaut-example/src/main/resources/application.yml create mode 100644 examples/micronaut-example/src/main/resources/logback.xml create mode 100644 examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/ProductControllerTest.kt create mode 100644 examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/TestSystemConfig.kt delete mode 100644 examples/spring-example/api/spring-example.api rename examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/{Stove.kt => TestSystemConfig.kt} (97%) delete mode 100644 examples/spring-standalone-example/api/spring-standalone-example.api rename examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/{Stove.kt => TestSystemConfig.kt} (90%) delete mode 100644 examples/spring-streams-example/api/spring-streams-example.api rename examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/{Stove.kt => TestSystemConfig.kt} (93%) delete mode 100644 lib/stove-testing-e2e-couchbase/api/stove-testing-e2e-couchbase.api delete mode 100644 lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api create mode 100644 lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/CouchbaseDsl.kt delete mode 100644 lib/stove-testing-e2e-http/api/stove-testing-e2e-http.api delete mode 100644 lib/stove-testing-e2e-kafka/api/stove-testing-e2e-kafka.api rename lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/{Stove.kt => ProjectConfig.kt} (94%) delete mode 100644 lib/stove-testing-e2e-mongodb/api/stove-testing-e2e-mongodb.api create mode 100644 lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt create mode 100644 lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt delete mode 100644 lib/stove-testing-e2e-mongodb/src/test/resources/logback-test.xml delete mode 100644 lib/stove-testing-e2e-rdbms-mssql/api/stove-testing-e2e-rdbms-mssql.api delete mode 100644 lib/stove-testing-e2e-rdbms-postgres/api/stove-testing-e2e-rdbms-postgres.api delete mode 100644 lib/stove-testing-e2e-rdbms/api/stove-testing-e2e-rdbms.api delete mode 100644 lib/stove-testing-e2e-redis/api/stove-testing-e2e-redis.api delete mode 100644 lib/stove-testing-e2e-wiremock/api/stove-testing-e2e-wiremock.api rename lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/{Stove.kt => TestSystemConfig.kt} (93%) delete mode 100644 lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockOperationsTest.kt delete mode 100644 lib/stove-testing-e2e/api/stove-testing-e2e.api create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt create mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt delete mode 100644 lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt delete mode 100644 starters/ktor/stove-ktor-testing-e2e/api/stove-ktor-testing-e2e.api create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/.gitignore create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/gradle.xml create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/misc.xml create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/vcs.xml create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/build.gradle.kts create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.jar create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.properties create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/BaseApplicationContextInitializer.kt create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautApplicationUnderTest.kt create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautBridgeSystem.kt create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/application.properties create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/logback.xml create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/BridgeSystemKtTest.kt create mode 100644 starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/io/kotest/provided/ProjectConfig.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/api/stove-spring-testing-e2e-kafka.api create mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/ProtobufSerdeKafkaSystemTest.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/app.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/shared.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/StringSerdeKafkaSystemTest.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/app.kt delete mode 100644 starters/spring/stove-spring-testing-e2e-kafka/src/test/proto/example.proto delete mode 100644 starters/spring/stove-spring-testing-e2e/api/stove-spring-testing-e2e.api diff --git a/README.md b/README.md index c2e872ed..071029dd 100644 --- a/README.md +++ b/README.md @@ -1,241 +1,218 @@

Stove

-

- The easiest way of writing e2e/component tests for your back-end API in Kotlin -

- -![Release](https://img.shields.io/maven-central/v/com.trendyol/stove-testing-e2e?versionPrefix=0&label=latest-release&color=blue) [](https://oss.sonatype.org/content/repositories/snapshots/com/trendyol/stove-testing-e2e/) [![codecov](https://codecov.io/gh/Trendyol/stove/graph/badge.svg?token=HcKBT3chO7)](https://codecov.io/gh/Trendyol/stove) +

The easiest way of writing e2e/component tests for your back-end API in Kotlin

# What is Stove? -Stove is an end-to-end testing framework that simplifies testing by managing physical dependencies and your -application in a unified way. -Write infrastructure-agnostic but component-aware tests in Kotlin, regardless of your JVM-based tech stack. +Stove is an end-to-end testing framework that spins up physical dependencies and your application all together. So you +have a control over dependencies via Kotlin code. -### Key Features +In the JVM world, thanks to code interoperability, you application code and test can be written with different JVM +languages and can be run together. +For example, you can write your application code with Java and write your tests with Kotlin, or Application code with +Scala and test with Kotlin, etc. +Stove uses this ability and provides a way to write your tests in Kotlin. -* 🚀 Zero Boilerplate: Write clean, focused tests without infrastructure setup code -* 🔌 Pluggable Architecture: Easily extend with custom infrastructure components -* 🐳 Docker-Based: Leverages Testcontainers for reliable, isolated test environments -* 🌐 Framework Agnostic: Works with Spring, Ktor, and other JVM frameworks _(up for grabs)_ -* 🔄 Physical Dependencies: Built-in support for Kafka, Couchbase, PostgreSQL, and more -* ⚡ Fast Development: On top of the boilerplate free testing, optional Reuse test containers for blazing-fast local - development +Your tests will be infra agnostic, but component aware, so they can use easily necessary physical components with Stove +provided APIs. +All the infra is **pluggable**, and can be added easily. You can also create your own infra needs by using the +abstractions +that Stove provides. +Having said that, the only dependency is `docker` since Stove is +using [testcontainers](https://github.com/testcontainers/testcontainers-java) underlying. -## Supported Infrastructure +You can use JUnit and Kotest for running the tests. You can run all the tests on your CI, too. +But that needs **DinD(docker-in-docker)** integration. -Physical dependencies: +The medium story about the motivation behind the framework: +[A New Approach to the API End-to-End Testing in Kotlin](https://medium.com/trendyol-tech/a-new-approach-to-the-api-end-to-end-testing-in-kotlin-f743fd1901f5) -* ✅ Kafka -* ✅ Couchbase -* ✅ PostgreSQL -* ✅ ElasticSearch -* ✅ MongoDB -* ✅ MSSQL -* ✅ Redis -* ✅ HTTP Client -* ✅ WireMock +_Note: Stove is not a replacement for the unit tests, it is a framework for end-to-end/component tests._ -Frameworks: +> [!NOTE] +> Some people tend to call these tests as _integration tests_, some call them as _component tests_, some call them as +> end-to-end tests. In this documentation, we will use the term end-to-end tests. +> We think that the **e2e/component tests** term is more serving to the purpose of message we want to convey. -* ✅ Spring -* ✅ Ktor -* 🚧 Quarkus (up for grabs) -* 🚧 Micronaut (up for grabs) +## What is the problem? -## Quick Start +In the JVM world, we have a lot of frameworks for the application code, but when it comes to integration/component/e2e testing we don't have a single framework that can be used for all the tech stacks. +We have testcontainers but you still need to do lots of plumbing to make it work with your tech stack. -### Add the dependency +The use-cases that led us develop the Stove are to increase the productivity of the developers while keeping the quality +of the codebase high and coherent. -```kotlin -// Add the following dependencies to your build.gradle.kts -testImplementation("com.trendyol:stove-testing-e2e:${version}") +Those use-cases are: +- Kotlin app with Spring-Boot +- Kotlin app with Ktor +- Java app with Spring-Boot +- Java app with Micronaut +- Java app with Quarkus +- Scala app with Spring-Boot -// And the any of the following for the infrastructure you want to use, for example Kafka -// you can also use Couchbase, PostgreSQL, ElasticSearch, MongoDB, MSSQL, Redis, HTTP Client, WireMock -// as much as you want -testImplementation("com.trendyol:stove-testing-e2e-kafka:${version}") +People have different tech stacks and each time when they want to write e2e tests, they need to write a lot of boilerplate code. +Stove is here to solve this problem. It provides a single API to write e2e tests for all the tech stacks. -// And Application Under Test (AUT) -testImplementation("com.trendyol:stove-ktor-testing-e2e:${version}") +**Stove unifies the testing experience whatever you use.** -// Or -testImplementation("com.trendyol:stove-spring-testing-e2e:${version}") -``` +For more info and how to use: [Check the documentation](https://trendyol.github.io/stove/) -### Set Up the TestSystem - -```kotlin -TestSystem() { - if (isRunningLocally()) { - enableReuseForTestContainers() - - // this will keep the dependencies running - // after the tests are finished, - // so next run will be blazing fast :) - keepDendenciesRunning() - } -}.with { - // Enables http client - // to make real http calls - // against the application under test - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8001", - ) - } - - // Enables Couchbase physically - // and exposes the configuration - // to the application under test - couchbase { - CouchbaseSystemOptions( - defaultBucket = "Stove", - configureExposedConfiguration = { cfg -> listOf("couchbase.hosts=${cfg.hostsWithPort}") }, - ) - } - - // Enables Kafka physically - // and exposes the configuration - // to the application under test - kafka { - KafkaSystemOptions( - configureExposedConfiguration = { cfg -> listOf("kafka.bootstrapServers=${cfg.boostrapServers}") }, - ) - } - - // Enables Wiremock on the given port - // and provides configurable mock HTTP server - // for your external API calls - wiremock { - WireMockSystemOptions( - port = 9090, - removeStubAfterRequestMatched = true, - afterRequest = { e, _, _ -> - logger.info(e.request.toString()) - }, - ) - } - - // The Application Under Test. - // Enables Spring Boot application - // to be run with the given parameters. - springBoot( - runner = { parameters -> - stove.spring.example.run(parameters) { it.addTestSystemDependencies() } - }, - withParameters = listOf( - "server.port=8001", - "logging.level.root=warn", - "logging.level.org.springframework.web=warn", - "spring.profiles.active=default", - "kafka.heartbeatInSeconds=2", - ), - ) -}.run() - -``` +> [!WARNING] +> Stove is under development and, despite being heavily tested, its API isn't yet stabilized; _breaking changes +> might happen on minor releases._ However, we will always provide migration guides. -### Write Tests +> Report any issue or bug [in the GitHub repository.](https://github.com/Trendyol/stove/issues) -```kotlin -TestSystem.validate { - wiremock { - mockGet("/example-url", responseBody = None, statusCode = 200) - } - - http { - get("/hello/index") { actual -> - actual shouldContain "Hi from Stove framework" - println(actual) - } - } +[![codecov](https://codecov.io/gh/Trendyol/stove/graph/badge.svg?token=HcKBT3chO7)](https://codecov.io/gh/Trendyol/stove) - couchbase { - shouldQuery("SELECT * FROM system:keyspaces") { actual -> - println(actual) - } - } +## Supports - kafka { - shouldBePublished { - actual.aggregateId == 123 - && metadata.topic = "example-topic" - && metadata.headers["example-header"] == "example-value" - } - shouldBeConsumed { - actual.aggregateId == 123 - && metadata.topic = "example-topic" - && metadata.headers["example-header"] == "example-value" - } - } +Physical dependencies: - couchbase { - save(collection = "Backlogs", id = "id-of-backlog", instance = Backlog("id-of-backlog")) - } +- [x] Kafka +- [x] Couchbase +- [x] HttpClient to make real http calls against the _application under test_ +- [x] Wiremock to mock all the external dependencies +- [x] PostgresSql +- [x] ElasticSearch +- [x] MongoDB +- [x] MSSQL +- [x] Redis - http { - postAndExpectBodilessResponse("/backlog/reserve") { actual -> - actual.status.shouldBe(200) - } - } +Frameworks: - kafka { - shouldBeConsumed { - actual.aggregateId == expectedId - } - } -} -``` +- [x] Spring +- [x] Ktor +- [ ] Quarkus _(up for grabs)_ +- [ ] Micronaut _(up for grabs)_ -## Why Stove? +## Show me the code -The JVM ecosystem lacks a unified approach to end-to-end testing. -While tools like Testcontainers exist, developers still need to: +[📹 Youtube Session about how to use](https://youtu.be/DJ0CI5cBanc?t=669) _(Turkish)_ -* Write extensive boilerplate code -* Complex setup code for each tech stack -* Create different testing setups for each framework -* Manage complex infrastructure configurations for each framework +### Setting-up all the physical dependencies with application -This affects teams across many tech stacks: +```kotlin +TestSystem() { + if (isRunningLocally()) { + enableReuseForTestContainers() -* Kotlin with Spring Boot/Ktor -* Java with Spring Boot/Micronaut/Quarkus -* Scala with Spring Boot + // this will keep the dependencies running + // after the tests are finished, + // so next run will be blazing fast :) + keepDendenciesRunning() + } +}.with { + // Enables http client + // to make real http calls + // against the application under test + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8001", + ) + } -Stove solves these challenges by providing: + // Enables Couchbase physically + // and exposes the configuration + // to the application under test + couchbase { + CouchbaseSystemOptions( + defaultBucket = "Stove", + configureExposedConfiguration = { cfg -> listOf("couchbase.hosts=${cfg.hostsWithPort}") }, + ) + } -* A unified testing API across all JVM stacks -* Built-in support for common infrastructure -* Clean, Kotlin-based test syntax -* Reusable test containers for fast local development + // Enables Kafka physically + // and exposes the configuration + // to the application under test + kafka { + KafkaSystemOptions( + configureExposedConfiguration = { cfg -> listOf("kafka.bootstrapServers=${cfg.boostrapServers}") }, + ) + } -**Stove unifies the testing experience across all JVM stacks, making it easier to write clean, focused tests.** + // Enables Wiremock on the given port + // and provides configurable mock HTTP server + // for your external API calls + wiremock { + WireMockSystemOptions( + port = 9090, + removeStubAfterRequestMatched = true, + afterRequest = { e, _, _ -> + logger.info(e.request.toString()) + }, + ) + } -## Resources + // The Application Under Test. + // Enables Spring Boot application + // to be run with the given parameters. + springBoot( + runner = { parameters -> + stove.spring.example.run(parameters) { it.addTestSystemDependencies() } + }, + withParameters = listOf( + "server.port=8001", + "logging.level.root=warn", + "logging.level.org.springframework.web=warn", + "spring.profiles.active=default", + "kafka.heartbeatInSeconds=2", + ), + ) +}.run() -* 📖 [Documentation](https://trendyol.github.io/stove/) -* [📹 Youtube Session about how to use](https://youtu.be/DJ0CI5cBanc?t=669) _(Turkish)_ -* 📝 [Motivation Article](https://medium.com/trendyol-tech/a-new-approach-to-the-api-end-to-end-testing-in-kotlin-f743fd1901f5) +``` -## Status +### Testing the entire application with physical dependencies -> [!WARNING] -> While Stove is production-ready and extensively used, the API is not yet fully stabilized. Breaking changes may -> occur in minor releases, but migration guides will always be provided. +```kotlin +TestSystem.validate { + wiremock { + mockGet("/example-url", responseBody = None, statusCode = 200) + } + + http { + get("/hello/index") { actual -> + actual shouldContain "Hi from Stove framework" + println(actual) + } + } -## Contributing + couchbase { + shouldQuery("SELECT * FROM system:keyspaces") { actual -> + println(actual) + } + } -Contributions are welcome! Whether it's: + kafka { + shouldBePublished { + actual.aggregateId == 123 + && metadata.topic = "example-topic" + && metadata.headers["example-header"] == "example-value" + } + shouldBeConsumed { + actual.aggregateId == 123 + && metadata.topic = "example-topic" + && metadata.headers["example-header"] == "example-value" + } + } -* 🐛 [Bug reports](https://github.com/Trendyol/stove/issues) -* 💡 [Feature requests](https://github.com/Trendyol/stove/issues) -* 📖 Documentation improvements -* 🚀 Code contributions + couchbase { + save(collection = "Backlogs", id = "id-of-backlog", instance = Backlog("id-of-backlog")) + } -## License + http { + postAndExpectBodilessResponse("/backlog/reserve") { actual -> + actual.status.shouldBe(200) + } + } -Stove is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. + kafka { + shouldBeConsumed { + actual.aggregateId == expectedId + } + } +} +``` diff --git a/api/stove.api b/api/stove.api deleted file mode 100644 index e69de29b..00000000 diff --git a/build.gradle.kts b/build.gradle.kts index f69f1d27..1f93f32a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import org.gradle.kotlin.dsl.libs import org.gradle.plugins.ide.idea.model.IdeaModel import org.jetbrains.dokka.gradle.DokkaMultiModuleTask import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -10,13 +9,12 @@ plugins { alias(libs.plugins.testLogger) alias(libs.plugins.kover) alias(libs.plugins.detekt) - alias(libs.plugins.binaryCompatibilityValidator) id("stove-publishing") apply false idea java } group = "com.trendyol" -version = CI.version(project) +version = version() allprojects { extra.set("dokka.outputDirectory", rootDir.resolve("docs")) @@ -42,21 +40,41 @@ kover { } } } -val related = subprojects.of("lib", "spring", "examples", "ktor") +val related = subprojects.of("lib", "spring", "examples", "ktor", "micronaut-starter") dependencies { related.forEach { kover(it) } } -subprojects.of("lib", "spring", "examples", "ktor") { +subprojects.of("lib", "spring", "examples", "ktor", "micronaut-starter") { apply { plugin("kotlin") - plugin(rootProject.libs.plugins.spotless.get().pluginId) - plugin(rootProject.libs.plugins.dokka.get().pluginId) - plugin(rootProject.libs.plugins.testLogger.get().pluginId) - plugin(rootProject.libs.plugins.kover.get().pluginId) - plugin(rootProject.libs.plugins.detekt.get().pluginId) + plugin( + rootProject.libs.plugins.spotless + .get() + .pluginId + ) + plugin( + rootProject.libs.plugins.dokka + .get() + .pluginId + ) + plugin( + rootProject.libs.plugins.testLogger + .get() + .pluginId + ) + plugin( + rootProject.libs.plugins.kover + .get() + .pluginId + ) + plugin( + rootProject.libs.plugins.detekt + .get() + .pluginId + ) plugin("idea") } @@ -76,13 +94,12 @@ subprojects.of("lib", "spring", "examples", "ktor") { testImplementation(libs.kotest.runner.junit5) testImplementation(libs.kotest.framework.api) testImplementation(libs.kotest.property) - testImplementation(libs.kotest.arrow) detektPlugins(libs.detekt.formatting) } spotless { kotlin { - ktlint(libs.versions.ktlint.get()).setEditorConfigPath(rootProject.layout.projectDirectory.file(".editorconfig")) + ktlint().setEditorConfigPath(rootProject.layout.projectDirectory.file(".editorconfig")) targetExclude("build/", "generated/", "out/") targetExcludeIfContentContains("generated") targetExcludeIfContentContainsRegex("generated.*") @@ -141,10 +158,11 @@ val publishedProjects = listOf( "stove-testing-e2e-redis", "stove-ktor-testing-e2e", "stove-spring-testing-e2e", - "stove-spring-testing-e2e-kafka" + "stove-spring-testing-e2e-kafka", + "stove-micronaut-testing-e2e" ) -subprojects.of("lib", "spring", "ktor", filter = { p -> publishedProjects.contains(p.name) }) { +subprojects.of("lib", "spring", "ktor", "micronaut-starter", filter = { p -> publishedProjects.contains(p.name) }) { apply { plugin("java") plugin("stove-publishing") @@ -160,3 +178,11 @@ tasks.withType().configureEach { outputDirectory.set(file(rootDir.resolve("docs/source"))) } +fun version(): String = when { + System.getenv("SNAPSHOT") != null -> { + println("SNAPSHOT: ${System.getenv("SNAPSHOT")}") + project.properties["snapshot"].toString() + } + + else -> project.properties["version"].toString() +} diff --git a/buildSrc/src/main/kotlin/CI.kt b/buildSrc/src/main/kotlin/CI.kt deleted file mode 100644 index b64d38a1..00000000 --- a/buildSrc/src/main/kotlin/CI.kt +++ /dev/null @@ -1,20 +0,0 @@ -import org.gradle.api.Project - -object CI { - private val isSnapshot: Boolean - get() = System.getenv("SNAPSHOT") != null && System.getenv("SNAPSHOT") == "true" - - private val Project.snapshotBase: String - get() = properties["snapshot"].toString() - - private val Project.releaseVersion: String - get() = properties["version"].toString() - - private val buildNumber: String - get() = System.getenv("BUILD_NUMBER") ?: "0" - - fun version(project: Project): String = when { - isSnapshot -> "${project.snapshotBase}.${buildNumber}-SNAPSHOT" - else -> project.properties["version"].toString() - } -} diff --git a/docs/Components/01-couchbase.md b/docs/Components/01-couchbase.md index 38471a9a..3463b1b7 100644 --- a/docs/Components/01-couchbase.md +++ b/docs/Components/01-couchbase.md @@ -10,14 +10,13 @@ ## Configure -After getting the library from the maven source, while configuring TestSystem you will have access to `couchbase` -function. +After getting the library from the maven source, while configuring TestSystem you will have access to `withCouchbase` function. This function configures the Couchbase Docker container that is going to be started. -Here you can define a `defaultBucket` name. +Here you can define a `defaultBucket` name. !!! warning -Make sure that your application has the same bucket names. + Make sure that your application has the same bucket names. ```kotlin TestSystem() @@ -42,38 +41,3 @@ Your application will start with the physical dependencies that are spun-up by t ## Migrations Stove provides a way to run migrations before the test starts. - -```kotlin -class CouchbaseMigration : DatabaseMigration { - override val order: Int = 1 - - override suspend fun execute(connection: Cluster) { - val bucket = connection.bucket(CollectionConstants.BUCKET_NAME) - listOf(CollectionConstants.PRODUCT_COLLECTION).forEach { collection -> - bucket.collections.createCollection(bucket.defaultScope().name, collection) - } - connection.waitUntilReady(30.seconds) - } -} -``` - -You can define your migration class by implementing the `DatabaseMigration` interface. You can define the order of the -migration by overriding the `order` property. The migrations will be executed in the order of the `order` property. - -After defining your migration class, you can pass it to the `migrations` function of the `couchbase` configuration. - -```kotlin -TestSystem() - .with { - couchbase { - CouchbaseSystemOptions(defaultBucket = "test-bucket", configureExposedConfiguration = { cfg -> - listOf( - "couchbase.hosts=${cfg.hostsWithPort}", - "couchbase.username=${cfg.username}", - "couchbase.password=${cfg.password}" - ) - }).migrations() - } - } - .run() -``` diff --git a/docs/Components/02-kafka.md b/docs/Components/02-kafka.md index cb22d879..cae36882 100644 --- a/docs/Components/02-kafka.md +++ b/docs/Components/02-kafka.md @@ -1,6 +1,5 @@ # Kafka -There are two ways to work with Kafka in Stove. ## Standalone Kafka diff --git a/docs/Components/03-elasticsearch.md b/docs/Components/03-elasticsearch.md index e390f3a1..56c6ba9e 100644 --- a/docs/Components/03-elasticsearch.md +++ b/docs/Components/03-elasticsearch.md @@ -9,23 +9,3 @@ ``` ## Configure - -After getting the library from the maven source, while configuring TestSystem you will have access to `elasticsearch` -function. -This function configures the Elasticsearch Docker container that is going to be started. - -```kotlin -TestSystem() - .with { - elasticsearch { - ElasticsearchSystemOptions(configureExposedConfiguration = { cfg -> - listOf( - "elasticsearch.hosts=${cfg.hostsWithPort}", - "elasticsearch.username=${cfg.username}", - "elasticsearch.password=${cfg.password}" - ) - }) - } - } - .run() -``` diff --git a/docs/Components/04-wiremock.md b/docs/Components/04-wiremock.md index 6333b3e0..e400f7e6 100644 --- a/docs/Components/04-wiremock.md +++ b/docs/Components/04-wiremock.md @@ -9,20 +9,3 @@ ``` ## Configure - -After getting the library from the maven source, while configuring TestSystem you will have access to `wiremock` -function. - -This will start an instance of Wiremock server. You can configure the port of the Wiremock server. - -```kotlin -TestSystem() - .with { - wiremock { - WiremockSystemOptions( - port = 8080, - ) - } - } - .run() -``` diff --git a/docs/Components/05-http.md b/docs/Components/05-http.md index bfd6bf49..16404441 100644 --- a/docs/Components/05-http.md +++ b/docs/Components/05-http.md @@ -10,54 +10,3 @@ ## Configure -After getting the library from the maven source, while configuring TestSystem you will have access to `http` - -```kotlin -TestSystem() - .with { - http { - HttpClientSystemOptions( - baseUrl = "http://localhost:8080", - ) - } - } - .run() -``` - -The other options that you can set are: -```kotlin -data class HttpClientSystemOptions( - /** - * Base URL of the HTTP client. - */ - val baseUrl: String, - - /** - * Content converter for the HTTP client. Default is JacksonConverter. You can use GsonConverter or any other converter. - * If you want to use your own converter, you can implement ContentConverter interface. - */ - val contentConverter: ContentConverter = JacksonConverter(StoveSerde.jackson.default), - - /** - * Timeout for the HTTP client. Default is 30 seconds. - */ - val timeout: Duration = 30.seconds, - - /** - * Create client function for the HTTP client. Default is jsonHttpClient. - */ - val createClient: () -> io.ktor.client.HttpClient = { jsonHttpClient(timeout, contentConverter) } -) -``` - -## Usage - -```kotlin -validate { - http { - get("/relative-url") { actual -> - actual shouldBe expected - } - } -} -``` diff --git a/docs/Components/06-postgresql.md b/docs/Components/06-postgresql.md index 0570cf31..54e5afbe 100644 --- a/docs/Components/06-postgresql.md +++ b/docs/Components/06-postgresql.md @@ -10,44 +10,3 @@ ## Configure -```kotlin -TestSystem() - .with { - postgresql { - PostgresqlSystemOptions { - listOf( - "postgresql.host=${it.host}", - "postgresql.port=${it.port}", - "postgresql.database=${it.database}", - "postgresql.username=${it.username}", - "postgresql.password=${it.password}" - ) - } - } - }.run() -``` - -## Usage - -```kotlin -TestSystem.validate { - postgresql { - shouldExecute( - """ - DROP TABLE IF EXISTS Dummies; - CREATE TABLE IF NOT EXISTS Dummies ( - id serial PRIMARY KEY, - description VARCHAR (50) NOT NULL - ); - """.trimIndent() - ) - shouldExecute("INSERT INTO Dummies (description) VALUES ('${testCase.name.testName}')") - shouldQuery("SELECT * FROM Dummies", mapper = { - IdAndDescription(it.getLong("id"), it.getString("description")) - }) { actual -> - actual.size shouldBeGreaterThan 0 - actual.first().description shouldBe testCase.name.testName - } - } -} -``` diff --git a/docs/Components/07-mongodb.md b/docs/Components/07-mongodb.md index 10b396f8..025052b0 100644 --- a/docs/Components/07-mongodb.md +++ b/docs/Components/07-mongodb.md @@ -10,45 +10,3 @@ ## Configure -```kotlin -TestSystem() - .with { - mongodb { - MongodbSystemOptions( - listOf( - "mongodb.host=${it.host}", - "mongodb.port=${it.port}", - "mongodb.database=${it.database}", - "mongodb.username=${it.username}", - "mongodb.password=${it.password}" - ) - ) - } - } - .run() -``` - -## Usage - -```kotlin -test("should save and get with string objectId") { - val id = ObjectId() - validate { - mongodb { - save( - ExampleInstanceWithStringObjectId( - id = id.toHexString(), - aggregateId = id.toHexString(), - description = testCase.name.testName - ), - id.toHexString() - ) - shouldGet(id.toHexString()) { actual -> - actual.aggregateId shouldBe id.toHexString() - actual.description shouldBe testCase.name.testName - } - } - } -} -``` - diff --git a/docs/Components/08-mssql.md b/docs/Components/08-mssql.md index 7a23cabf..8cda2f66 100644 --- a/docs/Components/08-mssql.md +++ b/docs/Components/08-mssql.md @@ -10,57 +10,3 @@ ## Configure -```kotlin -TestSystem() - .with { - mssql { - MssqlSystemOptions { - listOf( - "mssql.host=${it.host}", - "mssql.port=${it.port}", - "mssql.database=${it.database}", - "mssql.username=${it.username}", - "mssql.password=${it.password}" - ) - } - } - }.run() -``` - -## Usage - -```kotlin -validate { - mssql { - ops { - val result = select("SELECT 1") { - it.getInt(1) - } - result.first() shouldBe 1 - } - shouldExecute("insert into Person values (1, 'Doe', 'John', '123 Main St', 'Springfield')") - shouldQuery( - query = "select * from Person", - mapper = { - Person( - it.getInt(1), - it.getString(2), - it.getString(3), - it.getString(4), - it.getString(5) - ) - } - ) { result -> - result.size shouldBe 1 - result.first().apply { - personId shouldBe 1 - lastName shouldBe "Doe" - firstName shouldBe "John" - address shouldBe "123 Main St" - city shouldBe "Springfield" - } - } - } -} -``` - diff --git a/docs/Components/09-redis.md b/docs/Components/09-redis.md index 5c60b9d9..7375a1aa 100644 --- a/docs/Components/09-redis.md +++ b/docs/Components/09-redis.md @@ -10,53 +10,3 @@ ## Configure -```kotlin -TestSystem() - .with { - redis { - RedisSystemOptions { - listOf( - "redis.host=${it.host}", - "redis.port=${it.port}", - "redis.password=${it.password}" - ) - } - } - }.run() -``` - -## Usage - -There is not much to do with Redis. You can use the `RedisSystem` object to interact with the Redis instance. - -There is an extension function called `client` that gives the access to the underlying redis client of `RedisSystem`. - -You can create any extension on top of `client` to interact with Redis. - -```kotlin -class RedisSystem { - // Reference to the Redis client, this is already located in RedisSystem. - companion object { - fun RedisSystem.client(): RedisClient { - if (!isInitialized()) throw SystemNotInitializedException(RedisSystem::class) - return client - } - } -} -``` - -Your tests: - -```kotlin -validate { - redis { - client().use { client -> - client.set("key", "value") - val value = client.get("key") - value shouldBe "value" - } - } -} -``` - - diff --git a/docs/Components/index.md b/docs/Components/index.md index c5f8869f..48f0b83e 100644 --- a/docs/Components/index.md +++ b/docs/Components/index.md @@ -8,6 +8,3 @@ All the dependencies are pluggable. Stove supports: - [Wiremock](04-wiremock.md) - [Http client](05-http.md) - [Postgres Sql](06-postgresql.md) -- [MongoDB](07-mongodb.md) -- [Mssql](08-mssql.md) -- [Redis](09-redis.md) diff --git a/examples/ktor-example/api/ktor-example.api b/examples/ktor-example/api/ktor-example.api deleted file mode 100644 index 773cd4dc..00000000 --- a/examples/ktor-example/api/ktor-example.api +++ /dev/null @@ -1,218 +0,0 @@ -public final class stove/ktor/example/ApplicationKt { - public static final field CONNECT_TIMEOUT_SECONDS J - public static final fun main ([Ljava/lang/String;)V - public static final fun mainModule (Lio/ktor/server/application/Application;Lstove/ktor/example/app/AppConfiguration;Lkotlin/jvm/functions/Function0;)V - public static final fun run ([Ljava/lang/String;ZLkotlin/jvm/functions/Function0;)Lio/ktor/server/application/Application; - public static synthetic fun run$default ([Ljava/lang/String;ZLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lio/ktor/server/application/Application; -} - -public final class stove/ktor/example/app/AppConfiguration { - public fun (ILstove/ktor/example/app/DatabaseConfiguration;Lstove/ktor/example/app/KafkaConfiguration;)V - public final fun component1 ()I - public final fun component2 ()Lstove/ktor/example/app/DatabaseConfiguration; - public final fun component3 ()Lstove/ktor/example/app/KafkaConfiguration; - public final fun copy (ILstove/ktor/example/app/DatabaseConfiguration;Lstove/ktor/example/app/KafkaConfiguration;)Lstove/ktor/example/app/AppConfiguration; - public static synthetic fun copy$default (Lstove/ktor/example/app/AppConfiguration;ILstove/ktor/example/app/DatabaseConfiguration;Lstove/ktor/example/app/KafkaConfiguration;ILjava/lang/Object;)Lstove/ktor/example/app/AppConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getDatabase ()Lstove/ktor/example/app/DatabaseConfiguration; - public final fun getKafka ()Lstove/ktor/example/app/KafkaConfiguration; - public final fun getPort ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/app/AppEnv : java/lang/Enum { - public static final field Companion Lstove/ktor/example/app/AppEnv$Companion; - public static final field Local Lstove/ktor/example/app/AppEnv; - public static final field Prod Lstove/ktor/example/app/AppEnv; - public static final field Unspecified Lstove/ktor/example/app/AppEnv; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getEnv ()Ljava/lang/String; - public final fun isLocal ()Z - public final fun isProd ()Z - public static fun valueOf (Ljava/lang/String;)Lstove/ktor/example/app/AppEnv; - public static fun values ()[Lstove/ktor/example/app/AppEnv; -} - -public final class stove/ktor/example/app/AppEnv$Companion { - public final fun current ()Lstove/ktor/example/app/AppEnv; - public final fun toEnv ()Lcom/sksamuel/hoplite/env/Environment; -} - -public final class stove/ktor/example/app/AppKt { - public static final fun app ()Lorg/koin/core/module/Module; - public static final fun getObjectMapperRef ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/ktor/example/app/DatabaseConfiguration { - public fun (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lstove/ktor/example/app/DatabaseConfiguration; - public static synthetic fun copy$default (Lstove/ktor/example/app/DatabaseConfiguration;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lstove/ktor/example/app/DatabaseConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getHost ()Ljava/lang/String; - public final fun getJdbcUrl ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getPort ()I - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/app/DatabaseKt { - public static final fun postgresql ()Lorg/koin/core/module/Module; -} - -public final class stove/ktor/example/app/ExampleAppKafkaValueDeserializer : org/apache/kafka/common/serialization/Deserializer { - public fun ()V - public fun deserialize (Ljava/lang/String;[B)Ljava/lang/Object; -} - -public final class stove/ktor/example/app/ExampleAppKafkaValueSerializer : org/apache/kafka/common/serialization/Serializer { - public fun ()V - public fun serialize (Ljava/lang/String;Ljava/lang/Object;)[B -} - -public final class stove/ktor/example/app/KafkaConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/util/List; - public final fun component5 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)Lstove/ktor/example/app/KafkaConfiguration; - public static synthetic fun copy$default (Lstove/ktor/example/app/KafkaConfiguration;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lstove/ktor/example/app/KafkaConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getBootstrapServers ()Ljava/lang/String; - public final fun getClientId ()Ljava/lang/String; - public final fun getGroupId ()Ljava/lang/String; - public final fun getInterceptorClasses ()Ljava/util/List; - public final fun getTopics ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/app/KafkaKt { - public static final fun kafka ()Lorg/koin/core/module/Module; -} - -public final class stove/ktor/example/app/RoutingKt { - public static final fun configureRouting (Lio/ktor/server/application/Application;)V -} - -public final class stove/ktor/example/app/TopicConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lstove/ktor/example/app/TopicConfiguration; - public static synthetic fun copy$default (Lstove/ktor/example/app/TopicConfiguration;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lstove/ktor/example/app/TopicConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/lang/String; - public final fun getRetry ()Ljava/lang/String; - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/application/ExampleAppConsumer { - public fun (Lstove/ktor/example/app/AppConfiguration;Lorg/apache/kafka/clients/consumer/KafkaConsumer;)V - public final fun start ()V - public final fun stop ()V -} - -public final class stove/ktor/example/application/ExampleAppConsumerKt { - public static final field POLL_TIMEOUT_SECONDS I -} - -public abstract interface class stove/ktor/example/application/LockProvider { - public abstract fun acquireLock (Ljava/lang/String;Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun releaseLock (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class stove/ktor/example/application/MutexLockProvider : stove/ktor/example/application/LockProvider { - public fun ()V - public fun acquireLock (Ljava/lang/String;Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun releaseLock (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class stove/ktor/example/application/ProductService { - public static final field Companion Lstove/ktor/example/application/ProductService$Companion; - public fun (Lstove/ktor/example/domain/ProductRepository;Lstove/ktor/example/application/LockProvider;Lorg/apache/kafka/clients/producer/KafkaProducer;)V - public final fun update (ILstove/ktor/example/application/UpdateProductRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class stove/ktor/example/application/ProductService$Companion { -} - -public final class stove/ktor/example/application/UpdateProductRequest { - public static final field Companion Lstove/ktor/example/application/UpdateProductRequest$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lstove/ktor/example/application/UpdateProductRequest; - public static synthetic fun copy$default (Lstove/ktor/example/application/UpdateProductRequest;Ljava/lang/String;ILjava/lang/Object;)Lstove/ktor/example/application/UpdateProductRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public synthetic class stove/ktor/example/application/UpdateProductRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lstove/ktor/example/application/UpdateProductRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lstove/ktor/example/application/UpdateProductRequest; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lstove/ktor/example/application/UpdateProductRequest;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class stove/ktor/example/application/UpdateProductRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class stove/ktor/example/domain/DomainEvents { - public static final field INSTANCE Lstove/ktor/example/domain/DomainEvents; -} - -public final class stove/ktor/example/domain/DomainEvents$ProductUpdated { - public fun (ILjava/lang/String;)V - public final fun component1 ()I - public final fun component2 ()Ljava/lang/String; - public final fun copy (ILjava/lang/String;)Lstove/ktor/example/domain/DomainEvents$ProductUpdated; - public static synthetic fun copy$default (Lstove/ktor/example/domain/DomainEvents$ProductUpdated;ILjava/lang/String;ILjava/lang/Object;)Lstove/ktor/example/domain/DomainEvents$ProductUpdated; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()I - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/domain/Product { - public fun (ILjava/lang/String;)V - public final fun component1 ()I - public final fun component2 ()Ljava/lang/String; - public final fun copy (ILjava/lang/String;)Lstove/ktor/example/domain/Product; - public static synthetic fun copy$default (Lstove/ktor/example/domain/Product;ILjava/lang/String;ILjava/lang/Object;)Lstove/ktor/example/domain/Product; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()I - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public final fun setName (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public final class stove/ktor/example/domain/ProductRepository { - public fun (Lio/r2dbc/postgresql/PostgresqlConnectionFactory;)V - public final fun findById (ILkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun transaction (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun update (Lstove/ktor/example/domain/Product;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - diff --git a/examples/ktor-example/build.gradle.kts b/examples/ktor-example/build.gradle.kts index 2b08e4df..bd80e8f2 100644 --- a/examples/ktor-example/build.gradle.kts +++ b/examples/ktor-example/build.gradle.kts @@ -1,4 +1,4 @@ -@file:Suppress("UnstableApiUsage") +@file:Suppress("UnstableApiUsage", "DSL_SCOPE_VIOLATION") plugins { kotlin("jvm") version libs.versions.kotlin @@ -45,10 +45,6 @@ dependencies { testImplementation(projects.stove.starters.ktor.stoveKtorTestingE2e) } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.stove.ktor.example.e2e.Stove") -} - repositories { mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/app/configuration.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/app/configuration.kt index 667a6568..328e420f 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/app/configuration.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/app/configuration.kt @@ -4,8 +4,7 @@ import com.sksamuel.hoplite.* import com.sksamuel.hoplite.env.Environment @OptIn(ExperimentalHoplite::class) -inline fun loadConfiguration(args: Array = arrayOf()): T = ConfigLoaderBuilder - .default() +inline fun loadConfiguration(args: Array = arrayOf()): T = ConfigLoaderBuilder.default() .addEnvironmentSource() .addCommandLineSource(args) .withExplicitSealedTypes() @@ -25,7 +24,8 @@ inline fun loadConfiguration(args: Array = arrayOf()): addResourceSource("/application.yaml", optional = true) } } - }.build() + } + .build() .loadConfigOrThrow() data class AppConfiguration( @@ -57,9 +57,7 @@ data class TopicConfiguration( val error: String ) -enum class AppEnv( - val env: String -) { +enum class AppEnv(val env: String) { Unspecified(""), Local(Environment.local.name), Prod(Environment.prod.name) @@ -80,7 +78,11 @@ enum class AppEnv( } } - fun isLocal(): Boolean = this === Local + fun isLocal(): Boolean { + return this === Local + } - fun isProd(): Boolean = this === Prod + fun isProd(): Boolean { + return this === Prod + } } diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/ExampleAppConsumer.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/ExampleAppConsumer.kt index 4d57a0f5..c77da932 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/ExampleAppConsumer.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/ExampleAppConsumer.kt @@ -14,8 +14,7 @@ class ExampleAppConsumer( kafkaConsumer: KafkaConsumer ) { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - private val topics = config.kafka.topics.values - .fold(listOf()) { acc, topic -> acc + topic.topic + topic.error + topic.retry } + private val topics = config.kafka.topics.values.fold(listOf()) { acc, topic -> acc + topic.topic + topic.error + topic.retry } private val subscription = kafkaConsumer .apply { subscribe(topics) } diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/LockProvider.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/LockProvider.kt index 0a8adee6..634dcb62 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/LockProvider.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/LockProvider.kt @@ -18,7 +18,9 @@ class MutexLockProvider : LockProvider { override suspend fun acquireLock( name: String, duration: Duration - ): Boolean = mutex.tryLock(this) + ): Boolean { + return mutex.tryLock(this) + } override suspend fun releaseLock(name: String) { mutex.unlock(this) diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/UpdateProductRequest.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/UpdateProductRequest.kt index a54bc9d0..0473b511 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/UpdateProductRequest.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/application/UpdateProductRequest.kt @@ -3,6 +3,4 @@ package stove.ktor.example.application import kotlinx.serialization.Serializable @Serializable -data class UpdateProductRequest( - val name: String -) +data class UpdateProductRequest(val name: String) diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/Product.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/Product.kt index eed0da10..c27f4a8b 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/Product.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/Product.kt @@ -6,8 +6,5 @@ data class Product( ) object DomainEvents { - data class ProductUpdated( - val id: Int, - val name: String - ) + data class ProductUpdated(val id: Int, val name: String) } diff --git a/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/ProductRepository.kt b/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/ProductRepository.kt index ef38598f..bad3e94a 100644 --- a/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/ProductRepository.kt +++ b/examples/ktor-example/src/main/kotlin/stove/ktor/example/domain/ProductRepository.kt @@ -6,26 +6,20 @@ import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactive.awaitSingle -class ProductRepository( - private val postgresqlConnectionFactory: PostgresqlConnectionFactory -) { +class ProductRepository(private val postgresqlConnectionFactory: PostgresqlConnectionFactory) { private lateinit var connection: PostgresqlConnection - suspend fun findById(id: Int): Product = connection - .createStatement( - "SELECT * FROM Products WHERE id=$id" - ).execute() - .awaitFirst() - .map { r, rm -> + suspend fun findById(id: Int): Product { + return connection.createStatement("SELECT * FROM Products WHERE id=$id").execute().awaitFirst().map { r, rm -> Product( (r.get(Product::id.name, rm.getColumnMetadata(Product::id.name).javaType!!) as Int).toInt(), r.get(Product::name.name, rm.getColumnMetadata(Product::name.name).javaType!!) as String ) }.awaitSingle() + } suspend fun update(product: Product) { - connection - .createStatement("UPDATE Products SET ${Product::name.name}=('${product.name}') WHERE ${Product::id.name}=${product.id}") + connection.createStatement("UPDATE Products SET ${Product::name.name}=('${product.name}') WHERE ${Product::id.name}=${product.id}") .execute() .awaitFirstOrNull() } diff --git a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/ExampleTest.kt b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/ExampleTest.kt index 5bcb8448..5930e2f2 100644 --- a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/ExampleTest.kt +++ b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/ExampleTest.kt @@ -13,68 +13,67 @@ import stove.ktor.example.domain.* import kotlin.random.Random import kotlin.time.Duration.Companion.seconds -class ExampleTest : - FunSpec({ - data class ProductOfTest( - val id: Long, - val name: String - ) +class ExampleTest : FunSpec({ + data class ProductOfTest( + val id: Long, + val name: String + ) - test("should save the product") { - validate { - val givenId = Random.nextInt() - val givenName = "T-Shirt, Red, M" - postgresql { - shouldExecute( - """ + test("should save the product") { + validate { + val givenId = Random.nextInt() + val givenName = "T-Shirt, Red, M" + postgresql { + shouldExecute( + """ DROP TABLE IF EXISTS Products; CREATE TABLE IF NOT EXISTS Products ( id serial PRIMARY KEY, name VARCHAR (50) NOT NULL ); - """.trimIndent() - ) - shouldExecute("INSERT INTO Products (id, name) VALUES ('$givenId', 'T-Shirt, Red, S')") - } - http { - postAndExpectBodilessResponse( - "/products/$givenId", - body = UpdateProductRequest(givenName).some(), - token = None - ) { actual -> - actual.status shouldBe 200 - } + """.trimIndent() + ) + shouldExecute("INSERT INTO Products (id, name) VALUES ('$givenId', 'T-Shirt, Red, S')") + } + http { + postAndExpectBodilessResponse( + "/products/$givenId", + body = UpdateProductRequest(givenName).some(), + token = None + ) { actual -> + actual.status shouldBe 200 } + } - postgresql { - shouldQuery("Select * FROM Products WHERE id=$givenId", mapper = { row -> - ProductOfTest(row.getLong("id"), row.getString("name")) - }) { - it.count() shouldBe 1 - it.first().name shouldBe givenName - } + postgresql { + shouldQuery("Select * FROM Products WHERE id=$givenId", mapper = { row -> + ProductOfTest(row.getLong("id"), row.getString("name")) + }) { + it.count() shouldBe 1 + it.first().name shouldBe givenName } + } - using { - this.findById(givenId) shouldBe Product(givenId, givenName) - } + using { + this.findById(givenId) shouldBe Product(givenId, givenName) + } - kafka { - shouldBePublished(20.seconds) { - actual.id == givenId && actual.name == givenName - } - shouldBeConsumed(20.seconds) { - actual.id == givenId && actual.name == givenName - } + kafka { + shouldBePublished(20.seconds) { + actual.id == givenId && actual.name == givenName + } + shouldBeConsumed(20.seconds) { + actual.id == givenId && actual.name == givenName } } } + } - test("stove should be able to override the test deps") { - validate { - using { - (this is NoOpLockProvider) shouldBe true - } + test("stove should be able to override the test deps") { + validate { + using { + (this is NoOpLockProvider) shouldBe true } } - }) + } +}) diff --git a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt deleted file mode 100644 index 93bffdff..00000000 --- a/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/Stove.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.stove.ktor.example.e2e - -import com.trendol.stove.testing.e2e.rdbms.postgres.* -import com.trendyol.stove.testing.e2e.* -import com.trendyol.stove.testing.e2e.http.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde -import com.trendyol.stove.testing.e2e.standalone.kafka.* -import com.trendyol.stove.testing.e2e.system.TestSystem -import io.kotest.core.config.AbstractProjectConfig -import io.kotest.core.extensions.Extension -import io.kotest.extensions.system.SystemEnvironmentProjectListener -import stove.ktor.example.app.objectMapperRef - -class Stove : AbstractProjectConfig() { - companion object { - init { - stoveKafkaBridgePortDefault = "50053" - } - } - - override fun extensions(): List = listOf( - SystemEnvironmentProjectListener(STOVE_KAFKA_BRIDGE_PORT, stoveKafkaBridgePortDefault) - ) - - override suspend fun beforeProject() = TestSystem() - .with { - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8080" - ) - } - bridge() - postgresql { - PostgresqlOptions(configureExposedConfiguration = { cfg -> - listOf( - "database.jdbcUrl=${cfg.jdbcUrl}", - "database.host=${cfg.host}", - "database.port=${cfg.port}", - "database.username=${cfg.username}", - "database.password=${cfg.password}" - ) - }) - } - kafka { - KafkaSystemOptions( - serde = StoveSerde.jackson.anyByteArraySerde(objectMapperRef) - ) { - listOf( - "kafka.bootstrapServers=${it.bootstrapServers}", - "kafka.interceptorClasses=${it.interceptorClass}" - ) - } - } - ktor( - withParameters = listOf( - "port=8080" - ), - runner = { parameters -> - stove.ktor.example.run(parameters) { - addTestSystemDependencies() - } - } - ) - }.run() - - override suspend fun afterProject() { - TestSystem.stop() - } -} 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/TestSystemConfig.kt new file mode 100644 index 00000000..39914ebf --- /dev/null +++ b/examples/ktor-example/src/test/kotlin/com/stove/ktor/example/e2e/TestSystemConfig.kt @@ -0,0 +1,66 @@ +package com.stove.ktor.example.e2e + +import com.trendol.stove.testing.e2e.rdbms.postgres.* +import com.trendyol.stove.testing.e2e.* +import com.trendyol.stove.testing.e2e.http.* +import com.trendyol.stove.testing.e2e.standalone.kafka.* +import com.trendyol.stove.testing.e2e.system.TestSystem +import io.kotest.core.config.AbstractProjectConfig +import io.kotest.core.extensions.Extension +import io.kotest.extensions.system.SystemEnvironmentProjectListener +import stove.ktor.example.app.objectMapperRef + +class TestSystemConfig : AbstractProjectConfig() { + companion object { + init { + stoveKafkaBridgePortDefault = "50053" + } + } + + override fun extensions(): List = listOf( + SystemEnvironmentProjectListener(STOVE_KAFKA_BRIDGE_PORT, stoveKafkaBridgePortDefault) + ) + + override suspend fun beforeProject() = TestSystem().with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8080" + ) + } + bridge() + postgresql { + PostgresqlOptions(configureExposedConfiguration = { cfg -> + listOf( + "database.jdbcUrl=${cfg.jdbcUrl}", + "database.host=${cfg.host}", + "database.port=${cfg.port}", + "database.username=${cfg.username}", + "database.password=${cfg.password}" + ) + }) + } + kafka { + stoveKafkaObjectMapperRef = objectMapperRef + KafkaSystemOptions { + listOf( + "kafka.bootstrapServers=${it.bootstrapServers}", + "kafka.interceptorClasses=${it.interceptorClass}" + ) + } + } + ktor( + withParameters = listOf( + "port=8080" + ), + runner = { parameters -> + stove.ktor.example.run(parameters) { + addTestSystemDependencies() + } + } + ) + }.run() + + override suspend fun afterProject() { + TestSystem.stop() + } +} diff --git a/examples/micronaut-example/.gitignore b/examples/micronaut-example/.gitignore new file mode 100644 index 00000000..5a03bc30 --- /dev/null +++ b/examples/micronaut-example/.gitignore @@ -0,0 +1,15 @@ +Thumbs.db +.DS_Store +.gradle +build/ +target/ +out/ +.micronaut/ +.idea +*.iml +*.ipr +*.iws +.project +.settings +.classpath +.factorypath diff --git a/examples/micronaut-example/build.gradle.kts b/examples/micronaut-example/build.gradle.kts new file mode 100644 index 00000000..2f049d5e --- /dev/null +++ b/examples/micronaut-example/build.gradle.kts @@ -0,0 +1,81 @@ +@file:Suppress("UnstableApiUsage", "DSL_SCOPE_VIOLATION") + +plugins { + kotlin("jvm") version libs.versions.kotlin + id("org.jetbrains.kotlin.plugin.allopen") version libs.versions.kotlin + kotlin("plugin.serialization") version libs.versions.kotlin + application + idea + id("com.google.devtools.ksp") version "1.9.25-1.0.20" + id("com.github.johnrengelman.shadow") version "8.1.1" + id("io.micronaut.application") version "4.4.3" + id("io.micronaut.aot") version "4.4.3" +} + +repositories { + mavenCentral() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } +} + +dependencies { + runtimeOnly("org.yaml:snakeyaml:2.1") + implementation(platform("io.micronaut.platform:micronaut-parent:4.7.1")) + ksp("io.micronaut:micronaut-http-validation") + ksp("io.micronaut.serde:micronaut-serde-processor") + implementation("io.micronaut.kotlin:micronaut-kotlin-runtime") + implementation("io.micronaut.serde:micronaut-serde-jackson") + implementation("io.micronaut:micronaut-http-client") + implementation("io.micronaut:micronaut-http-server-netty") + implementation("io.micronaut:micronaut-inject") + implementation("io.micronaut:micronaut-core") + runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("com.couchbase.client:metrics-micrometer:0.7.5") + implementation("io.micronaut.configuration:micronaut-micrometer-core:1.3.1") + implementation("org.apache.kafka:kafka-clients") + implementation(libs.kotlinx.reactor) + implementation(libs.kotlinx.core) + implementation(libs.kotlinx.reactive) + implementation(libs.couchbase.client) + implementation(libs.couchbase.client.metrics) + implementation(libs.jackson.kotlin) + implementation(libs.kotlinx.slf4j) +} + +dependencies { + testImplementation(libs.kotest.property) + testImplementation(libs.kotest.runner.junit5) + testImplementation(projects.stove.lib.stoveTestingE2eHttp) + testImplementation(projects.stove.lib.stoveTestingE2eWiremock) + testImplementation(projects.stove.lib.stoveTestingE2eCouchbase) + testImplementation(projects.stove.lib.stoveTestingE2eElasticsearch) + testImplementation(projects.stove.starters.micronautStarter.stoveMicronautTestingE2e) +} + +application { + mainClass = "stove.micronaut.example.ApplicationKt" +} + +graalvmNative.toolchainDetection = false + +java { + sourceCompatibility = JavaVersion.toVersion("17") +} + +micronaut { + runtime("netty") + testRuntime("kotest5") + processing { + incremental(true) + annotations("stove.micronaut.example.*") + } + aot { + optimizeServiceLoading = false + convertYamlToJava = false + precomputeOperations = true + cacheEnvironment = true + optimizeClassLoading = true + deduceEnvironment = true + optimizeNetty = true + replaceLogbackXml = true + } +} diff --git a/examples/micronaut-example/gradle/wrapper/gradle-wrapper.jar b/examples/micronaut-example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/examples/micronaut-example/gradle/wrapper/gradle-wrapper.properties b/examples/micronaut-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..df97d72b --- /dev/null +++ b/examples/micronaut-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/Application.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/Application.kt new file mode 100644 index 00000000..1141cb95 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/Application.kt @@ -0,0 +1,28 @@ +package stove.micronaut.example + +import io.micronaut.context.ApplicationContext +import io.micronaut.runtime.EmbeddedApplication + +fun main(args: Array) { + run(args) +} + +fun run( + args: Array, + init: ApplicationContext.() -> Unit = {} +): ApplicationContext { + val context = ApplicationContext + .builder() + .args(*args) + .build() + .also(init) + .start() + + context.findBean(EmbeddedApplication::class.java).ifPresent { app -> + if (!app.isRunning) { + app.start() + } + } + + return context +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/domain/Product.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/domain/Product.kt new file mode 100644 index 00000000..53fb4ad0 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/domain/Product.kt @@ -0,0 +1,26 @@ +package stove.micronaut.example.application.domain + +import io.micronaut.serde.annotation.Serdeable +import java.util.* + +@Serdeable +data class Product( + val id: String, + val name: String, + val supplierId: Long, + val isBlacklist: Boolean, + val createdDate: Date +) { + companion object { + + fun new(id: String, name: String, supplierId: Long, isBlacklist: Boolean): Product { + return Product( + id = id, + name = name, + supplierId = supplierId, + createdDate = Date(), + isBlacklist = isBlacklist + ) + } + } +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/repository/ProductRepository.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/repository/ProductRepository.kt new file mode 100644 index 00000000..d78f2b56 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/repository/ProductRepository.kt @@ -0,0 +1,8 @@ +package stove.micronaut.example.application.repository + +import stove.micronaut.example.application.domain.Product + +interface ProductRepository { + suspend fun save(product: Product): Product + suspend fun findById(id: Long): Product? +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/ProductService.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/ProductService.kt new file mode 100644 index 00000000..ee77a9a9 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/ProductService.kt @@ -0,0 +1,19 @@ +package stove.micronaut.example.application.services + +import jakarta.inject.Singleton +import stove.micronaut.example.application.domain.Product +import stove.micronaut.example.application.repository.ProductRepository +import stove.micronaut.example.infrastructure.http.SupplierHttpService + +@Singleton +class ProductService( + private val productRepository: ProductRepository, + private val supplierHttpService: SupplierHttpService +) { + suspend fun createProduct(id: String, productName: String, supplierId: Long): Product { + val supplier = supplierHttpService.getSupplierPermission(supplierId) + val product = Product.new(id, productName, supplierId, supplier!!.isBlacklisted) + productRepository.save(product) + return product + } +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/SupplierService.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/SupplierService.kt new file mode 100644 index 00000000..c80f2bf4 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/application/services/SupplierService.kt @@ -0,0 +1,13 @@ +package stove.micronaut.example.application.services + +import io.micronaut.serde.annotation.Serdeable + +@Serdeable +data class SupplierPermission( + val id: Long, + val isBlacklisted: Boolean +) + +interface SupplierService { + suspend fun getSupplierPermission(supplierId: Long): SupplierPermission? +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/ProductController.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/ProductController.kt new file mode 100644 index 00000000..ba3f925b --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/ProductController.kt @@ -0,0 +1,25 @@ +package stove.micronaut.example.infrastructure.api + +import io.micronaut.http.annotation.* +import stove.micronaut.example.application.domain.Product +import stove.micronaut.example.application.services.ProductService +import stove.micronaut.example.infrastructure.api.model.request.CreateProductRequest + +@Controller("/products") +class ProductController( + private val productService: ProductService +) { + @Get("/index") + fun get( + @QueryValue keyword: String = "default" + ): String = "Hi from Stove framework with $keyword" + + @Post("/create") + suspend fun createProduct( + @Body request: CreateProductRequest + ): Product = productService.createProduct( + id = request.id, + productName = request.name, + supplierId = request.supplierId + ) +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/model/request/CreateProductRequest.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/model/request/CreateProductRequest.kt new file mode 100644 index 00000000..9925661a --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/api/model/request/CreateProductRequest.kt @@ -0,0 +1,10 @@ +package stove.micronaut.example.infrastructure.api.model.request + +import io.micronaut.serde.annotation.Serdeable + +@Serdeable +data class CreateProductRequest( + val id: String, + val name: String, + val supplierId: Long +) diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseConfiguration.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseConfiguration.kt new file mode 100644 index 00000000..43814989 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseConfiguration.kt @@ -0,0 +1,64 @@ +package stove.micronaut.example.infrastructure.couchbase + +import com.couchbase.client.java.* +import com.couchbase.client.java.Collection +import com.couchbase.client.java.codec.JacksonJsonSerializer +import com.couchbase.client.java.env.ClusterEnvironment +import com.couchbase.client.java.json.JsonValueModule +import com.fasterxml.jackson.databind.ObjectMapper +import io.micronaut.context.annotation.* +import jakarta.annotation.PreDestroy +import jakarta.inject.Singleton +import java.time.Duration + +@Factory +class CouchbaseConfiguration( + private val couchbaseProperties: CouchbaseProperties +) { + companion object { + val objectMapper: ObjectMapper = + ObjectMapper() + .findAndRegisterModules() + .registerModule(JsonValueModule()) + } + + @Primary + @Context + fun clusterEnvironment(): ClusterEnvironment { + val cbSerializer = JacksonJsonSerializer.create(objectMapper) + return ClusterEnvironment + .builder() + .timeoutConfig { + it + .kvTimeout(Duration.ofMillis(couchbaseProperties.kvTimeout)) + .connectTimeout(Duration.ofMillis(couchbaseProperties.connectTimeout)) + .queryTimeout(Duration.ofMillis(couchbaseProperties.queryTimeout)) + .viewTimeout(Duration.ofMillis(couchbaseProperties.viewTimeout)) + }.jsonSerializer(cbSerializer) + .build() + } + + @Primary + @Singleton + fun cluster(clusterEnvironment: ClusterEnvironment): Cluster { + val clusterOptions = ClusterOptions + .clusterOptions(couchbaseProperties.username, couchbaseProperties.password) + .environment(clusterEnvironment) + + return Cluster.connect(couchbaseProperties.hosts.joinToString(","), clusterOptions) + } + + @Primary + @Singleton + fun bucket(cluster: Cluster): Bucket = cluster.bucket(couchbaseProperties.bucketName) + + @Primary + @Singleton + fun productCouchbaseCollection(bucket: Bucket): Collection = bucket.defaultCollection() + + @PreDestroy + fun cleanup(cluster: Cluster, clusterEnvironment: ClusterEnvironment) { + cluster.disconnect() + clusterEnvironment.shutdown() + } +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseProperties.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseProperties.kt new file mode 100644 index 00000000..75e5918a --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/CouchbaseProperties.kt @@ -0,0 +1,15 @@ +package stove.micronaut.example.infrastructure.couchbase + +import io.micronaut.context.annotation.* + +@ConfigurationProperties("couchbase") +class CouchbaseProperties { + var username: String? = null + var password: String? = null + var bucketName: String = "" + var hosts: List = listOf() + var kvTimeout: Long = 0 + var connectTimeout: Long = 0 + var queryTimeout: Long = 0 + var viewTimeout: Long = 0 +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/ObjectMapperConfig.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/ObjectMapperConfig.kt new file mode 100644 index 00000000..8df6bae9 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/couchbase/ObjectMapperConfig.kt @@ -0,0 +1,29 @@ +package stove.micronaut.example.infrastructure.couchbase + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import io.micronaut.context.annotation.Bean +import io.micronaut.context.annotation.Factory + +@Factory +class ObjectMapperConfig { + + companion object { + fun createObjectMapperWithDefaults(): ObjectMapper { + val isoInstantModule = SimpleModule() + return ObjectMapper() + .registerModule(KotlinModule.Builder().build()) + .registerModule(isoInstantModule) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } + } + + @Bean + fun objectMapper(): ObjectMapper { + return createObjectMapperWithDefaults() + } +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/http/SupplierHttpService.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/http/SupplierHttpService.kt new file mode 100644 index 00000000..362914d2 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/http/SupplierHttpService.kt @@ -0,0 +1,28 @@ +package stove.micronaut.example.infrastructure.http + +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.annotation.Client +import io.micronaut.websocket.exceptions.WebSocketClientException +import jakarta.inject.Singleton +import stove.micronaut.example.application.services.SupplierPermission +import stove.micronaut.example.application.services.SupplierService + +@Singleton +class SupplierHttpService( + private val supplierHttpClient: SupplierHttpClient +) : SupplierService { + override suspend fun getSupplierPermission(supplierId: Long): SupplierPermission? = try { + val response = supplierHttpClient.getSupplierPermission(supplierId) + println("API Response: $response") // Yanıtı konsola yazdır + response + } catch (e: WebSocketClientException) { + println("Error fetching supplier permission: ${e.message}") + null + } +} + +@Client(id = "lookup-api") +interface SupplierHttpClient { + @Get("/v2/suppliers/{supplierId}?storeFrontId=1") + suspend fun getSupplierPermission(supplierId: Long): SupplierPermission +} diff --git a/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/persistence/ProductCBRepository.kt b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/persistence/ProductCBRepository.kt new file mode 100644 index 00000000..61eecc63 --- /dev/null +++ b/examples/micronaut-example/src/main/kotlin/stove/micronaut/example/infrastructure/persistence/ProductCBRepository.kt @@ -0,0 +1,18 @@ +package stove.micronaut.example.infrastructure.persistence + +import com.couchbase.client.java.Collection +import jakarta.inject.Singleton +import stove.micronaut.example.application.domain.Product +import stove.micronaut.example.application.repository.ProductRepository + +@Singleton +class ProductCBRepository( + private val productCouchbaseCollection: Collection +) : ProductRepository { + override suspend fun save(product: Product): Product { + productCouchbaseCollection.insert(product.id, product) + return product + } + + override suspend fun findById(id: Long): Product? = productCouchbaseCollection.get(id.toString()).contentAs(Product::class.java) +} diff --git a/examples/micronaut-example/src/main/resources/application.yml b/examples/micronaut-example/src/main/resources/application.yml new file mode 100644 index 00000000..d645d6e0 --- /dev/null +++ b/examples/micronaut-example/src/main/resources/application.yml @@ -0,0 +1,28 @@ +micronaut: + application: + name: micronaut-example + server: + port: 8080 + http: + services: + lookup-api: + url: http://localhost:7078 + connect-timeout: 2s + read-timeout: 22s + +micrometer: + metrics: + enabled: true + common-tags: + application: "micronaut-example" + +couchbase: + hosts: localhost + username: username + password: password + bucketName: Stove + kvTimeout: 3000 + connectTimeout: 15000 + queryTimeout: 5000 + viewTimeout: 5000 + diff --git a/examples/micronaut-example/src/main/resources/logback.xml b/examples/micronaut-example/src/main/resources/logback.xml new file mode 100644 index 00000000..2d77bdab --- /dev/null +++ b/examples/micronaut-example/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/ProductControllerTest.kt b/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/ProductControllerTest.kt new file mode 100644 index 00000000..fb242004 --- /dev/null +++ b/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/ProductControllerTest.kt @@ -0,0 +1,59 @@ +package com.stove.micronaut.example.e2e + +import arrow.core.some +import com.trendyol.stove.testing.e2e.couchbase.couchbase +import com.trendyol.stove.testing.e2e.http.http +import com.trendyol.stove.testing.e2e.system.TestSystem +import com.trendyol.stove.testing.e2e.wiremock.wiremock +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import stove.micronaut.example.application.domain.Product +import stove.micronaut.example.application.services.SupplierPermission +import stove.micronaut.example.infrastructure.api.model.request.CreateProductRequest + +class ProductControllerTest : + FunSpec({ + + test("index should be reachable") { + TestSystem.validate { + http { + get("/products/index", queryParams = mapOf("keyword" to "index")) { actual -> + actual shouldContain "Hi from Stove framework with index" + println(actual) + } + } + } + } + + test("should create a product in Couchbase when a product is created") { + val id = "RANDOM0001" + val request = CreateProductRequest(id = id, name = "Deneme", supplierId = 120688) + val supplierMock = SupplierPermission(id = 120688, isBlacklisted = false) + + TestSystem.validate { + + wiremock { + mockGet( + "/v2/suppliers/${supplierMock.id}?storeFrontId=1", + statusCode = 200, + responseBody = supplierMock.some() + ) + } + http { + postAndExpectJson("/products/create", body = request.some()) { actual -> + actual.supplierId shouldBe 120688 + actual.name shouldBe "Deneme" + } + } + couchbase { + shouldGet(request.id) { + it.name shouldBe request.name + it.id shouldBe request.id + it.supplierId shouldBe request.supplierId + it.isBlacklist shouldBe false + } + } + } + } + }) diff --git a/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/TestSystemConfig.kt b/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/TestSystemConfig.kt new file mode 100644 index 00000000..850b2188 --- /dev/null +++ b/examples/micronaut-example/src/test/kotlin/com/stove/micronaut/example/e2e/TestSystemConfig.kt @@ -0,0 +1,66 @@ +package com.stove.micronaut.example.e2e + +import com.trendyol.stove.testing.* +import com.trendyol.stove.testing.e2e.couchbase.* +import com.trendyol.stove.testing.e2e.http.* +import com.trendyol.stove.testing.e2e.system.TestSystem +import com.trendyol.stove.testing.e2e.wiremock.* +import io.kotest.core.config.AbstractProjectConfig +import org.slf4j.* +import stove.micronaut.example.infrastructure.couchbase.ObjectMapperConfig + +class TestSystemConfig : AbstractProjectConfig() { + private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor") + + @Suppress("LongMethod") + override suspend fun beforeProject() { + val objectMapper = ObjectMapperConfig.createObjectMapperWithDefaults() + TestSystem() + .with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8080" + ) + } + couchbase { + CouchbaseSystemOptions( + "Stove", + objectMapper = objectMapper, + containerOptions = CouchbaseContainerOptions(tag = "latest") { + withStartupAttempts(3) + }, + configureExposedConfiguration = { cfg -> + listOf( + "couchbase.hosts=${cfg.hostsWithPort}", + "couchbase.username=${cfg.username}", + "couchbase.password=${cfg.password}" + ) + } + ) + } + bridge() + wiremock { + WireMockSystemOptions( + port = 7078, + removeStubAfterRequestMatched = true, + afterRequest = { e, _ -> + logger.info(e.request.toString()) + } + ) + } + micronaut( + runner = { parameters -> + stove.micronaut.example.run(parameters) { + } + }, + withParameters = listOf( + "server.port=8080", + "logging.level.root=info", + "logging.level.org.springframework.web=info" + ) + ) + }.run() + } + + override suspend fun afterProject(): Unit = TestSystem.stop() +} diff --git a/examples/spring-example/api/spring-example.api b/examples/spring-example/api/spring-example.api deleted file mode 100644 index e9c5550f..00000000 --- a/examples/spring-example/api/spring-example.api +++ /dev/null @@ -1,417 +0,0 @@ -public class stove/spring/example/ExampleApp { - public fun ()V -} - -public final class stove/spring/example/ExampleAppKt { - public static final fun main ([Ljava/lang/String;)V - public static final fun run ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lorg/springframework/context/ConfigurableApplicationContext; - public static synthetic fun run$default ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/springframework/context/ConfigurableApplicationContext; -} - -public final class stove/spring/example/application/handlers/ProductCreateRequest { - public fun (JLjava/lang/String;J)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun copy (JLjava/lang/String;J)Lstove/spring/example/application/handlers/ProductCreateRequest; - public static synthetic fun copy$default (Lstove/spring/example/application/handlers/ProductCreateRequest;JLjava/lang/String;JILjava/lang/Object;)Lstove/spring/example/application/handlers/ProductCreateRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/example/application/handlers/ProductCreatedEvent { - public fun (JLjava/lang/String;JLjava/time/Instant;Ljava/lang/String;)V - public synthetic fun (JLjava/lang/String;JLjava/time/Instant;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun component4 ()Ljava/time/Instant; - public final fun component5 ()Ljava/lang/String; - public final fun copy (JLjava/lang/String;JLjava/time/Instant;Ljava/lang/String;)Lstove/spring/example/application/handlers/ProductCreatedEvent; - public static synthetic fun copy$default (Lstove/spring/example/application/handlers/ProductCreatedEvent;JLjava/lang/String;JLjava/time/Instant;Ljava/lang/String;ILjava/lang/Object;)Lstove/spring/example/application/handlers/ProductCreatedEvent; - public fun equals (Ljava/lang/Object;)Z - public final fun getCreatedDate ()Ljava/time/Instant; - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/example/application/handlers/ProductCreator { - public field productCreatedTopic Ljava/lang/String; - public fun (Lstove/spring/example/infrastructure/http/SupplierHttpService;Lcom/couchbase/client/java/ReactiveCollection;Lcom/fasterxml/jackson/databind/ObjectMapper;Lstove/spring/example/infrastructure/messaging/kafka/KafkaProducer;)V - public fun create (Lstove/spring/example/application/handlers/ProductCreateRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun getProductCreatedTopic ()Ljava/lang/String; - public fun setProductCreatedTopic (Ljava/lang/String;)V -} - -public final class stove/spring/example/application/handlers/ProductCreatorKt { - public static final fun mapToCreateRequest (Lstove/spring/example/infrastructure/messaging/kafka/consumers/CreateProductCommand;)Lstove/spring/example/application/handlers/ProductCreateRequest; - public static final fun mapToProductCreatedEvent (Lstove/spring/example/application/handlers/ProductCreateRequest;)Lstove/spring/example/application/handlers/ProductCreatedEvent; -} - -public final class stove/spring/example/application/services/SupplierPermission { - public fun (JZ)V - public final fun component1 ()J - public final fun component2 ()Z - public final fun copy (JZ)Lstove/spring/example/application/services/SupplierPermission; - public static synthetic fun copy$default (Lstove/spring/example/application/services/SupplierPermission;JZILjava/lang/Object;)Lstove/spring/example/application/services/SupplierPermission; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public fun hashCode ()I - public final fun isAllowed ()Z - public fun toString ()Ljava/lang/String; -} - -public abstract interface class stove/spring/example/application/services/SupplierService { - public abstract fun getSupplierPermission (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class stove/spring/example/infrastructure/Defaults { - public static final field AGENT_NAME Ljava/lang/String; - public static final field INSTANCE Lstove/spring/example/infrastructure/Defaults; - public static final field USER_EMAIL Ljava/lang/String; - public final fun getHOST_NAME ()Ljava/lang/String; -} - -public final class stove/spring/example/infrastructure/Headers { - public static final field AGENT_NAME_KEY Ljava/lang/String; - public static final field CORRELATION_ID_KEY Ljava/lang/String; - public static final field EVENT_TYPE Ljava/lang/String; - public static final field HOST_KEY Ljava/lang/String; - public static final field INSTANCE Lstove/spring/example/infrastructure/Headers; - public static final field MESSAGE_ID_KEY Ljava/lang/String; - public static final field PUBLISHED_DATE_KEY Ljava/lang/String; - public static final field USER_EMAIL_KEY Ljava/lang/String; - public final fun getOrDefault (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; - public static synthetic fun getOrDefault$default (Lstove/spring/example/infrastructure/Headers;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; -} - -public class stove/spring/example/infrastructure/ObjectMapperConfig { - public static final field Companion Lstove/spring/example/infrastructure/ObjectMapperConfig$Companion; - public fun ()V - public fun jacksonCustomizer ()Lorg/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer; - public fun objectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/spring/example/infrastructure/ObjectMapperConfig$Companion { - public final fun default ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public class stove/spring/example/infrastructure/api/ProductController { - public fun (Lstove/spring/example/application/handlers/ProductCreator;)V - public fun createProduct (Lstove/spring/example/application/handlers/ProductCreateRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun importFile (Ljava/lang/String;Lorg/springframework/http/codec/multipart/FilePart;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public class stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration { - public static final field Companion Lstove/spring/example/infrastructure/couchbase/CouchbaseConfiguration$Companion; - public fun (Lstove/spring/example/infrastructure/couchbase/CouchbaseProperties;Lio/micrometer/core/instrument/MeterRegistry;)V - public fun bucket (Lcom/couchbase/client/java/ReactiveCluster;)Lcom/couchbase/client/java/ReactiveBucket; - public fun cluster (Lcom/couchbase/client/java/env/ClusterEnvironment;)Lcom/couchbase/client/java/ReactiveCluster; - public fun clusterEnvironment ()Lcom/couchbase/client/java/env/ClusterEnvironment; - public fun collection (Lcom/couchbase/client/java/ReactiveBucket;)Lcom/couchbase/client/java/ReactiveCollection; -} - -public final class stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration$Companion { - public final fun getObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/spring/example/infrastructure/couchbase/CouchbaseProperties { - public fun ()V - public final fun getBucketName ()Ljava/lang/String; - public final fun getConnectTimeout ()J - public final fun getHosts ()Ljava/util/List; - public final fun getKvTimeout ()J - public final fun getPassword ()Ljava/lang/String; - public final fun getQueryTimeout ()J - public final fun getUsername ()Ljava/lang/String; - public final fun getViewTimeout ()J - public final fun setBucketName (Ljava/lang/String;)V - public final fun setConnectTimeout (J)V - public final fun setHosts (Ljava/util/List;)V - public final fun setKvTimeout (J)V - public final fun setPassword (Ljava/lang/String;)V - public final fun setQueryTimeout (J)V - public final fun setUsername (Ljava/lang/String;)V - public final fun setViewTimeout (J)V -} - -public final class stove/spring/example/infrastructure/http/ClientConfigurationProperty { - public fun ()V - public fun (Ljava/lang/String;Ljava/net/URI;IJ)V - public synthetic fun (Ljava/lang/String;Ljava/net/URI;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/net/URI; - public final fun component3 ()I - public final fun component4 ()J - public final fun copy (Ljava/lang/String;Ljava/net/URI;IJ)Lstove/spring/example/infrastructure/http/ClientConfigurationProperty; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;Ljava/lang/String;Ljava/net/URI;IJILjava/lang/Object;)Lstove/spring/example/infrastructure/http/ClientConfigurationProperty; - public fun equals (Ljava/lang/Object;)Z - public final fun getConnectTimeout ()I - public final fun getReadTimeout ()J - public final fun getUri ()Ljava/net/URI; - public final fun getUrl ()Ljava/lang/String; - public fun hashCode ()I - public final fun setConnectTimeout (I)V - public final fun setReadTimeout (J)V - public final fun setUrl (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/example/infrastructure/http/SupplierHttpService : stove/spring/example/application/services/SupplierService { - public fun (Lorg/springframework/web/reactive/function/client/WebClient;)V - public fun getSupplierPermission (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public class stove/spring/example/infrastructure/http/WebClientConfiguration { - public static final field Companion Lstove/spring/example/infrastructure/http/WebClientConfiguration$Companion; - public fun (Lstove/spring/example/infrastructure/http/WebClientConfigurationProperties;)V - public fun exchangeStrategies (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lorg/springframework/web/reactive/function/client/ExchangeStrategies; - public fun supplierHttpClient (Lorg/springframework/web/reactive/function/client/ExchangeStrategies;)Lorg/springframework/web/reactive/function/client/WebClient; - public fun webClientObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/spring/example/infrastructure/http/WebClientConfiguration$Companion { -} - -public final class stove/spring/example/infrastructure/http/WebClientConfigurationProperties { - public fun ()V - public fun (Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;)V - public synthetic fun (Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lstove/spring/example/infrastructure/http/ClientConfigurationProperty; - public final fun copy (Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;)Lstove/spring/example/infrastructure/http/WebClientConfigurationProperties; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/http/WebClientConfigurationProperties;Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;ILjava/lang/Object;)Lstove/spring/example/infrastructure/http/WebClientConfigurationProperties; - public fun equals (Ljava/lang/Object;)Z - public final fun getSupplierHttp ()Lstove/spring/example/infrastructure/http/ClientConfigurationProperty; - public fun hashCode ()I - public final fun setSupplierHttp (Lstove/spring/example/infrastructure/http/ClientConfigurationProperty;)V - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/KafkaOutgoingMessage { - public fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Object; - public final fun component3 ()Ljava/lang/Object; - public final fun component4 ()Ljava/util/Map; - public final fun component5 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;)Lstove/spring/example/infrastructure/messaging/kafka/KafkaOutgoingMessage; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/messaging/kafka/KafkaOutgoingMessage;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;ILjava/lang/Object;)Lstove/spring/example/infrastructure/messaging/kafka/KafkaOutgoingMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Ljava/util/Map; - public final fun getKey ()Ljava/lang/Object; - public final fun getPartition ()Ljava/lang/Integer; - public final fun getPayload ()Ljava/lang/Object; - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/example/infrastructure/messaging/kafka/KafkaProducer { - public fun (Lorg/springframework/kafka/core/KafkaTemplate;)V - public fun send (Lstove/spring/example/infrastructure/messaging/kafka/KafkaOutgoingMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings : stove/spring/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { -} - -public class stove/spring/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings : stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings { - public static final field AUTO_COMMIT_INTERVAL J - public static final field Companion Lstove/spring/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings$Companion; - public static final field MAX_POLL_INTERVAL J - public static final field SESSION_TIMEOUT J - public fun (Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties;)V - public fun getKafkaProperties ()Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public fun settings ()Ljava/util/Map; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings$Companion { -} - -public class stove/spring/example/infrastructure/messaging/kafka/configuration/DefaultProducerSettings : stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings { - public fun (Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties;)V - public fun settings ()Ljava/util/Map; -} - -public class stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration { - public static final field Companion Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration$Companion; - public static final field INTERVAL J - public static final field LISTENER_BEAN_NAME Ljava/lang/String; - public static final field RETRY_LISTENER_BEAN_NAME Ljava/lang/String; - public fun (Lorg/springframework/kafka/listener/RecordInterceptor;)V - public fun consumerFactory (Lstove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings;)Lorg/springframework/kafka/core/ConsumerFactory; - public fun consumerRetryFactory (Lstove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings;)Lorg/springframework/kafka/core/ConsumerFactory; - public fun kafkaListenerContainerFactory (Lorg/springframework/kafka/core/ConsumerFactory;)Lorg/springframework/kafka/config/ConcurrentKafkaListenerContainerFactory; - public fun kafkaRetryListenerContainerFactory (Lorg/springframework/kafka/core/ConsumerFactory;)Lorg/springframework/kafka/config/ConcurrentKafkaListenerContainerFactory; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration$Companion { -} - -public class stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration { - public fun ()V - public fun kafkaTemplate (Lorg/springframework/kafka/core/ProducerFactory;)Lorg/springframework/kafka/core/KafkaTemplate; - public fun producer (Lorg/springframework/kafka/core/ProducerFactory;)Lorg/apache/kafka/clients/producer/Producer; - public fun producerFactory (Lstove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings;)Lorg/springframework/kafka/core/ProducerFactory; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties { - public static final field Companion Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Z - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()I - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Z - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZILjava/lang/Object;)Lstove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public final fun createClientId ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getAcks ()Ljava/lang/String; - public final fun getAutoCreateTopics ()Z - public final fun getBootstrapServers ()Ljava/lang/String; - public final fun getCompression ()Ljava/lang/String; - public final fun getDefaultApiTimeout ()Ljava/lang/String; - public final fun getHeartbeatInSeconds ()I - public final fun getMaxProducerConsumerBytes ()Ljava/lang/String; - public final fun getOffset ()Ljava/lang/String; - public final fun getRequestTimeout ()Ljava/lang/String; - public final fun getSecureKafka ()Z - public final fun getTopicPrefix ()Ljava/lang/String; - public fun hashCode ()I - public final fun setAcks (Ljava/lang/String;)V - public final fun setAutoCreateTopics (Z)V - public final fun setBootstrapServers (Ljava/lang/String;)V - public final fun setCompression (Ljava/lang/String;)V - public final fun setDefaultApiTimeout (Ljava/lang/String;)V - public final fun setHeartbeatInSeconds (I)V - public final fun setOffset (Ljava/lang/String;)V - public final fun setRequestTimeout (Ljava/lang/String;)V - public final fun setSecureKafka (Z)V - public final fun setTopicPrefix (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProperties$Companion { -} - -public abstract interface class stove/spring/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { - public abstract fun settings ()Ljava/util/Map; -} - -public abstract interface class stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings : stove/spring/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { -} - -public final class stove/spring/example/infrastructure/messaging/kafka/consumers/BusinessException : java/lang/RuntimeException { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lstove/spring/example/infrastructure/messaging/kafka/consumers/BusinessException; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/messaging/kafka/consumers/BusinessException;Ljava/lang/String;ILjava/lang/Object;)Lstove/spring/example/infrastructure/messaging/kafka/consumers/BusinessException; - public fun equals (Ljava/lang/Object;)Z - public fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/example/infrastructure/messaging/kafka/consumers/ConsumerConfig { - public fun ()V - public fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getEnabled ()Z - public fun getErrorTopicSuffix ()Ljava/lang/String; - public fun getGroupId ()Ljava/lang/String; - public fun getRetryTopicSuffix ()Ljava/lang/String; - public fun setEnabled (Z)V - public fun setErrorTopicSuffix (Ljava/lang/String;)V - public fun setGroupId (Ljava/lang/String;)V - public fun setRetryTopicSuffix (Ljava/lang/String;)V -} - -public final class stove/spring/example/infrastructure/messaging/kafka/consumers/CreateProductCommand { - public fun (JLjava/lang/String;J)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun copy (JLjava/lang/String;J)Lstove/spring/example/infrastructure/messaging/kafka/consumers/CreateProductCommand; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/messaging/kafka/consumers/CreateProductCommand;JLjava/lang/String;JILjava/lang/Object;)Lstove/spring/example/infrastructure/messaging/kafka/consumers/CreateProductCommand; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/consumers/FailingEvent { - public fun (J)V - public final fun component1 ()J - public final fun copy (J)Lstove/spring/example/infrastructure/messaging/kafka/consumers/FailingEvent; - public static synthetic fun copy$default (Lstove/spring/example/infrastructure/messaging/kafka/consumers/FailingEvent;JILjava/lang/Object;)Lstove/spring/example/infrastructure/messaging/kafka/consumers/FailingEvent; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer { - public fun ()V - public fun listen (Lorg/apache/kafka/clients/consumer/ConsumerRecord;)V -} - -public class stove/spring/example/infrastructure/messaging/kafka/consumers/ProductCreateEventTopicConfig : stove/spring/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V -} - -public class stove/spring/example/infrastructure/messaging/kafka/consumers/ProductFailingEventTopicConfig : stove/spring/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V -} - -public class stove/spring/example/infrastructure/messaging/kafka/consumers/ProductTransferConsumers { - public fun (Lstove/spring/example/application/handlers/ProductCreator;Lcom/fasterxml/jackson/databind/ObjectMapper;)V - public fun listen (Lorg/apache/kafka/clients/consumer/ConsumerRecord;)Ljava/lang/String; -} - -public abstract class stove/spring/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getErrorTopic ()Ljava/lang/String; - public final fun getRetryTopic ()Ljava/lang/String; - public final fun getTopic ()Ljava/lang/String; - public final fun setErrorTopic (Ljava/lang/String;)V - public final fun setRetryTopic (Ljava/lang/String;)V - public final fun setTopic (Ljava/lang/String;)V -} - -public class stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor : org/springframework/kafka/listener/RecordInterceptor { - public fun ()V - public fun intercept (Lorg/apache/kafka/clients/consumer/ConsumerRecord;Lorg/apache/kafka/clients/consumer/Consumer;)Lorg/apache/kafka/clients/consumer/ConsumerRecord; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor : org/apache/kafka/clients/producer/ProducerInterceptor { - public static final field Companion Lstove/spring/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor$Companion; - public fun ()V - public fun close ()V - public fun configure (Ljava/util/Map;)V - public fun onAcknowledgement (Lorg/apache/kafka/clients/producer/RecordMetadata;Ljava/lang/Exception;)V - public fun onSend (Lorg/apache/kafka/clients/producer/ProducerRecord;)Lorg/apache/kafka/clients/producer/ProducerRecord; -} - -public final class stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor$Companion { -} - diff --git a/examples/spring-example/build.gradle.kts b/examples/spring-example/build.gradle.kts index 3fea60fa..5a84f4ed 100644 --- a/examples/spring-example/build.gradle.kts +++ b/examples/spring-example/build.gradle.kts @@ -32,8 +32,4 @@ dependencies { testImplementation(projects.stove.starters.spring.stoveSpringTestingE2eKafka) } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.stove.spring.example.e2e.Stove") -} - application { mainClass.set("stove.spring.example.ExampleAppkt") } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/application/handlers/ProductCreator.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/application/handlers/ProductCreator.kt index 93c0c06e..0ea714cb 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/application/handlers/ProductCreator.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/application/handlers/ProductCreator.kt @@ -8,9 +8,10 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import stove.spring.example.infrastructure.Headers import stove.spring.example.infrastructure.http.SupplierHttpService -import stove.spring.example.infrastructure.messaging.kafka.* +import stove.spring.example.infrastructure.messaging.kafka.KafkaOutgoingMessage +import stove.spring.example.infrastructure.messaging.kafka.KafkaProducer import stove.spring.example.infrastructure.messaging.kafka.consumers.CreateProductCommand -import java.time.Instant +import java.util.Date @Component class ProductCreator( @@ -37,27 +38,26 @@ class ProductCreator( key = req.id.toString(), headers = mapOf(Headers.EVENT_TYPE to ProductCreatedEvent::class.simpleName!!), partition = 0, - payload = req.mapToProductCreatedEvent() + payload = objectMapper.writeValueAsString(req.mapToProductCreatedEvent()) ) ) return "OK" } } -fun CreateProductCommand.mapToCreateRequest(): ProductCreateRequest = ProductCreateRequest(this.id, this.name, this.supplierId) +fun CreateProductCommand.mapToCreateRequest(): ProductCreateRequest { + return ProductCreateRequest(this.id, this.name, this.supplierId) +} -fun ProductCreateRequest.mapToProductCreatedEvent(): ProductCreatedEvent = ProductCreatedEvent( - this.id, - this.name, - this.supplierId, - Instant.now() -) +fun ProductCreateRequest.mapToProductCreatedEvent(): ProductCreatedEvent { + return ProductCreatedEvent(this.id, this.name, this.supplierId, Date()) +} data class ProductCreatedEvent( val id: Long, val name: String, val supplierId: Long, - val createdDate: Instant, + val createdDate: Date, val type: String = ProductCreatedEvent::class.simpleName!! ) diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/Constants.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/Constants.kt index 1c02fe95..5ef5b566 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/Constants.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/Constants.kt @@ -30,11 +30,13 @@ object Headers { fun getOrDefault( key: String, defaultValue: String = Defaults.USER_EMAIL - ): String = try { - MDC.get(key) ?: MDC.get(key.lowercase()) ?: MDC.get(key.uppercase()) ?: defaultValue - } catch ( - @Suppress("SwallowedException") exception: IllegalStateException - ) { - defaultValue + ): String { + return try { + MDC.get(key) ?: MDC.get(key.lowercase()) ?: MDC.get(key.uppercase()) ?: defaultValue + } catch ( + @Suppress("SwallowedException") exception: IllegalStateException + ) { + defaultValue + } } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/ObjectMapperConfig.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/ObjectMapperConfig.kt index b00b5e91..ed005047 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/ObjectMapperConfig.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/ObjectMapperConfig.kt @@ -1,32 +1,28 @@ package stove.spring.example.infrastructure import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.KotlinModule -import org.springframework.boot.autoconfigure.AutoConfigureBefore -import org.springframework.boot.autoconfigure.jackson.* -import org.springframework.context.annotation.* +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration @Configuration -@AutoConfigureBefore(JacksonAutoConfiguration::class) class ObjectMapperConfig { companion object { - fun default(): ObjectMapper = ObjectMapper() - .registerModule(KotlinModule.Builder().build()) - .findAndRegisterModules() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + fun createObjectMapperWithDefaults(): ObjectMapper { + val isoInstantModule = SimpleModule() + return ObjectMapper() + .registerModule(KotlinModule.Builder().build()) + .registerModule(isoInstantModule) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } } @Bean - @Primary - fun objectMapper(): ObjectMapper = default() - - @Bean - fun jacksonCustomizer(): Jackson2ObjectMapperBuilderCustomizer { - val default = default() - return Jackson2ObjectMapperBuilderCustomizer { builder -> - builder.factory(default.factory) - } + fun objectMapper(): ObjectMapper { + return createObjectMapperWithDefaults() } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/api/ProductController.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/api/ProductController.kt index 759df163..fc242eb9 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/api/ProductController.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/api/ProductController.kt @@ -15,26 +15,27 @@ import stove.spring.example.application.handlers.ProductCreator @RestController @RequestMapping("/api") -class ProductController( - private val productCreator: ProductCreator -) { +class ProductController(private val productCreator: ProductCreator) { @GetMapping("/index") suspend fun get( @RequestParam(required = false) keyword: String - ): String = "Hi from Stove framework with $keyword" + ): String { + return "Hi from Stove framework with $keyword" + } @PostMapping("/product/create") suspend fun createProduct( @RequestBody productCreateRequest: ProductCreateRequest - ): String = productCreator.create(productCreateRequest) + ): String { + return productCreator.create(productCreateRequest) + } @PostMapping("/product/import") suspend fun importFile( @RequestPart(name = "name") name: String, @RequestPart(name = "file") file: FilePart ): String { - val content = file - .content() + val content = file.content() .flatMap { mono { it.asInputStream().readAllBytes() } } .awaitSingle() .let { String(it) } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration.kt index 001f1c76..a2f5b0a0 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/couchbase/CouchbaseConfiguration.kt @@ -20,8 +20,7 @@ class CouchbaseConfiguration( ) { companion object { val objectMapper: ObjectMapper = - ObjectMapperConfig - .default() + ObjectMapperConfig.createObjectMapperWithDefaults() .registerModule(JsonValueModule()) } @@ -33,12 +32,12 @@ class CouchbaseConfiguration( .builder() .meter(MicrometerMeter.wrap(meterRegistry)) .timeoutConfig { - it - .kvTimeout(Duration.ofMillis(couchbaseProperties.kvTimeout)) + it.kvTimeout(Duration.ofMillis(couchbaseProperties.kvTimeout)) .connectTimeout(Duration.ofMillis(couchbaseProperties.connectTimeout)) .queryTimeout(Duration.ofMillis(couchbaseProperties.queryTimeout)) .viewTimeout(Duration.ofMillis(couchbaseProperties.viewTimeout)) - }.jsonSerializer(cbSerializer) + } + .jsonSerializer(cbSerializer) .build() } @@ -55,9 +54,13 @@ class CouchbaseConfiguration( @Primary @Bean - fun bucket(cluster: ReactiveCluster): ReactiveBucket = cluster.bucket(couchbaseProperties.bucketName) + fun bucket(cluster: ReactiveCluster): ReactiveBucket { + return cluster.bucket(couchbaseProperties.bucketName) + } @Primary @Bean - fun collection(bucket: ReactiveBucket): ReactiveCollection = bucket.defaultCollection() + fun collection(bucket: ReactiveBucket): ReactiveCollection { + return bucket.defaultCollection() + } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/SupplierHttpService.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/SupplierHttpService.kt index 8fe0c5fb..3adcd203 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/SupplierHttpService.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/SupplierHttpService.kt @@ -8,18 +8,19 @@ import stove.spring.example.application.services.SupplierPermission import stove.spring.example.application.services.SupplierService @Component -class SupplierHttpService( - private val supplierHttpClient: WebClient -) : SupplierService { - override suspend fun getSupplierPermission(id: Long): SupplierPermission = supplierHttpClient - .get() - .uri { - val builder = - it - .path("/suppliers/{id}/allowed") - builder.build(id) - }.accept(MediaType.APPLICATION_JSON) - .retrieve() - .bodyToMono(SupplierPermission::class.java) - .awaitFirst() +class SupplierHttpService(private val supplierHttpClient: WebClient) : SupplierService { + override suspend fun getSupplierPermission(id: Long): SupplierPermission { + return supplierHttpClient + .get() + .uri { + val builder = + it + .path("/suppliers/{id}/allowed") + builder.build(id) + } + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(SupplierPermission::class.java) + .awaitFirst() + } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/WebClientConfiguration.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/WebClientConfiguration.kt index 4c83d86a..75e38d69 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/WebClientConfiguration.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/http/WebClientConfiguration.kt @@ -19,9 +19,7 @@ import java.util.concurrent.TimeUnit @Configuration @EnableConfigurationProperties(WebClientConfigurationProperties::class) -class WebClientConfiguration( - private val webClientConfigurationProperties: WebClientConfigurationProperties -) { +class WebClientConfiguration(private val webClientConfigurationProperties: WebClientConfigurationProperties) { companion object { private const val MAX_MEMORY_SIZE = 50 * 1024 * 1024 } @@ -32,48 +30,50 @@ class WebClientConfiguration( webClientConfigurationProperties.supplierHttp.url, webClientConfigurationProperties.supplierHttp.connectTimeout, webClientConfigurationProperties.supplierHttp.readTimeout - ).exchangeStrategies(exchangeStrategies) + ) + .exchangeStrategies(exchangeStrategies) .build() @Bean - fun webClientObjectMapper(): ObjectMapper = ObjectMapperConfig - .default() - .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) + fun webClientObjectMapper(): ObjectMapper { + return ObjectMapperConfig.createObjectMapperWithDefaults() + .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) + } @Bean - fun exchangeStrategies(webClientObjectMapper: ObjectMapper): ExchangeStrategies = ExchangeStrategies - .builder() - .codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer -> - clientDefaultCodecsConfigurer.defaultCodecs().maxInMemorySize(MAX_MEMORY_SIZE) - clientDefaultCodecsConfigurer - .defaultCodecs() - .jackson2JsonEncoder(Jackson2JsonEncoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) - clientDefaultCodecsConfigurer - .defaultCodecs() - .jackson2JsonDecoder(Jackson2JsonDecoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) - }.build() + fun exchangeStrategies(webClientObjectMapper: ObjectMapper): ExchangeStrategies { + return ExchangeStrategies + .builder() + .codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer -> + clientDefaultCodecsConfigurer.defaultCodecs().maxInMemorySize(MAX_MEMORY_SIZE) + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonEncoder(Jackson2JsonEncoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonDecoder(Jackson2JsonDecoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) + }.build() + } private fun defaultWebClientBuilder( baseUrl: String, connectTimeout: Int, readTimeout: Long - ): WebClient.Builder = WebClient - .builder() - .baseUrl(baseUrl) - .clientConnector( - ReactorClientHttpConnector( - HttpClient - .create() - .followRedirect(true) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) - .doOnConnected { conn -> - conn.addHandlerLast( - ReadTimeoutHandler( - readTimeout, - TimeUnit.MILLISECONDS + ): WebClient.Builder { + return WebClient.builder() + .baseUrl(baseUrl) + .clientConnector( + ReactorClientHttpConnector( + HttpClient.create() + .followRedirect(true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) + .doOnConnected { conn -> + conn.addHandlerLast( + ReadTimeoutHandler( + readTimeout, + TimeUnit.MILLISECONDS + ) ) - ) - } + } + ) ) - ) + } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/KafkaProducer.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/KafkaProducer.kt index b8f3f1e7..6fe0b0c9 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/KafkaProducer.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/KafkaProducer.kt @@ -7,10 +7,10 @@ import org.slf4j.* import org.springframework.kafka.core.KafkaTemplate import org.springframework.stereotype.Component -data class KafkaOutgoingMessage( +data class KafkaOutgoingMessage( val topic: String, - val key: K, - val payload: V, + val key: String?, + val payload: String, val headers: Map, val partition: Int? = null ) @@ -21,8 +21,13 @@ class KafkaProducer( ) { private val logger: Logger = LoggerFactory.getLogger(KafkaProducer::class.java) - suspend fun send(message: KafkaOutgoingMessage) { - val recordHeaders = message.headers.map { RecordHeader(it.key, it.value.toByteArray()) } + suspend fun send(message: KafkaOutgoingMessage) { + val recordHeaders = message.headers.map { + RecordHeader( + it.key, + it.value.toByteArray() + ) + }.toMutableList() val record = ProducerRecord( message.topic, message.partition, @@ -31,6 +36,7 @@ class KafkaProducer( recordHeaders ) logger.info("Kafka message has published $message") + kafkaTemplate.send(record).await() } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt index 05cb6b23..247a78b9 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt @@ -4,8 +4,6 @@ import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.common.serialization.StringDeserializer import org.springframework.beans.factory.annotation.Value import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer -import org.springframework.kafka.support.serializer.JsonDeserializer import org.springframework.stereotype.Component import java.time.Duration @@ -39,11 +37,8 @@ class DefaultConsumerSettings( props[ConsumerConfig.CLIENT_ID_CONFIG] = kafkaProperties.createClientId() props[ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG] = kafkaProperties.autoCreateTopics props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = kafkaProperties.bootstrapServers - props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java - props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java - props[ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS] = JsonDeserializer::class.java - props[ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS] = StringDeserializer::class.java - props[JsonDeserializer.TRUSTED_PACKAGES] = "*" + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java props[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = kafkaProperties.offset props[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = true props[ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG] = ofSeconds(AUTO_COMMIT_INTERVAL) diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt index 6cb0b47b..f28ce895 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt @@ -1,16 +1,21 @@ package stove.spring.example.infrastructure.messaging.kafka.configuration -import org.springframework.context.annotation.* +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration import org.springframework.kafka.annotation.EnableKafka import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory -import org.springframework.kafka.core.* -import org.springframework.kafka.listener.* +import org.springframework.kafka.core.ConsumerFactory +import org.springframework.kafka.core.DefaultKafkaConsumerFactory +import org.springframework.kafka.listener.DefaultErrorHandler +import org.springframework.kafka.listener.RecordInterceptor +import org.springframework.kafka.support.converter.StringJsonMessageConverter import org.springframework.util.backoff.FixedBackOff @EnableKafka @Configuration -@Suppress("UNCHECKED_CAST") class KafkaConsumerConfiguration( + private val objectMapper: ObjectMapper, private val interceptor: RecordInterceptor<*, *> ) { @Bean @@ -21,8 +26,14 @@ class KafkaConsumerConfiguration( factory.setConcurrency(1) factory.consumerFactory = consumerFactory factory.containerProperties.isDeliveryAttemptHeader = true - val errorHandler = DefaultErrorHandler(FixedBackOff(0, 0)) + factory.setRecordMessageConverter(stringJsonMessageConverter()) + val errorHandler = + DefaultErrorHandler( + FixedBackOff(0, 0) + ) + factory.setCommonErrorHandler(errorHandler) + @Suppress("UNCHECKED_CAST") factory.setRecordInterceptor(interceptor as RecordInterceptor) return factory } @@ -33,23 +44,33 @@ class KafkaConsumerConfiguration( ): ConcurrentKafkaListenerContainerFactory { val factory = ConcurrentKafkaListenerContainerFactory() factory.setConcurrency(1) + factory.setRecordMessageConverter(stringJsonMessageConverter()) factory.containerProperties.isDeliveryAttemptHeader = true factory.consumerFactory = consumerRetryFactory - val errorHandler = DefaultErrorHandler(FixedBackOff(INTERVAL, 1)) + val errorHandler = + DefaultErrorHandler( + FixedBackOff(INTERVAL, 1) + ) factory.setCommonErrorHandler(errorHandler) + @Suppress("UNCHECKED_CAST") factory.setRecordInterceptor(interceptor as RecordInterceptor) return factory } @Bean - fun consumerFactory( - consumerSettings: ConsumerSettings - ): ConsumerFactory = DefaultKafkaConsumerFactory(consumerSettings.settings()) + fun consumerFactory(consumerSettings: ConsumerSettings): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerSettings.settings()) + } + + @Bean + fun consumerRetryFactory(consumerSettings: ConsumerSettings): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerSettings.settings()) + } @Bean - fun consumerRetryFactory( - consumerSettings: ConsumerSettings - ): ConsumerFactory = DefaultKafkaConsumerFactory(consumerSettings.settings()) + fun stringJsonMessageConverter(): StringJsonMessageConverter { + return StringJsonMessageConverter(objectMapper) + } companion object { const val RETRY_LISTENER_BEAN_NAME = "kafkaRetryListenerContainerFactory" diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt index 195c9fc7..96f50417 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt @@ -18,7 +18,9 @@ class KafkaProducerConfiguration { private val logger = LoggerFactory.getLogger(javaClass) @Bean - fun producer(producerFactory: ProducerFactory): Producer = producerFactory.createProducer() + fun producer(producerFactory: ProducerFactory): Producer { + return producerFactory.createProducer() + } @Bean fun kafkaTemplate(producerFactory: ProducerFactory): KafkaTemplate { diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt index 7b2d0e65..c637c2e7 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt @@ -3,7 +3,6 @@ package stove.spring.example.infrastructure.messaging.kafka.configuration import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.common.serialization.StringSerializer import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.kafka.support.serializer.JsonSerializer import org.springframework.stereotype.Component import stove.spring.example.infrastructure.messaging.kafka.interceptors.CustomProducerInterceptor @@ -11,14 +10,12 @@ interface ProducerSettings : MapBasedSettings @Component @EnableConfigurationProperties(KafkaProperties::class) -class DefaultProducerSettings( - private val kafkaProperties: KafkaProperties -) : ProducerSettings { +class DefaultProducerSettings(private val kafkaProperties: KafkaProperties) : ProducerSettings { override fun settings(): Map { val props: MutableMap = HashMap() props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = kafkaProperties.bootstrapServers props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java - props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java props[ProducerConfig.INTERCEPTOR_CLASSES_CONFIG] = CustomProducerInterceptor::class.java.name props[ProducerConfig.ACKS_CONFIG] = kafkaProperties.acks props[ProducerConfig.COMPRESSION_TYPE_CONFIG] = kafkaProperties.compression diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt index d4af2ad0..d1733703 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt @@ -9,13 +9,7 @@ import org.springframework.kafka.annotation.KafkaListener import org.springframework.stereotype.Component import stove.spring.example.infrastructure.messaging.kafka.configuration.KafkaConsumerConfiguration -data class BusinessException( - override val message: String -) : RuntimeException(message) - -data class FailingEvent( - val id: Long -) +data class BusinessException(override val message: String) : RuntimeException(message) @Component @ConditionalOnProperty(prefix = "kafka.consumers", value = ["enabled"], havingValue = "true") @@ -27,8 +21,10 @@ class FailingProductCreateConsumer { groupId = "#{@consumerConfig.groupId}", containerFactory = KafkaConsumerConfiguration.LISTENER_BEAN_NAME ) - fun listen(record: ConsumerRecord<*, *>): Unit = runBlocking(MDCContext()) { - logger.info("Received product failing event $record") - throw BusinessException("Failing product create event") - } + fun listen(cr: ConsumerRecord): Unit = + runBlocking(MDCContext()) { + logger.info("Received product failing event ${cr.value()}") + + throw BusinessException("Failing product create event") + } } diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt index 2fda3f8c..67232529 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt @@ -1,7 +1,6 @@ package stove.spring.example.infrastructure.messaging.kafka.consumers import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.convertValue import kotlinx.coroutines.runBlocking import kotlinx.coroutines.slf4j.MDCContext import org.apache.kafka.clients.consumer.ConsumerRecord @@ -9,14 +8,15 @@ import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.kafka.annotation.KafkaListener import org.springframework.stereotype.Component -import stove.spring.example.application.handlers.* +import stove.spring.example.application.handlers.ProductCreator +import stove.spring.example.application.handlers.mapToCreateRequest import stove.spring.example.infrastructure.messaging.kafka.configuration.KafkaConsumerConfiguration @Component @ConditionalOnProperty(prefix = "kafka.consumers", value = ["enabled"], havingValue = "true") class ProductTransferConsumers( - private val productCreator: ProductCreator, - private val objectMapper: ObjectMapper + private val objectMapper: ObjectMapper, + private val productCreator: ProductCreator ) { private val logger = LoggerFactory.getLogger(ProductTransferConsumers::class.java) @@ -30,11 +30,16 @@ class ProductTransferConsumers( groupId = "#{@consumerConfig.groupId}_retry", containerFactory = KafkaConsumerConfiguration.RETRY_LISTENER_BEAN_NAME ) - fun listen(record: ConsumerRecord<*, Any>) = runBlocking(MDCContext()) { - logger.info("Received product transfer command $record") - val command = objectMapper.convertValue(record.value()) - productCreator.create(command.mapToCreateRequest()) - } + fun listen(cr: ConsumerRecord) = + runBlocking(MDCContext()) { + logger.info("Received product transfer command ${cr.value()}") + val command = + objectMapper.readValue( + cr.value(), + CreateProductCommand::class.java + ) + productCreator.create(command.mapToCreateRequest()) + } } data class CreateProductCommand( diff --git a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt index a468dfe4..4735f296 100644 --- a/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt +++ b/examples/spring-example/src/main/kotlin/stove/spring/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt @@ -18,8 +18,7 @@ class CustomConsumerInterceptor : RecordInterceptor { consumer: Consumer ): ConsumerRecord? { val contextMap = HashMap() - record - .headers() + record.headers() .filter { it.key().lowercase().startsWith("x-") } .forEach { h: Header -> contextMap[h.key()] = String(h.value(), StandardCharsets.UTF_8) diff --git a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/ExampleTest.kt b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/ExampleTest.kt index cff6ae92..b62a8801 100644 --- a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/ExampleTest.kt +++ b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/ExampleTest.kt @@ -16,183 +16,175 @@ import stove.spring.example.infrastructure.couchbase.CouchbaseProperties import stove.spring.example.infrastructure.messaging.kafka.consumers.* import kotlin.time.Duration.Companion.seconds -class ExampleTest : - FunSpec({ - test("bridge should work") { - TestSystem.validate { - using { - this.bucketName shouldBe "Stove" - } +class ExampleTest : FunSpec({ + test("bridge should work") { + TestSystem.validate { + using { + this.bucketName shouldBe "Stove" } } + } - test("index should be reachable") { - TestSystem.validate { - http { - get("/api/index", queryParams = mapOf("keyword" to testCase.name.testName)) { actual -> - actual shouldContain "Hi from Stove framework with ${testCase.name.testName}" - println(actual) - } - get("/api/index") { actual -> - actual shouldContain "Hi from Stove framework with" - println(actual) - } + test("index should be reachable") { + TestSystem.validate { + http { + get("/api/index", queryParams = mapOf("keyword" to testCase.name.testName)) { actual -> + actual shouldContain "Hi from Stove framework with ${testCase.name.testName}" + println(actual) + } + get("/api/index") { actual -> + actual shouldContain "Hi from Stove framework with" + println(actual) } } } + } + + test("should create new product when send product create request from api for the allowed supplier") { + TestSystem.validate { + val productCreateRequest = ProductCreateRequest(1L, name = "product name", 99L) + val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = true) + + wiremock { + mockGet( + "/suppliers/${productCreateRequest.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should create new product when send product create request from api for the allowed supplier") { - TestSystem.validate { - val productCreateRequest = ProductCreateRequest(1L, name = "product name", 99L) - val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = true) - - wiremock { - mockGet( - "/suppliers/${productCreateRequest.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) - } - - http { - postAndExpectBodilessResponse(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> - actual.status shouldBe 200 - } + http { + postAndExpectBodilessResponse(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> + actual.status shouldBe 200 } + } - kafka { - shouldBePublished { - actual.id == productCreateRequest.id && - actual.name == productCreateRequest.name && - actual.supplierId == productCreateRequest.supplierId - } + kafka { + shouldBePublished { + actual.id == productCreateRequest.id && + actual.name == productCreateRequest.name && + actual.supplierId == productCreateRequest.supplierId } + } - couchbase { - shouldGet("product:${productCreateRequest.id}") { actual -> - actual.id shouldBe productCreateRequest.id - actual.name shouldBe productCreateRequest.name - actual.supplierId shouldBe productCreateRequest.supplierId - } + couchbase { + shouldGet("product:${productCreateRequest.id}") { actual -> + actual.id shouldBe productCreateRequest.id + actual.name shouldBe productCreateRequest.name + actual.supplierId shouldBe productCreateRequest.supplierId } } } - - test("should throw error when send product create request from api for for the not allowed supplier") { - TestSystem.validate { - val productCreateRequest = ProductCreateRequest(2L, name = "product name", 98L) - val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = false) - wiremock { - mockGet( - "/suppliers/${productCreateRequest.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) - } - http { - postAndExpectJson(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> - actual shouldBe "Supplier with the given id(${productCreateRequest.supplierId}) is not allowed for product creation" - } + } + + test("should throw error when send product create request from api for for the not allowed supplier") { + TestSystem.validate { + val productCreateRequest = ProductCreateRequest(2L, name = "product name", 98L) + val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = false) + wiremock { + mockGet( + "/suppliers/${productCreateRequest.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } + http { + postAndExpectJson(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> + actual shouldBe "Supplier with the given id(${productCreateRequest.supplierId}) is not allowed for product creation" } } } + } + + test("should throw error when send product create event for the not allowed supplier") { + TestSystem.validate { + val command = CreateProductCommand(3L, name = "product name", 97L) + val supplierPermission = SupplierPermission(command.supplierId, isAllowed = false) + + wiremock { + mockGet( + "/suppliers/${command.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should throw error when send product create event for the not allowed supplier") { - TestSystem.validate { - val command = CreateProductCommand(3L, name = "product name", 97L) - val supplierPermission = SupplierPermission(command.supplierId, isAllowed = false) - - wiremock { - mockGet( - "/suppliers/${command.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) - } - - kafka { - publish("trendyol.stove.service.product.create.0", command) - shouldBeConsumed(10.seconds) { - actual.id == command.id - } + kafka { + publish("trendyol.stove.service.product.create.0", command) + shouldBeConsumed(10.seconds) { + actual.id == command.id } } } + } + + test("should create new product when send product create event for the allowed supplier") { + TestSystem.validate { + val createProductCommand = CreateProductCommand(4L, name = "product name", 96L) + val supplierPermission = SupplierPermission(createProductCommand.supplierId, isAllowed = true) + + wiremock { + mockGet( + "/suppliers/${createProductCommand.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should create new product when send product create event for the allowed supplier") { - TestSystem.validate { - val createProductCommand = CreateProductCommand(4L, name = "product name", 96L) - val supplierPermission = SupplierPermission(createProductCommand.supplierId, isAllowed = true) - - wiremock { - mockGet( - "/suppliers/${createProductCommand.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) - } - - kafka { - publish("trendyol.stove.service.product.create.0", createProductCommand) - shouldBeConsumed { - actual.id == createProductCommand.id && - actual.name == createProductCommand.name && - actual.supplierId == createProductCommand.supplierId && - metadata.headers["X-UserEmail"] == "stove@trendyol.com" - } - } - - couchbase { - shouldGet("product:${createProductCommand.id}") { actual -> - actual.id shouldBe createProductCommand.id - actual.name shouldBe createProductCommand.name - actual.supplierId shouldBe createProductCommand.supplierId - } + kafka { + publish("trendyol.stove.service.product.create.0", createProductCommand) + shouldBePublished { + actual.id == createProductCommand.id && + actual.name == createProductCommand.name && + actual.supplierId == createProductCommand.supplierId && + metadata.headers["X-UserEmail"] == "stove@trendyol.com" } + } - kafka { - shouldBePublished { - actual.id == createProductCommand.id && - actual.name == createProductCommand.name && - actual.supplierId == createProductCommand.supplierId - } + couchbase { + shouldGet("product:${createProductCommand.id}") { actual -> + actual.id shouldBe createProductCommand.id + actual.name shouldBe createProductCommand.name + actual.supplierId shouldBe createProductCommand.supplierId } } } + } - test("when failing event is published then it should be validated") { - TestSystem.validate { - kafka { - publish("trendyol.stove.service.product.failing.0", FailingEvent(5L)) - shouldBeFailed { - actual.id == 5L && reason is BusinessException - } - - shouldBeFailed { - actual == FailingEvent(5L) && reason is BusinessException - } + test("when failing event is published then it should be validated") { + data class FailingEvent(val id: Long) + TestSystem.validate { + kafka { + publish("trendyol.stove.service.product.failing.0", FailingEvent(5L)) + shouldBeFailed { + actual.id == 5L && reason is BusinessException + } + + shouldBeFailed { + actual == FailingEvent(5L) && reason is BusinessException } } } - - test("file import should work") { - TestSystem.validate { - http { - postMultipartAndExpectResponse( - "/api/product/import", - body = listOf( - StoveMultiPartContent.Text("name", "product name"), - StoveMultiPartContent.File( - "file", - "file.txt", - "file".toByteArray(), - contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE - ) + } + + test("file import should work") { + TestSystem.validate { + http { + postMultipartAndExpectResponse( + "/api/product/import", + body = listOf( + StoveMultiPartContent.Text("name", "product name"), + StoveMultiPartContent.File( + "file", + "file.txt", + "file".toByteArray(), + contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE ) - ) { actual -> - actual.body() shouldBe "File file.txt is imported with product name and content: file" - } + ) + ) { actual -> + actual.body() shouldBe "File file.txt is imported with product name and content: file" } } } - }) + } +}) diff --git a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt similarity index 97% rename from examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt rename to examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt index f28a1960..ab3fd474 100644 --- a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/Stove.kt +++ b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemConfig.kt @@ -9,7 +9,7 @@ import com.trendyol.stove.testing.e2e.wiremock.* import io.kotest.core.config.AbstractProjectConfig import org.slf4j.* -class Stove : AbstractProjectConfig() { +class TestSystemConfig : AbstractProjectConfig() { private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor") @Suppress("LongMethod") @@ -38,7 +38,8 @@ class Stove : AbstractProjectConfig() { } kafka { KafkaSystemOptions( - containerOptions = KafkaContainerOptions(tag = "latest") { } + containerOptions = KafkaContainerOptions(tag = "latest") { + } ) { listOf( "kafka.bootstrapServers=${it.bootstrapServers}", diff --git a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemInitializer.kt b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemInitializer.kt index bbcad734..7b72e81b 100644 --- a/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemInitializer.kt +++ b/examples/spring-example/src/test/kotlin/com/stove/spring/example/e2e/TestSystemInitializer.kt @@ -2,16 +2,12 @@ package com.stove.spring.example.e2e import com.trendyol.stove.testing.e2e.BaseApplicationContextInitializer import com.trendyol.stove.testing.e2e.kafka.TestSystemKafkaInterceptor -import com.trendyol.stove.testing.e2e.serialization.* import org.springframework.boot.SpringApplication -import stove.spring.example.infrastructure.ObjectMapperConfig fun SpringApplication.addTestSystemDependencies() { this.addInitializers(TestSystemInitializer()) } -class TestSystemInitializer : - BaseApplicationContextInitializer({ - bean>(isPrimary = true) - bean { StoveSerde.jackson.anyByteArraySerde(ObjectMapperConfig.default()) } - }) +class TestSystemInitializer : BaseApplicationContextInitializer({ + bean>(isPrimary = true) +}) diff --git a/examples/spring-standalone-example/api/spring-standalone-example.api b/examples/spring-standalone-example/api/spring-standalone-example.api deleted file mode 100644 index b2457c0d..00000000 --- a/examples/spring-standalone-example/api/spring-standalone-example.api +++ /dev/null @@ -1,409 +0,0 @@ -public class stove/spring/standalone/example/ExampleApp { - public fun ()V -} - -public final class stove/spring/standalone/example/ExampleAppKt { - public static final fun main ([Ljava/lang/String;)V - public static final fun run ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lorg/springframework/context/ConfigurableApplicationContext; - public static synthetic fun run$default ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/springframework/context/ConfigurableApplicationContext; -} - -public final class stove/spring/standalone/example/application/handlers/ProductCreateRequest { - public fun (JLjava/lang/String;J)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun copy (JLjava/lang/String;J)Lstove/spring/standalone/example/application/handlers/ProductCreateRequest; - public static synthetic fun copy$default (Lstove/spring/standalone/example/application/handlers/ProductCreateRequest;JLjava/lang/String;JILjava/lang/Object;)Lstove/spring/standalone/example/application/handlers/ProductCreateRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/standalone/example/application/handlers/ProductCreatedEvent { - public fun (JLjava/lang/String;JLjava/util/Date;Ljava/lang/String;)V - public synthetic fun (JLjava/lang/String;JLjava/util/Date;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun component4 ()Ljava/util/Date; - public final fun component5 ()Ljava/lang/String; - public final fun copy (JLjava/lang/String;JLjava/util/Date;Ljava/lang/String;)Lstove/spring/standalone/example/application/handlers/ProductCreatedEvent; - public static synthetic fun copy$default (Lstove/spring/standalone/example/application/handlers/ProductCreatedEvent;JLjava/lang/String;JLjava/util/Date;Ljava/lang/String;ILjava/lang/Object;)Lstove/spring/standalone/example/application/handlers/ProductCreatedEvent; - public fun equals (Ljava/lang/Object;)Z - public final fun getCreatedDate ()Ljava/util/Date; - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/standalone/example/application/handlers/ProductCreator { - public field productCreatedTopic Ljava/lang/String; - public fun (Lstove/spring/standalone/example/infrastructure/http/SupplierHttpService;Lcom/couchbase/client/java/ReactiveCollection;Lcom/fasterxml/jackson/databind/ObjectMapper;Lstove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer;)V - public fun create (Lstove/spring/standalone/example/application/handlers/ProductCreateRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun getProductCreatedTopic ()Ljava/lang/String; - public fun setProductCreatedTopic (Ljava/lang/String;)V -} - -public final class stove/spring/standalone/example/application/handlers/ProductCreatorKt { - public static final fun mapToCreateRequest (Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/CreateProductCommand;)Lstove/spring/standalone/example/application/handlers/ProductCreateRequest; - public static final fun mapToProductCreatedEvent (Lstove/spring/standalone/example/application/handlers/ProductCreateRequest;)Lstove/spring/standalone/example/application/handlers/ProductCreatedEvent; -} - -public final class stove/spring/standalone/example/application/services/SupplierPermission { - public fun (JZ)V - public final fun component1 ()J - public final fun component2 ()Z - public final fun copy (JZ)Lstove/spring/standalone/example/application/services/SupplierPermission; - public static synthetic fun copy$default (Lstove/spring/standalone/example/application/services/SupplierPermission;JZILjava/lang/Object;)Lstove/spring/standalone/example/application/services/SupplierPermission; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public fun hashCode ()I - public final fun isAllowed ()Z - public fun toString ()Ljava/lang/String; -} - -public abstract interface class stove/spring/standalone/example/application/services/SupplierService { - public abstract fun getSupplierPermission (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class stove/spring/standalone/example/infrastructure/Defaults { - public static final field AGENT_NAME Ljava/lang/String; - public static final field INSTANCE Lstove/spring/standalone/example/infrastructure/Defaults; - public static final field USER_EMAIL Ljava/lang/String; - public final fun getHOST_NAME ()Ljava/lang/String; -} - -public final class stove/spring/standalone/example/infrastructure/Headers { - public static final field AGENT_NAME_KEY Ljava/lang/String; - public static final field CORRELATION_ID_KEY Ljava/lang/String; - public static final field EVENT_TYPE Ljava/lang/String; - public static final field HOST_KEY Ljava/lang/String; - public static final field INSTANCE Lstove/spring/standalone/example/infrastructure/Headers; - public static final field MESSAGE_ID_KEY Ljava/lang/String; - public static final field PUBLISHED_DATE_KEY Ljava/lang/String; - public static final field USER_EMAIL_KEY Ljava/lang/String; - public final fun getOrDefault (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; - public static synthetic fun getOrDefault$default (Lstove/spring/standalone/example/infrastructure/Headers;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; -} - -public class stove/spring/standalone/example/infrastructure/ObjectMapperConfig { - public static final field Companion Lstove/spring/standalone/example/infrastructure/ObjectMapperConfig$Companion; - public fun ()V - public fun jacksonCustomizer ()Lorg/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer; - public fun objectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/spring/standalone/example/infrastructure/ObjectMapperConfig$Companion { - public final fun getDefault ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public class stove/spring/standalone/example/infrastructure/api/ProductController { - public fun (Lstove/spring/standalone/example/application/handlers/ProductCreator;)V - public fun createProduct (Lstove/spring/standalone/example/application/handlers/ProductCreateRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun importFile (Ljava/lang/String;Lorg/springframework/http/codec/multipart/FilePart;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public class stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration { - public static final field Companion Lstove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration$Companion; - public fun (Lstove/spring/standalone/example/infrastructure/couchbase/CouchbaseProperties;Lio/micrometer/core/instrument/MeterRegistry;)V - public fun bucket (Lcom/couchbase/client/java/ReactiveCluster;)Lcom/couchbase/client/java/ReactiveBucket; - public fun cluster (Lcom/couchbase/client/java/env/ClusterEnvironment;)Lcom/couchbase/client/java/ReactiveCluster; - public fun clusterEnvironment ()Lcom/couchbase/client/java/env/ClusterEnvironment; - public fun collection (Lcom/couchbase/client/java/ReactiveBucket;)Lcom/couchbase/client/java/ReactiveCollection; -} - -public final class stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration$Companion { - public final fun getObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class stove/spring/standalone/example/infrastructure/couchbase/CouchbaseProperties { - public fun ()V - public final fun getBucketName ()Ljava/lang/String; - public final fun getConnectTimeout ()J - public final fun getHosts ()Ljava/util/List; - public final fun getKvTimeout ()J - public final fun getPassword ()Ljava/lang/String; - public final fun getQueryTimeout ()J - public final fun getUsername ()Ljava/lang/String; - public final fun getViewTimeout ()J - public final fun setBucketName (Ljava/lang/String;)V - public final fun setConnectTimeout (J)V - public final fun setHosts (Ljava/util/List;)V - public final fun setKvTimeout (J)V - public final fun setPassword (Ljava/lang/String;)V - public final fun setQueryTimeout (J)V - public final fun setUsername (Ljava/lang/String;)V - public final fun setViewTimeout (J)V -} - -public final class stove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty { - public fun ()V - public fun (Ljava/lang/String;Ljava/net/URI;IJ)V - public synthetic fun (Ljava/lang/String;Ljava/net/URI;IJILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/net/URI; - public final fun component3 ()I - public final fun component4 ()J - public final fun copy (Ljava/lang/String;Ljava/net/URI;IJ)Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;Ljava/lang/String;Ljava/net/URI;IJILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty; - public fun equals (Ljava/lang/Object;)Z - public final fun getConnectTimeout ()I - public final fun getReadTimeout ()J - public final fun getUri ()Ljava/net/URI; - public final fun getUrl ()Ljava/lang/String; - public fun hashCode ()I - public final fun setConnectTimeout (I)V - public final fun setReadTimeout (J)V - public final fun setUrl (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/standalone/example/infrastructure/http/SupplierHttpService : stove/spring/standalone/example/application/services/SupplierService { - public fun (Lorg/springframework/web/reactive/function/client/WebClient;)V - public fun getSupplierPermission (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public class stove/spring/standalone/example/infrastructure/http/WebClientConfiguration { - public static final field Companion Lstove/spring/standalone/example/infrastructure/http/WebClientConfiguration$Companion; - public fun (Lstove/spring/standalone/example/infrastructure/http/WebClientConfigurationProperties;)V - public fun exchangeStrategies (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lorg/springframework/web/reactive/function/client/ExchangeStrategies; - public fun supplierHttpClient (Lorg/springframework/web/reactive/function/client/ExchangeStrategies;)Lorg/springframework/web/reactive/function/client/WebClient; -} - -public final class stove/spring/standalone/example/infrastructure/http/WebClientConfiguration$Companion { -} - -public final class stove/spring/standalone/example/infrastructure/http/WebClientConfigurationProperties { - public fun ()V - public fun (Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;)V - public synthetic fun (Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty; - public final fun copy (Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;)Lstove/spring/standalone/example/infrastructure/http/WebClientConfigurationProperties; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/http/WebClientConfigurationProperties;Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;ILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/http/WebClientConfigurationProperties; - public fun equals (Ljava/lang/Object;)Z - public final fun getSupplierHttp ()Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty; - public fun hashCode ()I - public final fun setSupplierHttp (Lstove/spring/standalone/example/infrastructure/http/ClientConfigurationProperty;)V - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaOutgoingMessage { - public fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Object; - public final fun component3 ()Ljava/lang/Object; - public final fun component4 ()Ljava/util/Map; - public final fun component5 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/KafkaOutgoingMessage; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/messaging/kafka/KafkaOutgoingMessage;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/Integer;ILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/KafkaOutgoingMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Ljava/util/Map; - public final fun getKey ()Ljava/lang/Object; - public final fun getPartition ()Ljava/lang/Integer; - public final fun getPayload ()Ljava/lang/Object; - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer { - public fun (Lorg/springframework/kafka/core/KafkaTemplate;)V - public fun send (Lstove/spring/standalone/example/infrastructure/messaging/kafka/KafkaOutgoingMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings : stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings : stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings { - public static final field AUTO_COMMIT_INTERVAL J - public static final field Companion Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings$Companion; - public static final field MAX_POLL_INTERVAL J - public static final field SESSION_TIMEOUT J - public fun (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties;)V - public fun getKafkaProperties ()Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public fun settings ()Ljava/util/Map; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/DefaultConsumerSettings$Companion { -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/DefaultProducerSettings : stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings { - public fun (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties;)V - public fun settings ()Ljava/util/Map; -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration { - public static final field Companion Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration$Companion; - public static final field INTERVAL J - public static final field LISTENER_BEAN_NAME Ljava/lang/String; - public static final field RETRY_LISTENER_BEAN_NAME Ljava/lang/String; - public fun (Lcom/fasterxml/jackson/databind/ObjectMapper;Lorg/springframework/kafka/listener/RecordInterceptor;)V - public fun consumerFactory (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings;)Lorg/springframework/kafka/core/ConsumerFactory; - public fun consumerRetryFactory (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings;)Lorg/springframework/kafka/core/ConsumerFactory; - public fun kafkaListenerContainerFactory (Lorg/springframework/kafka/core/ConsumerFactory;Lorg/springframework/kafka/core/KafkaTemplate;)Lorg/springframework/kafka/config/ConcurrentKafkaListenerContainerFactory; - public fun kafkaRetryListenerContainerFactory (Lorg/springframework/kafka/core/ConsumerFactory;Lorg/springframework/kafka/core/KafkaTemplate;)Lorg/springframework/kafka/config/ConcurrentKafkaListenerContainerFactory; - public fun stringJsonMessageConverter ()Lorg/springframework/kafka/support/converter/StringJsonMessageConverter; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration$Companion { -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration { - public fun ()V - public fun kafkaTemplate (Lorg/springframework/kafka/core/ProducerFactory;)Lorg/springframework/kafka/core/KafkaTemplate; - public fun producer (Lorg/springframework/kafka/core/ProducerFactory;)Lorg/apache/kafka/clients/producer/Producer; - public fun producerFactory (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings;)Lorg/springframework/kafka/core/ProducerFactory; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties { - public static final field Companion Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/List;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Z - public final fun component11 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()I - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Z - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/List;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZLjava/util/List;ILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties; - public final fun createClientId ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getAcks ()Ljava/lang/String; - public final fun getAutoCreateTopics ()Z - public final fun getBootstrapServers ()Ljava/lang/String; - public final fun getCompression ()Ljava/lang/String; - public final fun getDefaultApiTimeout ()Ljava/lang/String; - public final fun getHeartbeatInSeconds ()I - public final fun getInterceptorClasses ()Ljava/util/List; - public final fun getMaxProducerConsumerBytes ()Ljava/lang/String; - public final fun getOffset ()Ljava/lang/String; - public final fun getRequestTimeout ()Ljava/lang/String; - public final fun getSecureKafka ()Z - public final fun getTopicPrefix ()Ljava/lang/String; - public fun hashCode ()I - public final fun setAcks (Ljava/lang/String;)V - public final fun setAutoCreateTopics (Z)V - public final fun setBootstrapServers (Ljava/lang/String;)V - public final fun setCompression (Ljava/lang/String;)V - public final fun setDefaultApiTimeout (Ljava/lang/String;)V - public final fun setHeartbeatInSeconds (I)V - public final fun setInterceptorClasses (Ljava/util/List;)V - public final fun setOffset (Ljava/lang/String;)V - public final fun setRequestTimeout (Ljava/lang/String;)V - public final fun setSecureKafka (Z)V - public final fun setTopicPrefix (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProperties$Companion { -} - -public abstract interface class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { - public abstract fun settings ()Ljava/util/Map; -} - -public abstract interface class stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings : stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/MapBasedSettings { -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/BusinessException : java/lang/RuntimeException { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/BusinessException; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/BusinessException;Ljava/lang/String;ILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/BusinessException; - public fun equals (Ljava/lang/Object;)Z - public fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ConsumerConfig { - public fun ()V - public fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getEnabled ()Z - public fun getErrorTopicSuffix ()Ljava/lang/String; - public fun getGroupId ()Ljava/lang/String; - public fun getRetryTopicSuffix ()Ljava/lang/String; - public fun setEnabled (Z)V - public fun setErrorTopicSuffix (Ljava/lang/String;)V - public fun setGroupId (Ljava/lang/String;)V - public fun setRetryTopicSuffix (Ljava/lang/String;)V -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/CreateProductCommand { - public fun (JLjava/lang/String;J)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()J - public final fun copy (JLjava/lang/String;J)Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/CreateProductCommand; - public static synthetic fun copy$default (Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/CreateProductCommand;JLjava/lang/String;JILjava/lang/Object;)Lstove/spring/standalone/example/infrastructure/messaging/kafka/consumers/CreateProductCommand; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()J - public final fun getName ()Ljava/lang/String; - public final fun getSupplierId ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer { - public fun ()V - public fun listen (Lorg/apache/kafka/clients/consumer/ConsumerRecord;)V -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductCreateEventTopicConfig : stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductFailingEventTopicConfig : stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductTransferConsumers { - public fun (Lcom/fasterxml/jackson/databind/ObjectMapper;Lstove/spring/standalone/example/application/handlers/ProductCreator;)V - public fun listen (Lorg/apache/kafka/clients/consumer/ConsumerRecord;)Ljava/lang/String; -} - -public abstract class stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/TopicConfig { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getErrorTopic ()Ljava/lang/String; - public final fun getRetryTopic ()Ljava/lang/String; - public final fun getTopic ()Ljava/lang/String; - public final fun setErrorTopic (Ljava/lang/String;)V - public final fun setRetryTopic (Ljava/lang/String;)V - public final fun setTopic (Ljava/lang/String;)V -} - -public class stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor : org/springframework/kafka/listener/RecordInterceptor { - public fun ()V - public fun intercept (Lorg/apache/kafka/clients/consumer/ConsumerRecord;Lorg/apache/kafka/clients/consumer/Consumer;)Lorg/apache/kafka/clients/consumer/ConsumerRecord; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor : org/apache/kafka/clients/producer/ProducerInterceptor { - public static final field Companion Lstove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor$Companion; - public fun ()V - public fun close ()V - public fun configure (Ljava/util/Map;)V - public fun onAcknowledgement (Lorg/apache/kafka/clients/producer/RecordMetadata;Ljava/lang/Exception;)V - public fun onSend (Lorg/apache/kafka/clients/producer/ProducerRecord;)Lorg/apache/kafka/clients/producer/ProducerRecord; -} - -public final class stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomProducerInterceptor$Companion { -} - diff --git a/examples/spring-standalone-example/build.gradle.kts b/examples/spring-standalone-example/build.gradle.kts index a5813b58..995dbb14 100644 --- a/examples/spring-standalone-example/build.gradle.kts +++ b/examples/spring-standalone-example/build.gradle.kts @@ -32,8 +32,4 @@ dependencies { testImplementation(projects.stove.starters.spring.stoveSpringTestingE2e) } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.stove.spring.standalone.example.e2e.Stove") -} - application { mainClass.set("stove.spring.standalone.example.ExampleAppkt") } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/application/handlers/ProductCreator.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/application/handlers/ProductCreator.kt index 1645a2ba..740aa334 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/application/handlers/ProductCreator.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/application/handlers/ProductCreator.kt @@ -35,7 +35,7 @@ class ProductCreator( key = req.id.toString(), headers = mapOf(Headers.EVENT_TYPE to ProductCreatedEvent::class.simpleName!!), partition = 0, - payload = req.mapToProductCreatedEvent() + payload = objectMapper.writeValueAsString(req.mapToProductCreatedEvent()) ) ) return "OK" diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/Constants.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/Constants.kt index 76d267c0..a3b6dec4 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/Constants.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/Constants.kt @@ -29,11 +29,13 @@ object Headers { fun getOrDefault( key: String, defaultValue: String = Defaults.USER_EMAIL - ): String = try { - MDC.get(key) ?: MDC.get(key.lowercase()) ?: MDC.get(key.uppercase()) ?: defaultValue - } catch ( - @Suppress("SwallowedException") exception: IllegalStateException - ) { - defaultValue + ): String { + return try { + MDC.get(key) ?: MDC.get(key.lowercase()) ?: MDC.get(key.uppercase()) ?: defaultValue + } catch ( + @Suppress("SwallowedException") exception: IllegalStateException + ) { + defaultValue + } } } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/ObjectMapperConfig.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/ObjectMapperConfig.kt index 4d607d92..067083c6 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/ObjectMapperConfig.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/ObjectMapperConfig.kt @@ -2,28 +2,23 @@ package stove.spring.standalone.example.infrastructure import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.* +import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.KotlinModule -import org.springframework.boot.autoconfigure.AutoConfigureBefore -import org.springframework.boot.autoconfigure.jackson.* import org.springframework.context.annotation.* @Configuration -@AutoConfigureBefore(JacksonAutoConfiguration::class) class ObjectMapperConfig { companion object { - val default: ObjectMapper = ObjectMapper() - .registerModule(KotlinModule.Builder().build()) - .findAndRegisterModules() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + fun createObjectMapperWithDefaults(): ObjectMapper { + val isoInstantModule = SimpleModule() + return ObjectMapper() + .registerModule(KotlinModule.Builder().build()) + .registerModule(isoInstantModule) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } } @Bean - @Primary - fun objectMapper(): ObjectMapper = default - - @Bean - fun jacksonCustomizer(): Jackson2ObjectMapperBuilderCustomizer = Jackson2ObjectMapperBuilderCustomizer { builder -> - builder.factory(default.factory) - } + fun objectMapper(): ObjectMapper = createObjectMapperWithDefaults() } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/api/ProductController.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/api/ProductController.kt index c2c0aa53..25013267 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/api/ProductController.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/api/ProductController.kt @@ -8,13 +8,13 @@ import stove.spring.standalone.example.application.handlers.* @RestController @RequestMapping("/api") -class ProductController( - private val productCreator: ProductCreator -) { +class ProductController(private val productCreator: ProductCreator) { @GetMapping("/index") suspend fun get( @RequestParam(required = false) keyword: String - ): String = "Hi from Stove framework with $keyword" + ): String { + return "Hi from Stove framework with $keyword" + } @PostMapping("/product/create") suspend fun createProduct( @@ -26,8 +26,7 @@ class ProductController( @RequestPart(name = "name") name: String, @RequestPart(name = "file") file: FilePart ): String { - val content = file - .content() + val content = file.content() .flatMap { mono { it.asInputStream().readAllBytes() } } .awaitSingle() .let { String(it) } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration.kt index 66a7ef6a..538fe9d4 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/couchbase/CouchbaseConfiguration.kt @@ -19,7 +19,9 @@ class CouchbaseConfiguration( private val meterRegistry: MeterRegistry ) { companion object { - val objectMapper: ObjectMapper = ObjectMapperConfig.default.registerModule(JsonValueModule()) + val objectMapper: ObjectMapper = + ObjectMapperConfig.createObjectMapperWithDefaults() + .registerModule(JsonValueModule()) } @Primary @@ -30,12 +32,12 @@ class CouchbaseConfiguration( .builder() .meter(MicrometerMeter.wrap(meterRegistry)) .timeoutConfig { - it - .kvTimeout(Duration.ofMillis(couchbaseProperties.kvTimeout)) + it.kvTimeout(Duration.ofMillis(couchbaseProperties.kvTimeout)) .connectTimeout(Duration.ofMillis(couchbaseProperties.connectTimeout)) .queryTimeout(Duration.ofMillis(couchbaseProperties.queryTimeout)) .viewTimeout(Duration.ofMillis(couchbaseProperties.viewTimeout)) - }.jsonSerializer(cbSerializer) + } + .jsonSerializer(cbSerializer) .build() } @@ -52,9 +54,13 @@ class CouchbaseConfiguration( @Primary @Bean - fun bucket(cluster: ReactiveCluster): ReactiveBucket = cluster.bucket(couchbaseProperties.bucketName) + fun bucket(cluster: ReactiveCluster): ReactiveBucket { + return cluster.bucket(couchbaseProperties.bucketName) + } @Primary @Bean - fun collection(bucket: ReactiveBucket): ReactiveCollection = bucket.defaultCollection() + fun collection(bucket: ReactiveBucket): ReactiveCollection { + return bucket.defaultCollection() + } } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/SupplierHttpService.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/SupplierHttpService.kt index 6d378cf2..9d3d64f3 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/SupplierHttpService.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/SupplierHttpService.kt @@ -7,9 +7,7 @@ import org.springframework.web.reactive.function.client.WebClient import stove.spring.standalone.example.application.services.* @Component -class SupplierHttpService( - private val supplierHttpClient: WebClient -) : SupplierService { +class SupplierHttpService(private val supplierHttpClient: WebClient) : SupplierService { override suspend fun getSupplierPermission(id: Long): SupplierPermission = supplierHttpClient .get() .uri("/suppliers/$id/allowed") diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/WebClientConfiguration.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/WebClientConfiguration.kt index ece9bd59..0b13d74b 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/WebClientConfiguration.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/http/WebClientConfiguration.kt @@ -1,6 +1,6 @@ package stove.spring.standalone.example.infrastructure.http -import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.* import io.netty.channel.ChannelOption import io.netty.handler.timeout.ReadTimeoutHandler import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -11,13 +11,12 @@ import org.springframework.http.codec.ClientCodecConfigurer import org.springframework.http.codec.json.* import org.springframework.web.reactive.function.client.* import reactor.netty.http.client.HttpClient +import stove.spring.standalone.example.infrastructure.ObjectMapperConfig import java.util.concurrent.TimeUnit @Configuration @EnableConfigurationProperties(WebClientConfigurationProperties::class) -class WebClientConfiguration( - private val webClientConfigurationProperties: WebClientConfigurationProperties -) { +class WebClientConfiguration(private val webClientConfigurationProperties: WebClientConfigurationProperties) { companion object { private const val MAX_MEMORY_SIZE = 50 * 1024 * 1024 } @@ -28,43 +27,50 @@ class WebClientConfiguration( webClientConfigurationProperties.supplierHttp.url, webClientConfigurationProperties.supplierHttp.connectTimeout, webClientConfigurationProperties.supplierHttp.readTimeout - ).exchangeStrategies(exchangeStrategies) + ) + .exchangeStrategies(exchangeStrategies) .build() @Bean - fun exchangeStrategies(webClientObjectMapper: ObjectMapper): ExchangeStrategies = ExchangeStrategies - .builder() - .codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer -> - clientDefaultCodecsConfigurer.defaultCodecs().maxInMemorySize(MAX_MEMORY_SIZE) - clientDefaultCodecsConfigurer - .defaultCodecs() - .jackson2JsonEncoder(Jackson2JsonEncoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) - clientDefaultCodecsConfigurer - .defaultCodecs() - .jackson2JsonDecoder(Jackson2JsonDecoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) - }.build() + fun webClientObjectMapper(): ObjectMapper { + return ObjectMapperConfig.createObjectMapperWithDefaults() + .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) + } + + @Bean + fun exchangeStrategies(webClientObjectMapper: ObjectMapper): ExchangeStrategies { + return ExchangeStrategies + .builder() + .codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer -> + clientDefaultCodecsConfigurer.defaultCodecs().maxInMemorySize(MAX_MEMORY_SIZE) + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonEncoder(Jackson2JsonEncoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonDecoder(Jackson2JsonDecoder(webClientObjectMapper, MediaType.APPLICATION_JSON)) + }.build() + } private fun defaultWebClientBuilder( baseUrl: String, connectTimeout: Int, readTimeout: Long - ): WebClient.Builder = WebClient - .builder() - .baseUrl(baseUrl) - .clientConnector( - ReactorClientHttpConnector( - HttpClient - .create() - .followRedirect(true) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) - .doOnConnected { conn -> - conn.addHandlerLast( - ReadTimeoutHandler( - readTimeout, - TimeUnit.MILLISECONDS + ): WebClient.Builder { + return WebClient.builder() + .baseUrl(baseUrl) + .clientConnector( + ReactorClientHttpConnector( + HttpClient.create() + .followRedirect(true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) + .doOnConnected { conn -> + conn.addHandlerLast( + ReadTimeoutHandler( + readTimeout, + TimeUnit.MILLISECONDS + ) ) - ) - } + } + ) ) - ) + } } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer.kt index 2e3b766d..4e9eb8f5 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/KafkaProducer.kt @@ -7,10 +7,10 @@ import org.slf4j.* import org.springframework.kafka.core.KafkaTemplate import org.springframework.stereotype.Component -data class KafkaOutgoingMessage( +data class KafkaOutgoingMessage( val topic: String, - val key: K, - val payload: V, + val key: String?, + val payload: String, val headers: Map, val partition: Int? = null ) @@ -21,7 +21,7 @@ class KafkaProducer( ) { private val logger: Logger = LoggerFactory.getLogger(KafkaProducer::class.java) - suspend fun send(message: KafkaOutgoingMessage) { + suspend fun send(message: KafkaOutgoingMessage) { val recordHeaders = message.headers.map { RecordHeader(it.key, it.value.toByteArray()) } val record = ProducerRecord( message.topic, diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt index 19d14f57..209a67fe 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ConsumerSettings.kt @@ -4,7 +4,6 @@ import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.common.serialization.StringDeserializer import org.springframework.beans.factory.annotation.Value import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.kafka.support.serializer.* import org.springframework.stereotype.Component import java.time.Duration @@ -38,13 +37,8 @@ class DefaultConsumerSettings( props[ConsumerConfig.CLIENT_ID_CONFIG] = kafkaProperties.createClientId() props[ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG] = kafkaProperties.autoCreateTopics props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = kafkaProperties.bootstrapServers - props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java - props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java - props[ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS] = JsonDeserializer::class.java - props[ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS] = StringDeserializer::class.java - props[JsonDeserializer.TRUSTED_PACKAGES] = "*" - props[JsonDeserializer.REMOVE_TYPE_INFO_HEADERS] = false - props[JsonDeserializer.VALUE_DEFAULT_TYPE] = Any::class.java + props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java + props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java props[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = kafkaProperties.offset props[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = true props[ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG] = ofSeconds(AUTO_COMMIT_INTERVAL) diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt index ea367693..32b9f1bd 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaConsumerConfiguration.kt @@ -24,6 +24,7 @@ class KafkaConsumerConfiguration( factory.setConcurrency(1) factory.consumerFactory = consumerFactory factory.containerProperties.isDeliveryAttemptHeader = true + factory.setRecordMessageConverter(stringJsonMessageConverter()) val errorHandler = DefaultErrorHandler( DeadLetterPublishingRecoverer(kafkaTemplate), FixedBackOff(0, 0) @@ -40,6 +41,7 @@ class KafkaConsumerConfiguration( ): ConcurrentKafkaListenerContainerFactory { val factory = ConcurrentKafkaListenerContainerFactory() factory.setConcurrency(1) + factory.setRecordMessageConverter(stringJsonMessageConverter()) factory.containerProperties.isDeliveryAttemptHeader = true factory.consumerFactory = consumerRetryFactory val errorHandler = DefaultErrorHandler( @@ -52,12 +54,14 @@ class KafkaConsumerConfiguration( } @Bean - fun consumerFactory(consumerSettings: ConsumerSettings): ConsumerFactory = - DefaultKafkaConsumerFactory(consumerSettings.settings()) + fun consumerFactory(consumerSettings: ConsumerSettings): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerSettings.settings()) + } @Bean - fun consumerRetryFactory(consumerSettings: ConsumerSettings): ConsumerFactory = - DefaultKafkaConsumerFactory(consumerSettings.settings()) + fun consumerRetryFactory(consumerSettings: ConsumerSettings): ConsumerFactory { + return DefaultKafkaConsumerFactory(consumerSettings.settings()) + } @Bean fun stringJsonMessageConverter(): StringJsonMessageConverter = StringJsonMessageConverter(objectMapper) diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt index 259b3008..7fa2a1ff 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/KafkaProducerConfiguration.kt @@ -13,7 +13,9 @@ class KafkaProducerConfiguration { private val logger = LoggerFactory.getLogger(javaClass) @Bean - fun producer(producerFactory: ProducerFactory): Producer = producerFactory.createProducer() + fun producer(producerFactory: ProducerFactory): Producer { + return producerFactory.createProducer() + } @Bean fun kafkaTemplate(producerFactory: ProducerFactory): KafkaTemplate { diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt index 79070699..60d9fb74 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/configuration/ProducerSettings.kt @@ -3,7 +3,6 @@ package stove.spring.standalone.example.infrastructure.messaging.kafka.configura import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.common.serialization.StringSerializer import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.kafka.support.serializer.JsonSerializer import org.springframework.stereotype.Component import stove.spring.standalone.example.infrastructure.messaging.kafka.interceptors.CustomProducerInterceptor @@ -11,14 +10,12 @@ interface ProducerSettings : MapBasedSettings @Component @EnableConfigurationProperties(KafkaProperties::class) -class DefaultProducerSettings( - private val kafkaProperties: KafkaProperties -) : ProducerSettings { +class DefaultProducerSettings(private val kafkaProperties: KafkaProperties) : ProducerSettings { override fun settings(): Map { val props: MutableMap = HashMap() props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = kafkaProperties.bootstrapServers props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java - props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java + props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java props[ProducerConfig.INTERCEPTOR_CLASSES_CONFIG] = listOf(CustomProducerInterceptor::class.java.name) + kafkaProperties.interceptorClasses props[ProducerConfig.ACKS_CONFIG] = kafkaProperties.acks diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt index c8fdf02b..9ec40a33 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/FailingProductCreateConsumer.kt @@ -9,9 +9,7 @@ import org.springframework.kafka.annotation.KafkaListener import org.springframework.stereotype.Component import stove.spring.standalone.example.infrastructure.messaging.kafka.configuration.KafkaConsumerConfiguration -data class BusinessException( - override val message: String -) : RuntimeException(message) +data class BusinessException(override val message: String) : RuntimeException(message) @Component @ConditionalOnProperty(prefix = "kafka.consumers", value = ["enabled"], havingValue = "true") @@ -23,8 +21,9 @@ class FailingProductCreateConsumer { groupId = "#{@consumerConfig.groupId}", containerFactory = KafkaConsumerConfiguration.LISTENER_BEAN_NAME ) - fun listen(record: ConsumerRecord<*, *>): Unit = runBlocking(MDCContext()) { - logger.info("Received product failing event ${record.value()}") + fun listen(cr: ConsumerRecord): Unit = runBlocking(MDCContext()) { + logger.info("Received product failing event ${cr.value()}") + throw BusinessException("Failing product create event") } } diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt index 3b4a1242..4d6d32dd 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/consumers/ProductCreateConsumers.kt @@ -29,11 +29,12 @@ class ProductTransferConsumers( groupId = "#{@consumerConfig.groupId}_retry", containerFactory = KafkaConsumerConfiguration.RETRY_LISTENER_BEAN_NAME ) - fun listen(record: ConsumerRecord<*, Any>) = runBlocking(MDCContext()) { - logger.info("Received product transfer command ${record.value()}") - val command = objectMapper.convertValue(record.value(), CreateProductCommand::class.java) - productCreator.create(command.mapToCreateRequest()) - } + fun listen(cr: ConsumerRecord) = + runBlocking(MDCContext()) { + logger.info("Received product transfer command ${cr.value()}") + val command = objectMapper.readValue(cr.value(), CreateProductCommand::class.java) + productCreator.create(command.mapToCreateRequest()) + } } data class CreateProductCommand( diff --git a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt index 1a033c8a..39ab3315 100644 --- a/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt +++ b/examples/spring-standalone-example/src/main/kotlin/stove/spring/standalone/example/infrastructure/messaging/kafka/interceptors/CustomConsumerInterceptor.kt @@ -18,8 +18,7 @@ class CustomConsumerInterceptor : RecordInterceptor { consumer: Consumer ): ConsumerRecord? { val contextMap = HashMap() - record - .headers() + record.headers() .filter { it.key().lowercase().startsWith("x-") } .forEach { h: Header -> contextMap[h.key()] = String(h.value(), StandardCharsets.UTF_8) diff --git a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/ExampleTest.kt b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/ExampleTest.kt index 66d35926..9e00b713 100644 --- a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/ExampleTest.kt +++ b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/ExampleTest.kt @@ -16,184 +16,181 @@ import stove.spring.standalone.example.infrastructure.couchbase.CouchbasePropert import stove.spring.standalone.example.infrastructure.messaging.kafka.consumers.CreateProductCommand import kotlin.time.Duration.Companion.seconds -class ExampleTest : - FunSpec({ - test("bridge should work") { - TestSystem.validate { - using { - this.bucketName shouldBe "Stove" - } +class ExampleTest : FunSpec({ + test("bridge should work") { + TestSystem.validate { + using { + this.bucketName shouldBe "Stove" } } - - test("index should be reachable") { - TestSystem.validate { - http { - get("/api/index", queryParams = mapOf("keyword" to testCase.name.testName)) { actual -> - actual shouldContain "Hi from Stove framework with ${testCase.name.testName}" - println(actual) - } - get("/api/index") { actual -> - actual shouldContain "Hi from Stove framework with" - println(actual) - } + } + + test("index should be reachable") { + TestSystem.validate { + http { + get("/api/index", queryParams = mapOf("keyword" to testCase.name.testName)) { actual -> + actual shouldContain "Hi from Stove framework with ${testCase.name.testName}" + println(actual) + } + get("/api/index") { actual -> + actual shouldContain "Hi from Stove framework with" + println(actual) } } } + } + + test("should create new product when send product create request from api for the allowed supplier") { + TestSystem.validate { + val productCreateRequest = ProductCreateRequest(1L, name = "product name", 99L) + val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = true) + + wiremock { + mockGet( + "/suppliers/${supplierPermission.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should create new product when send product create request from api for the allowed supplier") { - TestSystem.validate { - val request = ProductCreateRequest(1L, name = "product name", 99L) - val permission = SupplierPermission(request.supplierId, isAllowed = true) - - wiremock { - mockGet( - "/suppliers/${permission.id}/allowed", - statusCode = 200, - responseBody = permission.some() - ) - } - - http { - postAndExpectBodilessResponse(uri = "/api/product/create", body = request.some()) { actual -> - actual.status shouldBe 200 - } + http { + postAndExpectBodilessResponse(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> + actual.status shouldBe 200 } + } - kafka { - shouldBePublished { - actual.id == request.id && - actual.name == request.name && - actual.supplierId == request.supplierId - } + kafka { + shouldBePublished { + actual.id == productCreateRequest.id && + actual.name == productCreateRequest.name && + actual.supplierId == productCreateRequest.supplierId } + } - couchbase { - shouldGet("product:${request.id}") { actual -> - actual.id shouldBe request.id - actual.name shouldBe request.name - actual.supplierId shouldBe request.supplierId - } + couchbase { + shouldGet("product:${productCreateRequest.id}") { actual -> + actual.id shouldBe productCreateRequest.id + actual.name shouldBe productCreateRequest.name + actual.supplierId shouldBe productCreateRequest.supplierId } } } - - test("should throw error when send product create request from api for for the not allowed supplier") { - TestSystem.validate { - val request = ProductCreateRequest(2L, name = "product name", 98L) - val permission = SupplierPermission(request.supplierId, isAllowed = false) - wiremock { - mockGet( - "/suppliers/${permission.id}/allowed", - statusCode = 200, - responseBody = permission.some() - ) - } - http { - postAndExpectJson(uri = "/api/product/create", body = request.some()) { actual -> - actual shouldBe "Supplier with the given id(${request.supplierId}) is not allowed for product creation" - } + } + + test("should throw error when send product create request from api for for the not allowed supplier") { + TestSystem.validate { + val productCreateRequest = ProductCreateRequest(2L, name = "product name", 98L) + val supplierPermission = SupplierPermission(productCreateRequest.supplierId, isAllowed = false) + wiremock { + mockGet( + "/suppliers/${supplierPermission.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } + http { + postAndExpectJson(uri = "/api/product/create", body = productCreateRequest.some()) { actual -> + actual shouldBe "Supplier with the given id(${productCreateRequest.supplierId}) is not allowed for product creation" } } } + } + + test("should throw error when send product create event for the not allowed supplier") { + TestSystem.validate { + val productCreateEvent = CreateProductCommand(3L, name = "product name", 97L) + val supplierPermission = SupplierPermission(productCreateEvent.supplierId, isAllowed = false) + + wiremock { + mockGet( + "/suppliers/${supplierPermission.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should throw error when send product create event for the not allowed supplier") { - TestSystem.validate { - val command = CreateProductCommand(3L, name = "product name", 97L) - val supplierPermission = SupplierPermission(command.supplierId, isAllowed = false) - - wiremock { - mockGet( - "/suppliers/${supplierPermission.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) - } - - kafka { - publish("trendyol.stove.service.product.create.0", command) - shouldBeConsumed(10.seconds) { - actual.id == command.id - } + kafka { + publish("trendyol.stove.service.product.create.0", productCreateEvent) + shouldBeConsumed(10.seconds) { + actual.id == productCreateEvent.id } } } + } + + test("should create new product when send product create event for the allowed supplier") { + TestSystem.validate { + val createProductCommand = CreateProductCommand(4L, name = "product name", 96L) + val supplierPermission = SupplierPermission(createProductCommand.supplierId, isAllowed = true) + + wiremock { + mockGet( + "/suppliers/${supplierPermission.id}/allowed", + statusCode = 200, + responseBody = supplierPermission.some() + ) + } - test("should create new product when send product create event for the allowed supplier") { - TestSystem.validate { - val command = CreateProductCommand(4L, name = "product name", 96L) - val supplierPermission = SupplierPermission(command.supplierId, isAllowed = true) - - wiremock { - mockGet( - "/suppliers/${supplierPermission.id}/allowed", - statusCode = 200, - responseBody = supplierPermission.some() - ) + kafka { + publish("trendyol.stove.service.product.create.0", createProductCommand) + shouldBeConsumed { + actual.id == createProductCommand.id && + actual.name == createProductCommand.name && + actual.supplierId == createProductCommand.supplierId } - kafka { - publish("trendyol.stove.service.product.create.0", command) - shouldBeConsumed { - actual.id == command.id && - actual.name == command.name && - actual.supplierId == command.supplierId - } - - shouldBePublished { - actual.id == command.id && - actual.name == command.name && - actual.supplierId == command.supplierId && - metadata.headers["X-UserEmail"] == "stove@trendyol.com" - } + shouldBePublished { + actual.id == createProductCommand.id && + actual.name == createProductCommand.name && + actual.supplierId == createProductCommand.supplierId && + metadata.headers["X-UserEmail"] == "stove@trendyol.com" } + } - couchbase { - shouldGet("product:${command.id}") { actual -> - actual.id shouldBe command.id - actual.name shouldBe command.name - actual.supplierId shouldBe command.supplierId - } + couchbase { + shouldGet("product:${createProductCommand.id}") { actual -> + actual.id shouldBe createProductCommand.id + actual.name shouldBe createProductCommand.name + actual.supplierId shouldBe createProductCommand.supplierId } } } + } + + test("when failing event is published then it should be validated") { + data class FailingEvent(val id: Long) + TestSystem.validate { + kafka { + publish("trendyol.stove.service.product.failing.0", FailingEvent(5L)) + shouldBeFailed { + actual.id == 5L + } - test("when failing event is published then it should be validated") { - data class FailingEvent( - val id: Long - ) - TestSystem.validate { - kafka { - publish("trendyol.stove.service.product.failing.0", FailingEvent(5L)) - shouldBeFailed { - actual.id == 5L - } - - shouldBeFailed { - actual == FailingEvent(5L) - } + shouldBeFailed { + actual == FailingEvent(5L) } } } - - test("file import should work") { - TestSystem.validate { - http { - postMultipartAndExpectResponse( - "/api/product/import", - body = listOf( - StoveMultiPartContent.Text("name", "product name"), - StoveMultiPartContent.File( - "file", - "file.txt", - "file".toByteArray(), - contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE - ) + } + + test("file import should work") { + TestSystem.validate { + http { + postMultipartAndExpectResponse( + "/api/product/import", + body = listOf( + StoveMultiPartContent.Text("name", "product name"), + StoveMultiPartContent.File( + "file", + "file.txt", + "file".toByteArray(), + contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE ) - ) { actual -> - actual.body() shouldBe "File file.txt is imported with product name and content: file" - } + ) + ) { actual -> + actual.body() shouldBe "File file.txt is imported with product name and content: file" } } } - }) + } +}) diff --git a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt similarity index 90% rename from examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt rename to examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt index bbdb7efa..910da1ff 100644 --- a/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/Stove.kt +++ b/examples/spring-standalone-example/src/test/kotlin/com/stove/spring/standalone/example/e2e/TestSystemConfig.kt @@ -3,7 +3,6 @@ package com.stove.spring.standalone.example.e2e import com.trendyol.stove.testing.e2e.* import com.trendyol.stove.testing.e2e.couchbase.* import com.trendyol.stove.testing.e2e.http.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.standalone.kafka.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.wiremock.* @@ -11,7 +10,7 @@ import io.kotest.core.config.AbstractProjectConfig import org.slf4j.* import stove.spring.standalone.example.infrastructure.ObjectMapperConfig -class Stove : AbstractProjectConfig() { +class TestSystemConfig : AbstractProjectConfig() { private val logger: Logger = LoggerFactory.getLogger("WireMockMonitor") @Suppress("LongMethod") @@ -39,9 +38,10 @@ class Stove : AbstractProjectConfig() { ) } kafka { + stoveKafkaObjectMapperRef = ObjectMapperConfig.createObjectMapperWithDefaults() KafkaSystemOptions( topicSuffixes = TopicSuffixes().copy(error = listOf(".error", ".DLT", "dlt")), - serde = StoveSerde.jackson.anyByteArraySerde(ObjectMapperConfig.default), + objectMapper = ObjectMapperConfig.createObjectMapperWithDefaults(), containerOptions = KafkaContainerOptions(tag = "latest") { } ) { listOf( @@ -63,8 +63,7 @@ class Stove : AbstractProjectConfig() { } springBoot( runner = { parameters -> - stove.spring.standalone.example - .run(parameters) + stove.spring.standalone.example.run(parameters) }, withParameters = listOf( "server.port=8001", diff --git a/examples/spring-streams-example/api/spring-streams-example.api b/examples/spring-streams-example/api/spring-streams-example.api deleted file mode 100644 index 14b5109e..00000000 --- a/examples/spring-streams-example/api/spring-streams-example.api +++ /dev/null @@ -1,537 +0,0 @@ -public final class stove/example/protobuf/Input1Kt { - public static final field INSTANCE Lstove/example/protobuf/Input1Kt; -} - -public final class stove/example/protobuf/Input1Kt$Dsl { - public static final field Companion Lstove/example/protobuf/Input1Kt$Dsl$Companion; - public synthetic fun (Lstove/example/protobuf/Input1Value$Input1$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final synthetic fun _build ()Lstove/example/protobuf/Input1Value$Input1; - public final fun clearFirstName ()V - public final fun clearLastName ()V - public final fun getFirstName ()Ljava/lang/String; - public final fun getLastName ()Ljava/lang/String; - public final fun setFirstName (Ljava/lang/String;)V - public final fun setLastName (Ljava/lang/String;)V -} - -public final class stove/example/protobuf/Input1Kt$Dsl$Companion { - public final synthetic fun _create (Lstove/example/protobuf/Input1Value$Input1$Builder;)Lstove/example/protobuf/Input1Kt$Dsl; -} - -public final class stove/example/protobuf/Input1KtKt { - public static final fun -initializeinput1 (Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/Input1Value$Input1; - public static final synthetic fun copy (Lstove/example/protobuf/Input1Value$Input1;Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/Input1Value$Input1; -} - -public final class stove/example/protobuf/Input1Value { - public static fun getDescriptor ()Lcom/google/protobuf/Descriptors$FileDescriptor; - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistry;)V - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistryLite;)V -} - -public final class stove/example/protobuf/Input1Value$Input1 : com/google/protobuf/GeneratedMessageV3, stove/example/protobuf/Input1Value$Input1OrBuilder { - public static final field FIRSTNAME_FIELD_NUMBER I - public static final field LASTNAME_FIELD_NUMBER I - public fun equals (Ljava/lang/Object;)Z - public static fun getDefaultInstance ()Lstove/example/protobuf/Input1Value$Input1; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/Input1Value$Input1; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getFirstName ()Ljava/lang/String; - public fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public fun getLastName ()Ljava/lang/String; - public fun getLastNameBytes ()Lcom/google/protobuf/ByteString; - public fun getParserForType ()Lcom/google/protobuf/Parser; - public fun getSerializedSize ()I - public fun hashCode ()I - public final fun isInitialized ()Z - public static fun newBuilder ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public static fun newBuilder (Lstove/example/protobuf/Input1Value$Input1;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/Message$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/MessageLite$Builder; - public fun newBuilderForType ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public static fun parseDelimitedFrom (Ljava/io/InputStream;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseDelimitedFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Lcom/google/protobuf/ByteString;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Ljava/io/InputStream;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Ljava/nio/ByteBuffer;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom (Ljava/nio/ByteBuffer;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom ([B)Lstove/example/protobuf/Input1Value$Input1; - public static fun parseFrom ([BLcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1; - public static fun parser ()Lcom/google/protobuf/Parser; - public synthetic fun toBuilder ()Lcom/google/protobuf/Message$Builder; - public synthetic fun toBuilder ()Lcom/google/protobuf/MessageLite$Builder; - public fun toBuilder ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun writeTo (Lcom/google/protobuf/CodedOutputStream;)V -} - -public final class stove/example/protobuf/Input1Value$Input1$Builder : com/google/protobuf/GeneratedMessageV3$Builder, stove/example/protobuf/Input1Value$Input1OrBuilder { - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun build ()Lcom/google/protobuf/Message; - public synthetic fun build ()Lcom/google/protobuf/MessageLite; - public fun build ()Lstove/example/protobuf/Input1Value$Input1; - public synthetic fun buildPartial ()Lcom/google/protobuf/Message; - public synthetic fun buildPartial ()Lcom/google/protobuf/MessageLite; - public fun buildPartial ()Lstove/example/protobuf/Input1Value$Input1; - public synthetic fun clear ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clear ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clear ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clear ()Lcom/google/protobuf/MessageLite$Builder; - public fun clear ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun clearFirstName ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun clearLastName ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun clone ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clone ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clone ()Lcom/google/protobuf/MessageLite$Builder; - public synthetic fun clone ()Ljava/lang/Object; - public fun clone ()Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/Input1Value$Input1; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getDescriptorForType ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getFirstName ()Ljava/lang/String; - public fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public fun getLastName ()Ljava/lang/String; - public fun getLastNameBytes ()Lcom/google/protobuf/ByteString; - public final fun isInitialized ()Z - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/Message$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/MessageLite$Builder; - public fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/Message$Builder; - public fun mergeFrom (Lcom/google/protobuf/Message;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun mergeFrom (Lstove/example/protobuf/Input1Value$Input1;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun setFirstName (Ljava/lang/String;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun setFirstNameBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun setLastName (Ljava/lang/String;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public fun setLastNameBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lstove/example/protobuf/Input1Value$Input1$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/Input1Value$Input1$Builder; -} - -public abstract interface class stove/example/protobuf/Input1Value$Input1OrBuilder : com/google/protobuf/MessageOrBuilder { - public abstract fun getFirstName ()Ljava/lang/String; - public abstract fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public abstract fun getLastName ()Ljava/lang/String; - public abstract fun getLastNameBytes ()Lcom/google/protobuf/ByteString; -} - -public final class stove/example/protobuf/Input2Kt { - public static final field INSTANCE Lstove/example/protobuf/Input2Kt; -} - -public final class stove/example/protobuf/Input2Kt$Dsl { - public static final field Companion Lstove/example/protobuf/Input2Kt$Dsl$Companion; - public synthetic fun (Lstove/example/protobuf/Input2Value$Input2$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final synthetic fun _build ()Lstove/example/protobuf/Input2Value$Input2; - public final fun clearAge ()V - public final fun clearBsn ()V - public final fun getAge ()I - public final fun getBsn ()Ljava/lang/String; - public final fun setAge (I)V - public final fun setBsn (Ljava/lang/String;)V -} - -public final class stove/example/protobuf/Input2Kt$Dsl$Companion { - public final synthetic fun _create (Lstove/example/protobuf/Input2Value$Input2$Builder;)Lstove/example/protobuf/Input2Kt$Dsl; -} - -public final class stove/example/protobuf/Input2KtKt { - public static final fun -initializeinput2 (Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/Input2Value$Input2; - public static final synthetic fun copy (Lstove/example/protobuf/Input2Value$Input2;Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/Input2Value$Input2; -} - -public final class stove/example/protobuf/Input2Value { - public static fun getDescriptor ()Lcom/google/protobuf/Descriptors$FileDescriptor; - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistry;)V - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistryLite;)V -} - -public final class stove/example/protobuf/Input2Value$Input2 : com/google/protobuf/GeneratedMessageV3, stove/example/protobuf/Input2Value$Input2OrBuilder { - public static final field AGE_FIELD_NUMBER I - public static final field BSN_FIELD_NUMBER I - public fun equals (Ljava/lang/Object;)Z - public fun getAge ()I - public fun getBsn ()Ljava/lang/String; - public fun getBsnBytes ()Lcom/google/protobuf/ByteString; - public static fun getDefaultInstance ()Lstove/example/protobuf/Input2Value$Input2; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/Input2Value$Input2; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getParserForType ()Lcom/google/protobuf/Parser; - public fun getSerializedSize ()I - public fun hashCode ()I - public final fun isInitialized ()Z - public static fun newBuilder ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public static fun newBuilder (Lstove/example/protobuf/Input2Value$Input2;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/Message$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/MessageLite$Builder; - public fun newBuilderForType ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public static fun parseDelimitedFrom (Ljava/io/InputStream;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseDelimitedFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Lcom/google/protobuf/ByteString;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Ljava/io/InputStream;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Ljava/nio/ByteBuffer;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom (Ljava/nio/ByteBuffer;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom ([B)Lstove/example/protobuf/Input2Value$Input2; - public static fun parseFrom ([BLcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2; - public static fun parser ()Lcom/google/protobuf/Parser; - public synthetic fun toBuilder ()Lcom/google/protobuf/Message$Builder; - public synthetic fun toBuilder ()Lcom/google/protobuf/MessageLite$Builder; - public fun toBuilder ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun writeTo (Lcom/google/protobuf/CodedOutputStream;)V -} - -public final class stove/example/protobuf/Input2Value$Input2$Builder : com/google/protobuf/GeneratedMessageV3$Builder, stove/example/protobuf/Input2Value$Input2OrBuilder { - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun build ()Lcom/google/protobuf/Message; - public synthetic fun build ()Lcom/google/protobuf/MessageLite; - public fun build ()Lstove/example/protobuf/Input2Value$Input2; - public synthetic fun buildPartial ()Lcom/google/protobuf/Message; - public synthetic fun buildPartial ()Lcom/google/protobuf/MessageLite; - public fun buildPartial ()Lstove/example/protobuf/Input2Value$Input2; - public synthetic fun clear ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clear ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clear ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clear ()Lcom/google/protobuf/MessageLite$Builder; - public fun clear ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun clearAge ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun clearBsn ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun clone ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clone ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clone ()Lcom/google/protobuf/MessageLite$Builder; - public synthetic fun clone ()Ljava/lang/Object; - public fun clone ()Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun getAge ()I - public fun getBsn ()Ljava/lang/String; - public fun getBsnBytes ()Lcom/google/protobuf/ByteString; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/Input2Value$Input2; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getDescriptorForType ()Lcom/google/protobuf/Descriptors$Descriptor; - public final fun isInitialized ()Z - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/Message$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/MessageLite$Builder; - public fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/Message$Builder; - public fun mergeFrom (Lcom/google/protobuf/Message;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun mergeFrom (Lstove/example/protobuf/Input2Value$Input2;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun setAge (I)Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun setBsn (Ljava/lang/String;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public fun setBsnBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lstove/example/protobuf/Input2Value$Input2$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/Input2Value$Input2$Builder; -} - -public abstract interface class stove/example/protobuf/Input2Value$Input2OrBuilder : com/google/protobuf/MessageOrBuilder { - public abstract fun getAge ()I - public abstract fun getBsn ()Ljava/lang/String; - public abstract fun getBsnBytes ()Lcom/google/protobuf/ByteString; -} - -public final class stove/example/protobuf/OutputKt { - public static final field INSTANCE Lstove/example/protobuf/OutputKt; -} - -public final class stove/example/protobuf/OutputKt$Dsl { - public static final field Companion Lstove/example/protobuf/OutputKt$Dsl$Companion; - public synthetic fun (Lstove/example/protobuf/OutputValue$Output$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final synthetic fun _build ()Lstove/example/protobuf/OutputValue$Output; - public final fun clearAge ()V - public final fun clearBsn ()V - public final fun clearFirstName ()V - public final fun clearLastName ()V - public final fun getAge ()I - public final fun getBsn ()Ljava/lang/String; - public final fun getFirstName ()Ljava/lang/String; - public final fun getLastName ()Ljava/lang/String; - public final fun setAge (I)V - public final fun setBsn (Ljava/lang/String;)V - public final fun setFirstName (Ljava/lang/String;)V - public final fun setLastName (Ljava/lang/String;)V -} - -public final class stove/example/protobuf/OutputKt$Dsl$Companion { - public final synthetic fun _create (Lstove/example/protobuf/OutputValue$Output$Builder;)Lstove/example/protobuf/OutputKt$Dsl; -} - -public final class stove/example/protobuf/OutputKtKt { - public static final fun -initializeoutput (Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/OutputValue$Output; - public static final synthetic fun copy (Lstove/example/protobuf/OutputValue$Output;Lkotlin/jvm/functions/Function1;)Lstove/example/protobuf/OutputValue$Output; -} - -public final class stove/example/protobuf/OutputValue { - public static fun getDescriptor ()Lcom/google/protobuf/Descriptors$FileDescriptor; - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistry;)V - public static fun registerAllExtensions (Lcom/google/protobuf/ExtensionRegistryLite;)V -} - -public final class stove/example/protobuf/OutputValue$Output : com/google/protobuf/GeneratedMessageV3, stove/example/protobuf/OutputValue$OutputOrBuilder { - public static final field AGE_FIELD_NUMBER I - public static final field BSN_FIELD_NUMBER I - public static final field FIRSTNAME_FIELD_NUMBER I - public static final field LASTNAME_FIELD_NUMBER I - public fun equals (Ljava/lang/Object;)Z - public fun getAge ()I - public fun getBsn ()Ljava/lang/String; - public fun getBsnBytes ()Lcom/google/protobuf/ByteString; - public static fun getDefaultInstance ()Lstove/example/protobuf/OutputValue$Output; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/OutputValue$Output; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getFirstName ()Ljava/lang/String; - public fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public fun getLastName ()Ljava/lang/String; - public fun getLastNameBytes ()Lcom/google/protobuf/ByteString; - public fun getParserForType ()Lcom/google/protobuf/Parser; - public fun getSerializedSize ()I - public fun hashCode ()I - public final fun isInitialized ()Z - public static fun newBuilder ()Lstove/example/protobuf/OutputValue$Output$Builder; - public static fun newBuilder (Lstove/example/protobuf/OutputValue$Output;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/Message$Builder; - public synthetic fun newBuilderForType ()Lcom/google/protobuf/MessageLite$Builder; - public fun newBuilderForType ()Lstove/example/protobuf/OutputValue$Output$Builder; - public static fun parseDelimitedFrom (Ljava/io/InputStream;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseDelimitedFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Lcom/google/protobuf/ByteString;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Ljava/io/InputStream;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Ljava/io/InputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Ljava/nio/ByteBuffer;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom (Ljava/nio/ByteBuffer;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom ([B)Lstove/example/protobuf/OutputValue$Output; - public static fun parseFrom ([BLcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output; - public static fun parser ()Lcom/google/protobuf/Parser; - public synthetic fun toBuilder ()Lcom/google/protobuf/Message$Builder; - public synthetic fun toBuilder ()Lcom/google/protobuf/MessageLite$Builder; - public fun toBuilder ()Lstove/example/protobuf/OutputValue$Output$Builder; - public fun writeTo (Lcom/google/protobuf/CodedOutputStream;)V -} - -public final class stove/example/protobuf/OutputValue$Output$Builder : com/google/protobuf/GeneratedMessageV3$Builder, stove/example/protobuf/OutputValue$OutputOrBuilder { - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun addRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun build ()Lcom/google/protobuf/Message; - public synthetic fun build ()Lcom/google/protobuf/MessageLite; - public fun build ()Lstove/example/protobuf/OutputValue$Output; - public synthetic fun buildPartial ()Lcom/google/protobuf/Message; - public synthetic fun buildPartial ()Lcom/google/protobuf/MessageLite; - public fun buildPartial ()Lstove/example/protobuf/OutputValue$Output; - public synthetic fun clear ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clear ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clear ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clear ()Lcom/google/protobuf/MessageLite$Builder; - public fun clear ()Lstove/example/protobuf/OutputValue$Output$Builder; - public fun clearAge ()Lstove/example/protobuf/OutputValue$Output$Builder; - public fun clearBsn ()Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearField (Lcom/google/protobuf/Descriptors$FieldDescriptor;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun clearFirstName ()Lstove/example/protobuf/OutputValue$Output$Builder; - public fun clearLastName ()Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lcom/google/protobuf/Message$Builder; - public fun clearOneof (Lcom/google/protobuf/Descriptors$OneofDescriptor;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun clone ()Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun clone ()Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun clone ()Lcom/google/protobuf/Message$Builder; - public synthetic fun clone ()Lcom/google/protobuf/MessageLite$Builder; - public synthetic fun clone ()Ljava/lang/Object; - public fun clone ()Lstove/example/protobuf/OutputValue$Output$Builder; - public fun getAge ()I - public fun getBsn ()Ljava/lang/String; - public fun getBsnBytes ()Lcom/google/protobuf/ByteString; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/Message; - public synthetic fun getDefaultInstanceForType ()Lcom/google/protobuf/MessageLite; - public fun getDefaultInstanceForType ()Lstove/example/protobuf/OutputValue$Output; - public static final fun getDescriptor ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getDescriptorForType ()Lcom/google/protobuf/Descriptors$Descriptor; - public fun getFirstName ()Ljava/lang/String; - public fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public fun getLastName ()Ljava/lang/String; - public fun getLastNameBytes ()Lcom/google/protobuf/ByteString; - public final fun isInitialized ()Z - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/AbstractMessageLite$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/Message$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lcom/google/protobuf/MessageLite$Builder; - public fun mergeFrom (Lcom/google/protobuf/CodedInputStream;Lcom/google/protobuf/ExtensionRegistryLite;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeFrom (Lcom/google/protobuf/Message;)Lcom/google/protobuf/Message$Builder; - public fun mergeFrom (Lcom/google/protobuf/Message;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun mergeFrom (Lstove/example/protobuf/OutputValue$Output;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/AbstractMessage$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun mergeUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setAge (I)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setBsn (Ljava/lang/String;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setBsnBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setField (Lcom/google/protobuf/Descriptors$FieldDescriptor;Ljava/lang/Object;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setFirstName (Ljava/lang/String;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setFirstNameBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setLastName (Ljava/lang/String;)Lstove/example/protobuf/OutputValue$Output$Builder; - public fun setLastNameBytes (Lcom/google/protobuf/ByteString;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lcom/google/protobuf/Message$Builder; - public fun setRepeatedField (Lcom/google/protobuf/Descriptors$FieldDescriptor;ILjava/lang/Object;)Lstove/example/protobuf/OutputValue$Output$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/GeneratedMessageV3$Builder; - public synthetic fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lcom/google/protobuf/Message$Builder; - public final fun setUnknownFields (Lcom/google/protobuf/UnknownFieldSet;)Lstove/example/protobuf/OutputValue$Output$Builder; -} - -public abstract interface class stove/example/protobuf/OutputValue$OutputOrBuilder : com/google/protobuf/MessageOrBuilder { - public abstract fun getAge ()I - public abstract fun getBsn ()Ljava/lang/String; - public abstract fun getBsnBytes ()Lcom/google/protobuf/ByteString; - public abstract fun getFirstName ()Ljava/lang/String; - public abstract fun getFirstNameBytes ()Lcom/google/protobuf/ByteString; - public abstract fun getLastName ()Ljava/lang/String; - public abstract fun getLastNameBytes ()Lcom/google/protobuf/ByteString; -} - -public class stove/spring/streams/example/ExampleApp { - public fun ()V -} - -public final class stove/spring/streams/example/ExampleAppKt { - public static final fun main ([Ljava/lang/String;)V - public static final fun run ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lorg/springframework/context/ConfigurableApplicationContext; - public static synthetic fun run$default ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/springframework/context/ConfigurableApplicationContext; -} - -public final class stove/spring/streams/example/kafka/CustomDeserializationExceptionHandler : org/apache/kafka/streams/errors/DeserializationExceptionHandler { - public static final field Companion Lstove/spring/streams/example/kafka/CustomDeserializationExceptionHandler$Companion; - public fun ()V - public fun configure (Ljava/util/Map;)V - public fun handle (Lorg/apache/kafka/streams/processor/ProcessorContext;Lorg/apache/kafka/clients/consumer/ConsumerRecord;Ljava/lang/Exception;)Lorg/apache/kafka/streams/errors/DeserializationExceptionHandler$DeserializationHandlerResponse; -} - -public final class stove/spring/streams/example/kafka/CustomDeserializationExceptionHandler$Companion { -} - -public final class stove/spring/streams/example/kafka/CustomProductionExceptionHandler : org/apache/kafka/streams/errors/ProductionExceptionHandler { - public static final field Companion Lstove/spring/streams/example/kafka/CustomProductionExceptionHandler$Companion; - public fun ()V - public fun configure (Ljava/util/Map;)V - public fun handle (Lorg/apache/kafka/clients/producer/ProducerRecord;Ljava/lang/Exception;)Lorg/apache/kafka/streams/errors/ProductionExceptionHandler$ProductionExceptionHandlerResponse; -} - -public final class stove/spring/streams/example/kafka/CustomProductionExceptionHandler$Companion { -} - -public class stove/spring/streams/example/kafka/CustomSerDe { - public fun ()V - public fun createSerdeForValues ()Lio/confluent/kafka/streams/serdes/protobuf/KafkaProtobufSerde; - public fun getSchemaRegistryUrl ()Ljava/lang/String; -} - -public abstract class stove/spring/streams/example/kafka/KafkaRegistry { - public static final field Companion Lstove/spring/streams/example/kafka/KafkaRegistry$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getUrl ()Ljava/lang/String; -} - -public final class stove/spring/streams/example/kafka/KafkaRegistry$Companion { - public final fun createSerde (Ljava/lang/String;)Lio/confluent/kafka/streams/serdes/protobuf/KafkaProtobufSerde; - public final fun createSerde (Lstove/spring/streams/example/kafka/KafkaRegistry;)Lio/confluent/kafka/streams/serdes/protobuf/KafkaProtobufSerde; - public static synthetic fun createSerde$default (Lstove/spring/streams/example/kafka/KafkaRegistry$Companion;Lstove/spring/streams/example/kafka/KafkaRegistry;ILjava/lang/Object;)Lio/confluent/kafka/streams/serdes/protobuf/KafkaProtobufSerde; -} - -public final class stove/spring/streams/example/kafka/KafkaRegistry$Defined : stove/spring/streams/example/kafka/KafkaRegistry { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lstove/spring/streams/example/kafka/KafkaRegistry$Defined; - public static synthetic fun copy$default (Lstove/spring/streams/example/kafka/KafkaRegistry$Defined;Ljava/lang/String;ILjava/lang/Object;)Lstove/spring/streams/example/kafka/KafkaRegistry$Defined; - public fun equals (Ljava/lang/Object;)Z - public fun getUrl ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class stove/spring/streams/example/kafka/KafkaRegistry$Mock : stove/spring/streams/example/kafka/KafkaRegistry { - public static final field INSTANCE Lstove/spring/streams/example/kafka/KafkaRegistry$Mock; -} - -public class stove/spring/streams/example/kafka/StreamsConfig { - public fun ()V - public fun getBootstrapServers ()Ljava/lang/String; - public fun getInterceptorClass ()Ljava/util/List; - public fun kStreamsConfig ()Lorg/springframework/kafka/config/KafkaStreamsConfiguration; -} - -public class stove/spring/streams/example/kafka/application/processor/ExampleJoin { - public fun (Lstove/spring/streams/example/kafka/CustomSerDe;)V - public fun buildPipeline (Lorg/apache/kafka/streams/StreamsBuilder;)V -} - diff --git a/examples/spring-streams-example/build.gradle.kts b/examples/spring-streams-example/build.gradle.kts index 0cd75b67..d417cb8d 100644 --- a/examples/spring-streams-example/build.gradle.kts +++ b/examples/spring-streams-example/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { implementation(libs.kafka.streams) implementation(libs.kotlin.reflect) implementation(libs.google.protobuf.kotlin) - implementation(libs.kafka.streams.protobuf.serde) + implementation(libs.kafka.streams.registry) } dependencies { @@ -32,10 +32,6 @@ dependencies { application { mainClass.set("stove.spring.streams.example.ExampleAppkt") } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.stove.spring.streams.example.e2e.Stove") -} - java.sourceSets["main"].java { srcDir("build/generated/source/proto/main/java") srcDir("build/generated/source/proto/main/kotlin") diff --git a/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/CustomSerDe.kt b/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/CustomSerDe.kt index 69ffa7ff..2cf5dadb 100644 --- a/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/CustomSerDe.kt +++ b/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/CustomSerDe.kt @@ -12,33 +12,14 @@ class CustomSerDe { @Value("\${kafka.schema-registry-url}") val schemaRegistryUrl = "" - fun createSerdeForValues(): KafkaProtobufSerde = KafkaRegistry.createSerde(schemaRegistryUrl) -} - -sealed class KafkaRegistry( - open val url: String -) { - object Mock : KafkaRegistry("mock://mock-registry") - - data class Defined( - override val url: String - ) : KafkaRegistry(url) - - companion object { - fun createSerde(fromUrl: String): KafkaProtobufSerde = createSerde( - if (fromUrl.contains(Mock.url)) Mock else Defined(fromUrl) - ) - - fun createSerde(registry: KafkaRegistry = Mock): KafkaProtobufSerde { - val schemaRegistryClient = when (registry) { - is Mock -> MockSchemaRegistry.getClientForScope("mock-registry") - is Defined -> MockSchemaRegistry.getClientForScope(registry.url) - } - val serde: KafkaProtobufSerde = KafkaProtobufSerde(schemaRegistryClient) - val serdeConfig: MutableMap = HashMap() - serdeConfig[SCHEMA_REGISTRY_URL_CONFIG] = registry.url - serde.configure(serdeConfig, false) - return serde + fun createConfiguredSerdeForRecordValues(): KafkaProtobufSerde { + var serde: KafkaProtobufSerde = KafkaProtobufSerde() + if (schemaRegistryUrl.contains("mock://")) { + serde = KafkaProtobufSerde(MockSchemaRegistry.getClientForScope("mock-registry")) } + val serdeConfig: MutableMap = HashMap() + serdeConfig[SCHEMA_REGISTRY_URL_CONFIG] = schemaRegistryUrl + serde.configure(serdeConfig, false) + return serde } } diff --git a/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/application/processor/ExampleJoin.kt b/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/application/processor/ExampleJoin.kt index b51f514b..072aff50 100644 --- a/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/application/processor/ExampleJoin.kt +++ b/examples/spring-streams-example/src/main/kotlin/stove/spring/streams/example/kafka/application/processor/ExampleJoin.kt @@ -16,10 +16,8 @@ import stove.spring.streams.example.kafka.CustomSerDe @Component @EnableKafka @EnableKafkaStreams -class ExampleJoin( - customSerDe: CustomSerDe -) { - private val protobufSerde: KafkaProtobufSerde = customSerDe.createSerdeForValues() +class ExampleJoin(customSerDe: CustomSerDe) { + private val protobufSerde: KafkaProtobufSerde = customSerDe.createConfiguredSerdeForRecordValues() private val byteArraySerde: Serde = Serdes.ByteArray() private val stringSerde: Serde = Serdes.String() @@ -33,20 +31,21 @@ class ExampleJoin( .stream("input2", Consumed.with(stringSerde, protobufSerde)) .toTable(Materialized.with(stringSerde, protobufSerde)) - val joinedTable = input1.join( - input2, - { input1Message: Message, input2Message: Message -> - protobufSerde.serializer().serialize( - "output", - output { - this.firstName = Input1.parseFrom(input1Message.toByteArray()).firstName - this.lastName = Input1.parseFrom(input1Message.toByteArray()).lastName - this.bsn = Input2.parseFrom(input2Message.toByteArray()).bsn - this.age = Input2.parseFrom(input2Message.toByteArray()).age - } - ) - } - ) + val joinedTable = + input1.join( + input2, + { input1Message: Message, input2Message: Message -> + protobufSerde.serializer().serialize( + "output", + output { + this.firstName = Input1.parseFrom(input1Message.toByteArray()).firstName + this.lastName = Input1.parseFrom(input1Message.toByteArray()).lastName + this.bsn = Input2.parseFrom(input2Message.toByteArray()).bsn + this.age = Input2.parseFrom(input2Message.toByteArray()).age + } + ) + } + ) joinedTable.toStream().to("output", Produced.with(stringSerde, byteArraySerde)) } } diff --git a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/ExampleTest.kt b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/ExampleTest.kt index e8143f17..ba9c1a70 100644 --- a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/ExampleTest.kt +++ b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/ExampleTest.kt @@ -4,6 +4,7 @@ import arrow.core.Option import com.google.protobuf.Message import com.trendyol.stove.testing.e2e.standalone.kafka.kafka import com.trendyol.stove.testing.e2e.system.TestSystem +import io.confluent.kafka.streams.serdes.protobuf.KafkaProtobufSerde import io.kotest.core.spec.style.FunSpec import org.apache.kafka.common.serialization.StringDeserializer import stove.example.protobuf.* @@ -13,79 +14,94 @@ import stove.example.protobuf.OutputValue.Output import java.util.* import kotlin.time.Duration.Companion.seconds -class ExampleTest : - FunSpec({ - test("expect join") { +class ExampleTest : FunSpec({ + val protobufSerde: KafkaProtobufSerde = createConfiguredSerdeForRecordValues() + + test("expect join") { /*------------------------- | Create test data --------------------------*/ - val firstName = UUID.randomUUID().toString() - val lastName = UUID.randomUUID().toString() - val bsn = UUID.randomUUID().toString() - val age = 18 + val firstName = UUID.randomUUID().toString() + val lastName = UUID.randomUUID().toString() + val bsn = UUID.randomUUID().toString() + val age = 18 - // create input - val input1Message = input1 { - this.firstName = firstName - this.lastName = lastName - } - val input2Message = input2 { - this.bsn = bsn - this.age = age - } - val outputMessage = output { - this.firstName = firstName - this.lastName = lastName - this.bsn = bsn - this.age = age - } + // create input + val input1Message = input1 { + this.firstName = firstName + this.lastName = lastName + } + val input2Message = input2 { + this.bsn = bsn + this.age = age + } + val outputMessage = output { + this.firstName = firstName + this.lastName = lastName + this.bsn = bsn + this.age = age + } - TestSystem.validate { - kafka { + TestSystem.validate { + kafka { /*------------------------- | publish kafka messages --------------------------*/ - // inputs - publish(INPUT_TOPIC, input1Message, Option("test")) - publish(INPUT_TOPIC2, input2Message, Option("test")) + // inputs + publish(INPUT_TOPIC, input1Message, Option("test")) + publish(INPUT_TOPIC2, input2Message, Option("test")) /*--------------------------- | verify messages consumed ----------------------------*/ - // Assert input1 message is consumed - shouldBeConsumed { - actual == input1Message - } + // Assert input1 message is consumed + shouldBeConsumed { + protobufSerde.messageAsBase64(actual) + .isSome { message -> + message.onMatchingAssert(Input1.getDescriptor().name) { + Input1.parseFrom(it.toByteArray()) == input1Message + } + } + } - // Assert input2 message is consumed - shouldBeConsumed { - actual == input2Message - } + // Assert input2 message is consumed + shouldBeConsumed { + protobufSerde.messageAsBase64(actual) + .isSome { message -> + message.onMatchingAssert(Input2.getDescriptor().name) { + Input2.parseFrom(it.toByteArray()) == input2Message + } + } + } /*--------------------------- | verify messages published ----------------------------*/ - // Assert joined message is correctly published - shouldBePublished(atLeastIn = 20.seconds) { - actual.bsn == bsn + // Assert joined message is correctly published + shouldBePublished(atLeastIn = 20.seconds) { + protobufSerde.messageAsBase64(actual).isSome { message -> + message.onMatchingAssert(Output.getDescriptor().name) { + Output.parseFrom(it.toByteArray()) == outputMessage + } } + } - // Assert joined message is correctly published - // Similar to test above, but is able to run even if no messages are published - consumer( - "output", - valueDeserializer = StoveKafkaValueDeserializer(), - keyDeserializer = StringDeserializer() - ) { record -> - if (Output.parseFrom(record.value().toByteArray()) != outputMessage) throw AssertionError() - } + // Assert joined message is correctly published + // Similar to test above, but is able to run even if no messages are published + consumer( + "output", + valueDeserializer = StoveKafkaValueDeserializer(), + keyDeserializer = StringDeserializer() + ) { record -> + if (Output.parseFrom(record.value().toByteArray()) != outputMessage) throw AssertionError() } } } - }) { + } +}) { companion object { const val INPUT_TOPIC = "input1" const val INPUT_TOPIC2 = "input2" diff --git a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestHelper.kt b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestHelper.kt index e665121e..af79819d 100644 --- a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestHelper.kt +++ b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestHelper.kt @@ -2,16 +2,15 @@ package com.stove.spring.streams.example.e2e import arrow.core.* import com.google.protobuf.Message -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import io.confluent.kafka.schemaregistry.testutil.MockSchemaRegistry +import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG import io.confluent.kafka.streams.serdes.protobuf.KafkaProtobufSerde -import kotlinx.serialization.ExperimentalSerializationApi import okio.ByteString.Companion.toByteString import org.apache.kafka.common.serialization.* -import stove.spring.streams.example.kafka.KafkaRegistry import java.util.* class StoveKafkaValueSerializer : Serializer { - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde(KafkaRegistry.Mock) + private val protobufSerde: KafkaProtobufSerde = createConfiguredSerdeForRecordValues() override fun serialize( topic: String, @@ -22,8 +21,8 @@ class StoveKafkaValueSerializer : Serializer { } } -class StoveKafkaValueDeserializer : Deserializer { - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde(KafkaRegistry.Mock) +class StoveKafkaValueDeserializer : Deserializer { + private val protobufSerde: KafkaProtobufSerde = createConfiguredSerdeForRecordValues() override fun deserialize( topic: String, @@ -31,47 +30,28 @@ class StoveKafkaValueDeserializer : Deserializer { ): Message = protobufSerde.deserializer().deserialize(topic, data) } -@Suppress("UNCHECKED_CAST") -@OptIn(ExperimentalSerializationApi::class) -class StoveProtobufSerde : StoveSerde { - private val parseFromMethod = "parseFrom" - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde(KafkaRegistry.Mock) - - override fun serialize(value: Any): ByteArray = protobufSerde.serializer().serialize("any", value as Message) - - override fun deserialize(value: ByteArray, clazz: Class): T { - val incoming: Message = protobufSerde.deserializer().deserialize("any", value) - incoming.isAssignableFrom(clazz).also { isAssignableFrom -> - require(isAssignableFrom) { - "Expected '${clazz.simpleName}' but got '${incoming.descriptorForType.name}'. " + - "This could be transient ser/de problem since the message stream is constantly checked if the expected message is arrived, " + - "so you can ignore this error if you are sure that the message is the expected one." - } - } - - val parseFromMethod = clazz.getDeclaredMethod(parseFromMethod, ByteArray::class.java) - val parsed = parseFromMethod(incoming, incoming.toByteArray()) as T - return parsed - } +fun createConfiguredSerdeForRecordValues(): KafkaProtobufSerde { + val schemaRegistryClient = MockSchemaRegistry.getClientForScope("mock-registry") + val serde: KafkaProtobufSerde = KafkaProtobufSerde(schemaRegistryClient) + val serdeConfig: MutableMap = HashMap() + serdeConfig[SCHEMA_REGISTRY_URL_CONFIG] = "mock://mock-registry" + serde.configure(serdeConfig, false) + return serde } -private fun Message.isAssignableFrom(clazz: Class<*>): Boolean = this.descriptorForType.name == clazz.simpleName - fun KafkaProtobufSerde.messageAsBase64( message: Any -): Option = Either - .catch { - deserializer() - .deserialize( - "any", - Base64 - .getDecoder() - .decode(message.toString()) - .toByteString() - .toByteArray() - ) - }.getOrNull() - .toOption() +): Option = Either.catch { + deserializer() + .deserialize( + "any", + Base64 + .getDecoder() + .decode(message.toString()) + .toByteString() + .toByteArray() + ) +}.getOrNull().toOption() fun Message.onMatchingAssert( descriptor: String, diff --git a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt similarity index 93% rename from examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt rename to examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt index 4e15031a..7c23ff0e 100644 --- a/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/Stove.kt +++ b/examples/spring-streams-example/src/test/kotlin/com/stove/spring/streams/example/e2e/TestSystemConfig.kt @@ -10,16 +10,16 @@ import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.config.AbstractProjectConfig import org.apache.kafka.clients.admin.NewTopic -class Stove : AbstractProjectConfig() { +class TestSystemConfig : AbstractProjectConfig() { @Suppress("LongMethod") override suspend fun beforeProject(): Unit = TestSystem() .also { stoveKafkaBridgePortDefault = "50054" - }.with { + } + .with { kafka { KafkaSystemOptions( listenPublishedMessagesFromStove = false, - serde = StoveProtobufSerde(), valueSerializer = StoveKafkaValueSerializer(), containerOptions = KafkaContainerOptions(tag = "latest") { } ) { @@ -36,8 +36,7 @@ class Stove : AbstractProjectConfig() { bridge() springBoot( runner = { parameters -> - stove.spring.streams.example - .run(parameters) + stove.spring.streams.example.run(parameters) }, withParameters = listOf( "server.port=8001", diff --git a/gradle.properties b/gradle.properties index 9de20adb..4a6ed33e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,6 @@ projectDescription=The easiest way of e2e testing in Kotlin projectUrl=https://github.com/Trendyol/stove licenceUrl=https://github.com/Trendyol/stove/blob/master/LICENCE licence=Apache-2.0 license -snapshot=1.0.0 -version=0.15.0 - - +snapshot=1.0.0-SNAPSHOT +version=0.14.2 +micronautVersion=4.4.3 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7774b84e..6a25b954 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.1.0" +kotlin = "1.9.25" kotlinx = "1.9.0" spring-boot = "2.7.18" spring-framework = "5.3.39" @@ -8,11 +8,11 @@ spring-boot-3x = "3.4.0" spring-dependency-management = "1.1.6" spring-kafka = "2.9.13" spring-kafka-3x = "3.3.0" -couchbase-client = "3.7.6" -couchbase-client-metrics = "0.7.6" -couchbase-kotlin = "1.4.6" -jackson = "2.18.2" -arrow = "2.0.0" +couchbase-client = "3.7.5" +couchbase-client-metrics = "0.7.5" +couchbase-kotlin = "1.4.5" +jackson = "2.18.1" +arrow = "1.2.4" arrowSuspendApp = "0.4.0" arrow-jackson = "0.14.1" io-reactor = "3.7.0" @@ -20,7 +20,7 @@ io-reactor-extensions = "1.2.3" slf4j = "2.0.16" kafka = "3.9.0" kafka-kotlin = "0.4.0" -kafka-streams-registry = "7.8.0" +kafka-streams-registry = "7.7.1" dokka = "1.9.20" knit = "0.5.0" kover = "0.8.3" @@ -30,28 +30,25 @@ r2dbc-spi = "1.0.0.RELEASE" r2dbc-postgresql = "0.8.13.RELEASE" elastic = "8.16.1" mongodb = "5.2.1" -wiremock = "3.10.0" +wiremock = "3.9.2" testcontainers = "1.20.4" r2dbc-mssql = "1.0.2.RELEASE" -spotless = "7.0.0.BETA4" +spotless = "6.25.0" detekt = "1.23.7" wire = "5.1.0" -io-grpc = "1.68.2" +io-grpc = "1.68.1" io-grpc-kotlin = "1.4.1" google-protobuf = "3.25.5" gradle-release = "3.0.2" nexusPublish = "2.0.0" shadow = "8.1.1" -hoplite = "2.9.0" +hoplite = "2.8.2" junit = "5.11.3" -kotest = "6.0.0.M1" +kotest = "5.9.1" mockito = "5.4.0" quiver = "0.5.12" akkurate = "0.10.0" -exposed = "0.57.0" -kotlinx-serialization = "1.7.3" -ktlint = "1.5.0" -kotlinBinaryCompatibilityValidator = "0.16.3" +exposed = "0.56.0" [libraries] kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } @@ -64,8 +61,6 @@ 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-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version.ref = "kotlinx-serialization" } -kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" } # Arrow arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } @@ -90,7 +85,7 @@ akkurate-ksp-plugin = { module = "dev.nesk.akkurate:akkurate-ksp-plugin", versio kafka = { module = "org.apache.kafka:kafka-clients", version.ref = "kafka" } kafkaKotlin = { module = "io.github.nomisrev:kotlin-kafka", version.ref = "kafka-kotlin" } kafka-streams = { module = "org.apache.kafka:kafka-streams", version.ref = "kafka" } -kafka-streams-protobuf-serde = { module = "io.confluent:kafka-streams-protobuf-serde", version.ref = "kafka-streams-registry" } +kafka-streams-registry = { module = "io.confluent:kafka-streams-protobuf-serde", version.ref = "kafka-streams-registry" } # Couchbase couchbase-kotlin = { module = "com.couchbase.client:kotlin-client", version.ref = "couchbase-kotlin" } @@ -136,13 +131,10 @@ 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.2" } # misc -lettuce-core = { module = "io.lettuce:lettuce-core", version = "6.5.1.RELEASE" } +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" } @@ -194,8 +186,6 @@ 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" } @@ -207,8 +197,6 @@ kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } kotest-framework-api = { module = "io.kotest:kotest-framework-api", version.ref = "kotest" } ktor-server-tests-jvm = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } -kotest-arrow = { module = "io.kotest.extensions:kotest-assertions-arrow", version = "1.4.0" } -ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } [bundles] arrow-stack = ["arrow-core", @@ -249,7 +237,5 @@ 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" } -binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinBinaryCompatibilityValidator" } diff --git a/lib/stove-testing-e2e-couchbase/api/stove-testing-e2e-couchbase.api b/lib/stove-testing-e2e-couchbase/api/stove-testing-e2e-couchbase.api deleted file mode 100644 index c54f6280..00000000 --- a/lib/stove-testing-e2e-couchbase/api/stove-testing-e2e-couchbase.api +++ /dev/null @@ -1,133 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseContext { - public fun (Lorg/testcontainers/couchbase/BucketDefinition;Lcom/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions;)V - public final fun component1 ()Lorg/testcontainers/couchbase/BucketDefinition; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer; - public final fun component3 ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions; - public final fun copy (Lorg/testcontainers/couchbase/BucketDefinition;Lcom/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContext;Lorg/testcontainers/couchbase/BucketDefinition;Lcom/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getBucket ()Lorg/testcontainers/couchbase/BucketDefinition; - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/couchbase/CouchbaseDsl : java/lang/annotation/Annotation { -} - -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseExposedConfiguration;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getConnectionString ()Ljava/lang/String; - public final fun getHostsWithPort ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem : com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem$Companion; - public field cluster Lcom/couchbase/client/kotlin/Cluster; - public field collection Lcom/couchbase/client/kotlin/Collection; - public fun close ()V - public fun configuration ()Ljava/util/List; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getCluster ()Lcom/couchbase/client/kotlin/Cluster; - public final fun getCollection ()Lcom/couchbase/client/kotlin/Collection; - public final fun getContext ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContext; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun setCluster (Lcom/couchbase/client/kotlin/Cluster;)V - public final fun setCollection (Lcom/couchbase/client/kotlin/Collection;)V - public final fun shouldDelete (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldDelete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldNotExist (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldNotExist (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem; -} - -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem$Companion { - public final fun bucket (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem;)Lcom/couchbase/client/kotlin/Bucket; - public final fun cluster (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystem;)Lcom/couchbase/client/kotlin/Cluster; -} - -public final class com/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions;Lcom/couchbase/client/kotlin/codec/JsonSerializer;Lcom/couchbase/client/kotlin/codec/Transcoder;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions;Lcom/couchbase/client/kotlin/codec/JsonSerializer;Lcom/couchbase/client/kotlin/codec/Transcoder;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions; - public final fun component3 ()Lcom/couchbase/client/kotlin/codec/JsonSerializer; - public final fun component4 ()Lcom/couchbase/client/kotlin/codec/Transcoder; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions;Lcom/couchbase/client/kotlin/codec/JsonSerializer;Lcom/couchbase/client/kotlin/codec/Transcoder;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions;Lcom/couchbase/client/kotlin/codec/JsonSerializer;Lcom/couchbase/client/kotlin/codec/Transcoder;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getClusterSerDe ()Lcom/couchbase/client/kotlin/codec/JsonSerializer; - public final fun getClusterTranscoder ()Lcom/couchbase/client/kotlin/codec/Transcoder; - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainerOptions ()Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseContainerOptions; - public final fun getDefaultBucket ()Ljava/lang/String; - public fun hashCode ()I - public final fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/couchbase/CouchbaseSystemOptions; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/couchbase/OptionsKt { - public static final fun couchbase-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun couchbase-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public class com/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer : org/testcontainers/couchbase/CouchbaseContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - -public final class com/trendyol/stove/testing/e2e/couchbase/UtilKt { - public static final fun waitForKeySpaceAvailability-45ZY6uE (Lcom/couchbase/client/kotlin/Cluster;Ljava/lang/String;Ljava/lang/String;JJLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun waitForKeySpaceAvailability-45ZY6uE$default (Lcom/couchbase/client/kotlin/Cluster;Ljava/lang/String;Ljava/lang/String;JJLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun waitUntilIndexIsCreated-WPi__2c (Lcom/couchbase/client/kotlin/Cluster;Ljava/lang/String;JJLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun waitUntilIndexIsCreated-WPi__2c$default (Lcom/couchbase/client/kotlin/Cluster;Ljava/lang/String;JJLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun waitUntilSucceeds-SYHnMyU (Lcom/couchbase/client/kotlin/Cluster;Lkotlin/jvm/functions/Function1;JJLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun waitUntilSucceeds-SYHnMyU$default (Lcom/couchbase/client/kotlin/Cluster;Lkotlin/jvm/functions/Function1;JJLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; -} - diff --git a/lib/stove-testing-e2e-couchbase/build.gradle.kts b/lib/stove-testing-e2e-couchbase/build.gradle.kts index e301d679..ee33cc9a 100644 --- a/lib/stove-testing-e2e-couchbase/build.gradle.kts +++ b/lib/stove-testing-e2e-couchbase/build.gradle.kts @@ -7,7 +7,3 @@ dependencies { dependencies { testImplementation(libs.slf4j.simple) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.couchbase.Stove") -} 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 b4edf727..553c5da8 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,12 +1,13 @@ package com.trendyol.stove.testing.e2e.couchbase import com.couchbase.client.kotlin.* -import com.couchbase.client.kotlin.codec.typeRef +import com.couchbase.client.kotlin.Collection +import com.couchbase.client.kotlin.codec.* 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.* @@ -14,14 +15,15 @@ import org.slf4j.* class CouchbaseSystem internal constructor( override val testSystem: TestSystem, val context: CouchbaseContext -) : PluggedSystem, - RunAware, - ExposesConfiguration { +) : PluggedSystem, RunAware, ExposesConfiguration { @PublishedApi internal lateinit var cluster: Cluster @PublishedApi - internal lateinit var collection: com.couchbase.client.kotlin.Collection + internal lateinit var collection: Collection + + @PublishedApi + internal val objectMapper: ObjectMapper = context.options.objectMapper private lateinit var exposedConfiguration: CouchbaseExposedConfiguration private val logger: Logger = LoggerFactory.getLogger(javaClass) @@ -57,16 +59,17 @@ class CouchbaseSystem internal constructor( query: String, assertion: (List) -> Unit ): CouchbaseSystem { - 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 } + 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 } @CouchbaseDsl @@ -74,8 +77,7 @@ class CouchbaseSystem internal constructor( key: String, assertion: (T) -> Unit ): CouchbaseSystem = - collection - .get(key) + collection.get(key) .contentAs() .let(assertion) .let { this } @@ -86,8 +88,7 @@ class CouchbaseSystem internal constructor( key: String, assertion: (T) -> Unit ): CouchbaseSystem = - cluster - .bucket(context.bucket.name) + cluster.bucket(context.bucket.name) .collection(collection) .get(key) .contentAs() @@ -121,8 +122,7 @@ class CouchbaseSystem internal constructor( suspend fun shouldDelete( collection: String, key: String - ): CouchbaseSystem = cluster - .bucket(context.bucket.name) + ): CouchbaseSystem = cluster.bucket(context.bucket.name) .collection(collection) .remove(key) .let { this } @@ -139,8 +139,7 @@ class CouchbaseSystem internal constructor( ): CouchbaseSystem = cluster .bucket(context.bucket.name) .collection(collection) - .insert(id, instance) - .let { this } + .insert(id, instance).let { this } /** * Saves the [instance] with given [id] to the default collection @@ -175,13 +174,16 @@ class CouchbaseSystem internal constructor( } } - private fun createCluster(exposedConfiguration: CouchbaseExposedConfiguration): Cluster = Cluster.connect( - exposedConfiguration.hostsWithPort, - exposedConfiguration.username, - exposedConfiguration.password - ) { - jsonSerializer = context.options.clusterSerDe - transcoder = context.options.clusterTranscoder + private fun createCluster(exposedConfiguration: CouchbaseExposedConfiguration): Cluster { + val jackson = JacksonJsonSerializer(objectMapper) + return Cluster.connect( + exposedConfiguration.hostsWithPort, + exposedConfiguration.username, + exposedConfiguration.password + ) { + jsonSerializer = jackson + transcoder = JsonTranscoder(jackson) + } } 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 95b378f2..eb261a7f 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.couchbase.client.kotlin.codec.* +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.containers.* import com.trendyol.stove.testing.e2e.database.migrations.* -import com.trendyol.stove.testing.e2e.serialization.E2eObjectMapperConfig +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper 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,11 +22,9 @@ data class CouchbaseExposedConfiguration( data class CouchbaseSystemOptions( val defaultBucket: String, val containerOptions: CouchbaseContainerOptions = CouchbaseContainerOptions(), - val clusterSerDe: JsonSerializer = JacksonJsonSerializer(E2eObjectMapperConfig.createObjectMapperWithDefaults()), - val clusterTranscoder: Transcoder = JsonTranscoder(clusterSerDe), + val objectMapper: ObjectMapper = StoveObjectMapper.Default, override val configureExposedConfiguration: (CouchbaseExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration { +) : SystemOptions, ConfiguresExposedConfiguration { internal val migrationCollection: MigrationCollection = MigrationCollection() /** @@ -64,8 +62,7 @@ internal fun TestSystem.withCouchbase(options: CouchbaseSystemOptions): TestSyst registry = options.containerOptions.registry, compatibleSubstitute = options.containerOptions.compatibleSubstitute ) { - options.containerOptions - .useContainerFn(it) + options.containerOptions.useContainerFn(it) .withBucket(bucketDefinition) .withReuse(this.options.keepDependenciesRunning) .let { c -> c as StoveCouchbaseContainer } diff --git a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer.kt b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer.kt index b8272b71..6fa5f890 100644 --- a/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer.kt +++ b/lib/stove-testing-e2e-couchbase/src/main/kotlin/com/trendyol/stove/testing/e2e/couchbase/StoveCouchbaseContainer.kt @@ -6,5 +6,4 @@ import org.testcontainers.utility.DockerImageName open class StoveCouchbaseContainer( override val imageNameAccess: DockerImageName -) : CouchbaseContainer(imageNameAccess), - StoveContainer +) : CouchbaseContainer(imageNameAccess), StoveContainer diff --git a/lib/stove-testing-e2e-couchbase/src/test/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseTestSystemTests.kt b/lib/stove-testing-e2e-couchbase/src/test/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseTestSystemTests.kt index 00954dfc..4c88eb89 100644 --- a/lib/stove-testing-e2e-couchbase/src/test/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseTestSystemTests.kt +++ b/lib/stove-testing-e2e-couchbase/src/test/kotlin/com/trendyol/stove/testing/e2e/couchbase/CouchbaseTestSystemTests.kt @@ -20,9 +20,7 @@ import kotlin.time.Duration.Companion.seconds const val TEST_BUCKET = "test-couchbase-bucket" -class ExtendedCouchbaseContainer( - dockerImageName: DockerImageName -) : StoveCouchbaseContainer(dockerImageName) { +class ExtendedCouchbaseContainer(dockerImageName: DockerImageName) : StoveCouchbaseContainer(dockerImageName) { private val logger: Logger = LoggerFactory.getLogger(javaClass) override fun start() { @@ -36,29 +34,28 @@ class ExtendedCouchbaseContainer( } } -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = - TestSystem {} - .with { - couchbase { - CouchbaseSystemOptions( - defaultBucket = TEST_BUCKET, - configureExposedConfiguration = { _ -> - listOf() - }, - containerOptions = CouchbaseContainerOptions( - useContainerFn = { ExtendedCouchbaseContainer(it) }, - tag = "7.6.1" - ) { - withStartupAttempts(3) - withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) - } - ).migrations { - register() + TestSystem {}.with { + couchbase { + CouchbaseSystemOptions( + defaultBucket = TEST_BUCKET, + configureExposedConfiguration = { _ -> + listOf() + }, + containerOptions = CouchbaseContainerOptions( + useContainerFn = { ExtendedCouchbaseContainer(it) }, + tag = "7.6.1" + ) { + withStartupAttempts(3) + withEnabledServices(CouchbaseService.KV, CouchbaseService.INDEX, CouchbaseService.QUERY) } + ).migrations { + register() } - applicationUnderTest(NoOpApplication()) - }.run() + } + applicationUnderTest(NoOpApplication()) + }.run() override suspend fun afterProject(): Unit = TestSystem.stop() } @@ -77,8 +74,7 @@ class DefaultMigration : DatabaseMigration { override suspend fun execute(connection: Cluster) { connection .bucket(TEST_BUCKET) - .collections - .createCollection("_default", "another") + .collections.createCollection("_default", "another") connection.bucket(TEST_BUCKET).waitUntilReady(30.seconds) connection.waitUntilIndexIsCreated("CREATE PRIMARY INDEX ON `${connection.bucket(TEST_BUCKET).name}`.`_default`.`another`", 30.seconds) @@ -87,129 +83,128 @@ class DefaultMigration : DatabaseMigration { } } -class CouchbaseTestSystemUsesDslTests : - FunSpec({ - - data class ExampleInstance( - val id: String, - val description: String - ) - - test("should save and get") { - val id = UUID.randomUUID().toString() - val anotherCollectionName = "another" - validate { - couchbase { - saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) - save(anotherCollectionName, id = id, ExampleInstance(id = id, description = testCase.name.testName)) - shouldGet(id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } - shouldGet(anotherCollectionName, id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } +class CouchbaseTestSystemUsesDslTests : FunSpec({ + + data class ExampleInstance( + val id: String, + val description: String + ) + + test("should save and get") { + val id = UUID.randomUUID().toString() + val anotherCollectionName = "another" + validate { + couchbase { + saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) + save(anotherCollectionName, id = id, ExampleInstance(id = id, description = testCase.name.testName)) + shouldGet(id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName + } + shouldGet(anotherCollectionName, id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName } } } + } - test("should not get when document does not exist") { - val id = UUID.randomUUID().toString() - val notExistDocId = UUID.randomUUID().toString() - validate { - couchbase { - saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) - shouldGet(id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } - shouldNotExist(notExistDocId) + test("should not get when document does not exist") { + val id = UUID.randomUUID().toString() + val notExistDocId = UUID.randomUUID().toString() + validate { + couchbase { + saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) + shouldGet(id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName } + shouldNotExist(notExistDocId) } } + } - test("should throw assertion exception when document exist") { - val id = UUID.randomUUID().toString() - validate { - couchbase { - saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) - shouldGet(id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } - assertThrows { shouldNotExist(id) } + test("should throw assertion exception when document exist") { + val id = UUID.randomUUID().toString() + validate { + couchbase { + saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) + shouldGet(id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName } + assertThrows { shouldNotExist(id) } } } + } - test("should delete") { - val id = UUID.randomUUID().toString() - validate { - couchbase { - saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) - shouldGet(id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } - shouldDelete(id) - shouldNotExist(id) + test("should delete") { + val id = UUID.randomUUID().toString() + validate { + couchbase { + saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) + shouldGet(id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName } + shouldDelete(id) + shouldNotExist(id) } } + } - test("should delete from another collection") { - val id = UUID.randomUUID().toString() - val anotherCollectionName = "another" - validate { - couchbase { - save(anotherCollectionName, id = id, ExampleInstance(id = id, description = testCase.name.testName)) - shouldGet(anotherCollectionName, id) { actual -> - actual.id shouldBe id - actual.description shouldBe testCase.name.testName - } - shouldDelete(anotherCollectionName, id) - shouldNotExist(anotherCollectionName, id) + test("should delete from another collection") { + val id = UUID.randomUUID().toString() + val anotherCollectionName = "another" + validate { + couchbase { + save(anotherCollectionName, id = id, ExampleInstance(id = id, description = testCase.name.testName)) + shouldGet(anotherCollectionName, id) { actual -> + actual.id shouldBe id + actual.description shouldBe testCase.name.testName } + shouldDelete(anotherCollectionName, id) + shouldNotExist(anotherCollectionName, id) } } + } - test("should not delete when document does not exist") { - val id = UUID.randomUUID().toString() - validate { - couchbase { - shouldNotExist(id) - assertThrows { shouldDelete(id) } - } + test("should not delete when document does not exist") { + val id = UUID.randomUUID().toString() + validate { + couchbase { + shouldNotExist(id) + assertThrows { shouldDelete(id) } } } + } - test("should not delete from another collection when document does not exist") { - val id = UUID.randomUUID().toString() - val anotherCollectionName = "another" - validate { - couchbase { - shouldNotExist(anotherCollectionName, id) - assertThrows { shouldDelete(anotherCollectionName, id) } - } + test("should not delete from another collection when document does not exist") { + val id = UUID.randomUUID().toString() + val anotherCollectionName = "another" + validate { + couchbase { + shouldNotExist(anotherCollectionName, id) + assertThrows { shouldDelete(anotherCollectionName, id) } } } + } - test("should query") { - val id = UUID.randomUUID().toString() - val id2 = UUID.randomUUID().toString() - validate { - couchbase { - saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) - saveToDefaultCollection(id2, ExampleInstance(id = id2, description = testCase.name.testName)) - shouldQuery( - "SELECT c.id, c.* FROM `${this.bucket().name}`.`${this.collection.scope.name}`.`${this.collection.name}` c" - ) { result -> - result.size shouldBeGreaterThanOrEqual 2 - result.contains(ExampleInstance(id = id, description = testCase.name.testName)) shouldBe true - result.contains(ExampleInstance(id = id2, description = testCase.name.testName)) shouldBe true - } + test("should query") { + val id = UUID.randomUUID().toString() + val id2 = UUID.randomUUID().toString() + validate { + couchbase { + saveToDefaultCollection(id, ExampleInstance(id = id, description = testCase.name.testName)) + saveToDefaultCollection(id2, ExampleInstance(id = id2, description = testCase.name.testName)) + shouldQuery( + "SELECT c.id, c.* FROM `${this.bucket().name}`.`${this.collection.scope.name}`.`${this.collection.name}` c" + ) { result -> + result.size shouldBeGreaterThanOrEqual 2 + result.contains(ExampleInstance(id = id, description = testCase.name.testName)) shouldBe true + result.contains(ExampleInstance(id = id2, description = testCase.name.testName)) shouldBe true } } } - }) + } +}) diff --git a/lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api b/lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api deleted file mode 100644 index ab58d20b..00000000 --- a/lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api +++ /dev/null @@ -1,159 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer { - public fun ()V - public fun (Lkotlin/jvm/functions/Function1;Larrow/core/Option;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;Larrow/core/Option;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlin/jvm/functions/Function1; - public final fun component2 ()Larrow/core/Option; - public final fun copy (Lkotlin/jvm/functions/Function1;Larrow/core/Option;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lkotlin/jvm/functions/Function1;Larrow/core/Option;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer; - public fun equals (Ljava/lang/Object;)Z - public final fun getHttpClientBuilder ()Lkotlin/jvm/functions/Function1; - public final fun getRestClientOverrideFn ()Larrow/core/Option; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public static final field Companion Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions$Companion; - public static final field DEFAULT_ELASTIC_PORT I - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/util/List; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Z - public final fun component8 ()Lkotlin/jvm/functions/Function1; - public final fun component9 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public final fun getDisableSecurity ()Z - public final fun getExposedPorts ()Ljava/util/List; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions$Companion { -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/elasticsearch/ElasticDsl : java/lang/annotation/Annotation { -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; - public final fun copy (Ljava/lang/String;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration;Ljava/lang/String;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getCertificate ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; - public final fun getHost ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getPort ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchContext { - public fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; - public final fun copy (Lcom/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchContext;Lcom/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate { - public static final field Companion Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate$Companion; - public fun ([B)V - public final fun component1 ()[B - public final fun copy ([B)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;[BILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; - public static final fun create ([B)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; - public fun equals (Ljava/lang/Object;)Z - public final fun getBytes ()[B - public final fun getSslContext ()Ljavax/net/ssl/SSLContext; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate$Companion { - public final fun create ([B)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem : com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAware, com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$Companion; - public field esClient Lco/elastic/clients/elasticsearch/ElasticsearchClient; - public fun afterRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun close ()V - public fun configuration ()Ljava/util/List; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getEsClient ()Lco/elastic/clients/elasticsearch/ElasticsearchClient; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun save (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; - public final fun setEsClient (Lco/elastic/clients/elasticsearch/ElasticsearchClient;)V - public final fun shouldDelete (Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; - public final fun shouldNotExist (Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$Companion { - public final fun client (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem;)Lco/elastic/clients/elasticsearch/ElasticsearchClient; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions; - public final fun component3 ()Lco/elastic/clients/json/JsonpMapper; - public final fun component4 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getClientConfigurer ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer; - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions; - public final fun getJsonpMapper ()Lco/elastic/clients/json/JsonpMapper; - public fun hashCode ()I - public final fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/elasticsearch/ExtensionsKt { - public static final fun elasticsearch-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun elasticsearch-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public class com/trendyol/stove/testing/e2e/elasticsearch/StoveElasticSearchContainer : org/testcontainers/elasticsearch/ElasticsearchContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - diff --git a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts index 5a295a43..e5550a3a 100644 --- a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts +++ b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts @@ -1,15 +1,12 @@ plugins {} dependencies { - api(projects.lib.stoveTestingE2e) - api(libs.elastic) - api(libs.testcontainers.elasticsearch) - implementation(libs.jackson.databind) + api(projects.lib.stoveTestingE2e) + api(libs.elastic) + api(libs.testcontainers.elasticsearch) + implementation(libs.jackson.databind) + implementation(libs.jackson.arrow) } dependencies { - testImplementation(libs.slf4j.simple) -} - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.elasticsearch.Stove") + testImplementation(libs.slf4j.simple) } diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/CouchbaseDsl.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/CouchbaseDsl.kt new file mode 100644 index 00000000..1bd338fc --- /dev/null +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/CouchbaseDsl.kt @@ -0,0 +1,5 @@ +package com.trendyol.stove.testing.e2e.elasticsearch + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class ElasticDsl 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 a930037a..7db81f24 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,6 +5,7 @@ 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 @@ -24,10 +25,7 @@ import kotlin.jvm.optionals.getOrElse class ElasticsearchSystem internal constructor( override val testSystem: TestSystem, private val context: ElasticsearchContext -) : PluggedSystem, - RunAware, - AfterRunAware, - ExposesConfiguration { +) : PluggedSystem, RunAware, AfterRunAware, ExposesConfiguration { @PublishedApi internal lateinit var esClient: ElasticsearchClient @@ -43,7 +41,7 @@ class ElasticsearchSystem internal constructor( context.container.host, context.container.firstMappedPort, context.options.container.password, - determineCertificate().getOrNull() + determineCertificate() ) } } @@ -74,12 +72,10 @@ class ElasticsearchSystem internal constructor( ): ElasticsearchSystem { require(index.isNotBlank()) { "Index cannot be blank" } require(query.isNotBlank()) { "Query cannot be blank" } - return esClient - .search( - SearchRequest.of { req -> req.index(index).query { q -> q.withJson(query.reader()) } }, - T::class.java - ).hits() - .hits() + return esClient.search( + SearchRequest.of { req -> req.index(index).query { q -> q.withJson(query.reader()) } }, + T::class.java + ).hits().hits() .mapNotNull { it.source() } .also(assertion) .let { this } @@ -89,12 +85,10 @@ class ElasticsearchSystem internal constructor( inline fun shouldQuery( query: Query, assertion: (List) -> Unit - ): ElasticsearchSystem = esClient - .search( - SearchRequest.of { q -> q.query(query) }, - T::class.java - ).hits() - .hits() + ): ElasticsearchSystem = esClient.search( + SearchRequest.of { q -> q.query(query) }, + T::class.java + ).hits().hits() .mapNotNull { it.source() } .also(assertion) .let { this } @@ -109,8 +103,7 @@ class ElasticsearchSystem internal constructor( require(key.isNotBlank()) { "Key cannot be blank" } return esClient .get({ req -> req.index(index).id(key).refresh(true) }, T::class.java) - .source() - .toOption() + .source().toOption() .map(assertion) .getOrElse { throw AssertionError("Resource with key ($key) is not found") } .let { this } @@ -150,14 +143,12 @@ class ElasticsearchSystem internal constructor( ): ElasticsearchSystem { require(index.isNotBlank()) { "Index cannot be blank" } require(id.isNotBlank()) { "Id cannot be blank" } - return esClient - .index { req -> - req - .index(index) - .id(id) - .document(instance) - .refresh(Refresh.WaitFor) - }.let { this } + return esClient.index { req -> + req.index(index) + .id(id) + .document(instance) + .refresh(Refresh.WaitFor) + }.let { this } } /** @@ -186,17 +177,15 @@ class ElasticsearchSystem internal constructor( private fun createEsClient(exposedConfiguration: ElasticSearchExposedConfiguration): ElasticsearchClient = context.options.clientConfigurer.restClientOverrideFn .getOrElse { { cfg -> restClient(cfg) } } - .let { RestClientTransport(it(exposedConfiguration), context.options.jsonpMapper) } + .let { RestClientTransport(it(exposedConfiguration), JacksonJsonpMapper(context.options.objectMapper)) } .let { ElasticsearchClient(it) } private fun restClient(cfg: ElasticSearchExposedConfiguration): RestClient = when (context.options.container.disableSecurity) { true -> - RestClient - .builder(HttpHost(exposedConfiguration.host, exposedConfiguration.port)) - .apply { - setHttpClientConfigCallback { http -> http.also(context.options.clientConfigurer.httpClientBuilder) } - }.build() + RestClient.builder(HttpHost(exposedConfiguration.host, exposedConfiguration.port)).apply { + setHttpClientConfigCallback { http -> http.also(context.options.clientConfigurer.httpClientBuilder) } + }.build() false -> secureRestClient(cfg, context.container.createSslContextFromCa()) } @@ -213,13 +202,12 @@ class ElasticsearchSystem internal constructor( val builder: RestClientBuilder = RestClient .builder(HttpHost(exposedConfiguration.host, exposedConfiguration.port, "https")) - return builder - .setHttpClientConfigCallback { clientBuilder: HttpAsyncClientBuilder -> - clientBuilder.setSSLContext(sslContext) - clientBuilder.setDefaultCredentialsProvider(credentialsProvider) - context.options.clientConfigurer.httpClientBuilder(clientBuilder) - clientBuilder - }.build() + return builder.setHttpClientConfigCallback { clientBuilder: HttpAsyncClientBuilder -> + clientBuilder.setSSLContext(sslContext) + clientBuilder.setDefaultCredentialsProvider(credentialsProvider) + context.options.clientConfigurer.httpClientBuilder(clientBuilder) + clientBuilder + }.build() } companion object { 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 97360390..e6213f83 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,6 +1,7 @@ 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 @@ -12,21 +13,26 @@ 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 = 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") +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) } - withReuse(this@withElasticsearch.options.keepDependenciesRunning) - options.container.containerFn(this) - }.let { getOrRegister(ElasticsearchSystem(this, ElasticsearchContext(it, options))) } - .let { 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 2efc518e..9dc6eb18 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,11 +2,10 @@ package com.trendyol.stove.testing.e2e.elasticsearch import arrow.core.* import co.elastic.clients.elasticsearch.ElasticsearchClient -import co.elastic.clients.json.JsonpMapper -import co.elastic.clients.json.jackson.JacksonJsonpMapper +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.containers.* import com.trendyol.stove.testing.e2e.database.migrations.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl import org.apache.http.client.config.RequestConfig @@ -16,18 +15,13 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer import org.testcontainers.utility.DockerImageName import kotlin.time.Duration.Companion.minutes -@DslMarker -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) -annotation class ElasticDsl - @StoveDsl data class ElasticsearchSystemOptions( val clientConfigurer: ElasticClientConfigurer = ElasticClientConfigurer(), val container: ElasticContainerOptions = ElasticContainerOptions(), - val jsonpMapper: JsonpMapper = JacksonJsonpMapper(StoveSerde.jackson.default), + val objectMapper: ObjectMapper = StoveObjectMapper.Default, override val configureExposedConfiguration: (ElasticSearchExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration { +) : SystemOptions, ConfiguresExposedConfiguration { internal val migrationCollection: MigrationCollection = MigrationCollection() /** @@ -47,7 +41,7 @@ data class ElasticSearchExposedConfiguration( val host: String, val port: Int, val password: String, - val certificate: ElasticsearchExposedCertificate? + val certificate: Option ) : ExposedConfiguration data class ElasticsearchContext( @@ -57,8 +51,7 @@ data class ElasticsearchContext( open class StoveElasticSearchContainer( override val imageNameAccess: DockerImageName -) : ElasticsearchContainer(imageNameAccess), - StoveContainer +) : ElasticsearchContainer(imageNameAccess), StoveContainer data class ElasticContainerOptions( override val registry: String = "docker.elastic.co/", @@ -79,8 +72,7 @@ data class ElasticContainerOptions( data class ElasticClientConfigurer( val httpClientBuilder: HttpAsyncClientBuilder.() -> Unit = { setDefaultRequestConfig( - RequestConfig - .custom() + RequestConfig.custom() .setSocketTimeout(5.minutes.inWholeMilliseconds.toInt()) .setConnectTimeout(5.minutes.inWholeMilliseconds.toInt()) .setConnectionRequestTimeout(5.minutes.inWholeMilliseconds.toInt()) 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 de5e4d92..4d91d3a9 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,18 +1,18 @@ 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.* +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.abstractions.StateWithProcess import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.* import io.kotest.matchers.ints.shouldBeGreaterThan -class ElasticsearchExposedCertificateTest : - FunSpec({ - test("ser/de") { - val state = - """ +class ElasticsearchExposedCertificateTest : FunSpec({ + test("ser/de") { + val state = + """ { "state": { "host": "localhost", @@ -24,15 +24,15 @@ class ElasticsearchExposedCertificateTest : }, "processId": 10496 } - """.trimIndent() - val j = StoveSerde.jackson.default - val stateWithProcess = j.readValue>(state) - val serialize = j.writeValueAsString(stateWithProcess) - val stateWithProcess2 = j.readValue>(serialize) + """.trimIndent() + val j = StoveObjectMapper.byConfiguring { this.registerArrowModule() } + val stateWithProcess = j.readValue>(state) + val serialize = j.writeValueAsString(stateWithProcess) + val stateWithProcess2 = j.readValue>(serialize) - val cert = stateWithProcess2.state.certificate!! - cert.bytes.size shouldBeGreaterThan 0 - cert.sslContext shouldNotBe null - cert.sslContext.protocol shouldBe "TLSv1.3" - } - }) + val cert = stateWithProcess2.state.certificate.get() + cert.bytes.size shouldBeGreaterThan 0 + cert.sslContext shouldNotBe null + cert.sslContext.protocol shouldBe "TLSv1.3" + } +}) diff --git a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchTestSystemTests.kt b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchTestSystemTests.kt index 4d84a8ef..83a5962d 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchTestSystemTests.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchTestSystemTests.kt @@ -26,8 +26,7 @@ class TestIndexMigrator : DatabaseMigration { override suspend fun execute(connection: ElasticsearchClient) { val createIndexRequest: CreateIndexRequest = - CreateIndexRequest - .Builder() + CreateIndexRequest.Builder() .index(TEST_INDEX) .build() connection.indices().create(createIndexRequest) @@ -41,8 +40,7 @@ class AnotherIndexMigrator : DatabaseMigration { override suspend fun execute(connection: ElasticsearchClient) { val createIndexRequest: CreateIndexRequest = - CreateIndexRequest - .Builder() + CreateIndexRequest.Builder() .index(ANOTHER_INDEX) .build() connection.indices().create(createIndexRequest) @@ -50,7 +48,7 @@ class AnotherIndexMigrator : DatabaseMigration { } } -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { elasticsearch { @@ -79,86 +77,80 @@ class NoOpApplication : ApplicationUnderTest { override suspend fun stop() = Unit } -class ElasticsearchTestSystemTests : - FunSpec({ - - @JsonIgnoreProperties - data class ExampleInstance( - val id: String, - val description: String - ) - test("should save and get") { - val exampleInstance = ExampleInstance("1", "1312") - TestSystem.validate { - elasticsearch { - save(exampleInstance.id, exampleInstance, TEST_INDEX) - shouldGet(key = exampleInstance.id, index = TEST_INDEX) { - it.description shouldBe exampleInstance.description - } +class ElasticsearchTestSystemTests : FunSpec({ + + @JsonIgnoreProperties + data class ExampleInstance( + val id: String, + val description: String + ) + test("should save and get") { + val exampleInstance = ExampleInstance("1", "1312") + TestSystem.validate { + elasticsearch { + save(exampleInstance.id, exampleInstance, TEST_INDEX) + shouldGet(key = exampleInstance.id, index = TEST_INDEX) { + it.description shouldBe exampleInstance.description } } } + } - test("should save and get from another index") { - val exampleInstance = ExampleInstance("1", "1312") - TestSystem.validate { - elasticsearch { - save(exampleInstance.id, exampleInstance, ANOTHER_INDEX) - shouldGet(ANOTHER_INDEX, exampleInstance.id) { - it.description shouldBe exampleInstance.description - } + test("should save and get from another index") { + val exampleInstance = ExampleInstance("1", "1312") + TestSystem.validate { + elasticsearch { + save(exampleInstance.id, exampleInstance, ANOTHER_INDEX) + shouldGet(ANOTHER_INDEX, exampleInstance.id) { + it.description shouldBe exampleInstance.description } } } + } - test("should save 2 documents with the same description, then delete first one and query by description") { - val desc = "some description" - val exampleInstance1 = ExampleInstance("1", desc) - val exampleInstance2 = ExampleInstance("2", desc) - val queryByDesc = QueryBuilders - .term() - .field("description.keyword") - .value(desc) - .queryName("query_name") - .build() - val queryAsString = queryByDesc.asJsonString() - TestSystem.validate { - elasticsearch { - save(exampleInstance1.id, exampleInstance1, TEST_INDEX) - save(exampleInstance2.id, exampleInstance2, TEST_INDEX) - shouldQuery(queryByDesc._toQuery()) { - it.size shouldBe 2 - } - shouldDelete(exampleInstance1.id, TEST_INDEX) - shouldGet(key = exampleInstance2.id, index = TEST_INDEX) {} - shouldQuery(queryAsString, TEST_INDEX) { - it.size shouldBe 1 - } + test("should save 2 documents with the same description, then delete first one and query by description") { + val desc = "some description" + val exampleInstance1 = ExampleInstance("1", desc) + val exampleInstance2 = ExampleInstance("2", desc) + val queryByDesc = QueryBuilders.term().field("description.keyword").value(desc).queryName("query_name").build() + val queryAsString = queryByDesc.asJsonString() + TestSystem.validate { + elasticsearch { + save(exampleInstance1.id, exampleInstance1, TEST_INDEX) + save(exampleInstance2.id, exampleInstance2, TEST_INDEX) + shouldQuery(queryByDesc._toQuery()) { + it.size shouldBe 2 + } + shouldDelete(exampleInstance1.id, TEST_INDEX) + shouldGet(key = exampleInstance2.id, index = TEST_INDEX) {} + shouldQuery(queryAsString, TEST_INDEX) { + it.size shouldBe 1 } } } + } - test("should throw assertion error when document does exist") { - val existDocId = UUID.randomUUID().toString() - val exampleInstance = ExampleInstance(existDocId, "1312") - TestSystem.validate { - elasticsearch { - save(exampleInstance.id, exampleInstance, TEST_INDEX) - shouldGet(key = exampleInstance.id, index = TEST_INDEX) { - it.description shouldBe exampleInstance.description - } - - assertThrows { shouldNotExist(existDocId, index = TEST_INDEX) } + test("should throw assertion error when document does exist") { + val existDocId = UUID.randomUUID().toString() + val exampleInstance = ExampleInstance(existDocId, "1312") + TestSystem.validate { + elasticsearch { + save(exampleInstance.id, exampleInstance, TEST_INDEX) + shouldGet(key = exampleInstance.id, index = TEST_INDEX) { + it.description shouldBe exampleInstance.description } + + assertThrows { shouldNotExist(existDocId, index = TEST_INDEX) } } } + } - test("should does not throw exception when given does not exist id") { - val notExistDocId = UUID.randomUUID().toString() - TestSystem.validate { - elasticsearch { - shouldNotExist(notExistDocId, index = TEST_INDEX) - } + test("should does not throw exception when given does not exist id") { + val notExistDocId = UUID.randomUUID().toString() + TestSystem.validate { + elasticsearch { + shouldNotExist(notExistDocId, index = TEST_INDEX) } } - }) + } +}) diff --git a/lib/stove-testing-e2e-http/api/stove-testing-e2e-http.api b/lib/stove-testing-e2e-http/api/stove-testing-e2e-http.api deleted file mode 100644 index 47f48f34..00000000 --- a/lib/stove-testing-e2e-http/api/stove-testing-e2e-http.api +++ /dev/null @@ -1,117 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/http/HttpClientSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public static final field Companion Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions$Companion; - public synthetic fun (Ljava/lang/String;Lio/ktor/serialization/ContentConverter;JLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lio/ktor/serialization/ContentConverter;JLkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/ktor/serialization/ContentConverter; - public final fun component3-UwyO8pc ()J - public final fun component4 ()Lkotlin/jvm/functions/Function0; - public final fun copy-exY8QGI (Ljava/lang/String;Lio/ktor/serialization/ContentConverter;JLkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions; - public static synthetic fun copy-exY8QGI$default (Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions;Ljava/lang/String;Lio/ktor/serialization/ContentConverter;JLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getBaseUrl ()Ljava/lang/String; - public final fun getContentConverter ()Lio/ktor/serialization/ContentConverter; - public final fun getCreateClient ()Lkotlin/jvm/functions/Function0; - public final fun getTimeout-UwyO8pc ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/HttpClientSystemOptions$Companion { -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/http/HttpDsl : java/lang/annotation/Annotation { -} - -public final class com/trendyol/stove/testing/e2e/http/HttpSystem : com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem { - public static final field Companion Lcom/trendyol/stove/testing/e2e/http/HttpSystem$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions;)V - public fun close ()V - public final fun deleteAndExpectBodilessResponse (Ljava/lang/String;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun deleteAndExpectBodilessResponse$default (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Ljava/lang/String;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun get (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Larrow/core/Option;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getKtorHttpClient ()Lio/ktor/client/HttpClient; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/http/HttpClientSystemOptions; - public final fun getResponse (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Larrow/core/Option;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getResponse$default (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Larrow/core/Option;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun patchAndExpectBodilessResponse (Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun patchAndExpectBodilessResponse$default (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun postAndExpectBodilessResponse (Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun postAndExpectBodilessResponse$default (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun putAndExpectBodilessResponse (Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun putAndExpectBodilessResponse$default (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Ljava/lang/String;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun relative (Ljava/lang/String;)Lio/ktor/http/Url; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun toFormData (Ljava/util/List;)Ljava/util/List; -} - -public final class com/trendyol/stove/testing/e2e/http/HttpSystem$Companion { - public final fun client (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;)Lio/ktor/client/HttpClient; - public final fun client (Lcom/trendyol/stove/testing/e2e/http/HttpSystem;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/http/HttpSystem$Companion$HeaderConstants { - public static final field AUTHORIZATION Ljava/lang/String; - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/http/HttpSystem$Companion$HeaderConstants; - public final fun bearer (Ljava/lang/String;)Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/HttpSystemKt { - public static final fun http-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun httpClient-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public abstract class com/trendyol/stove/testing/e2e/http/StoveMultiPartContent { -} - -public final class com/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Binary : com/trendyol/stove/testing/e2e/http/StoveMultiPartContent { - public fun (Ljava/lang/String;[B)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()[B - public final fun copy (Ljava/lang/String;[B)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Binary; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Binary;Ljava/lang/String;[BILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Binary; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()[B - public final fun getParam ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/StoveMultiPartContent$File : com/trendyol/stove/testing/e2e/http/StoveMultiPartContent { - public fun (Ljava/lang/String;Ljava/lang/String;[BLjava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()[B - public final fun component4 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;[BLjava/lang/String;)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$File; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$File;Ljava/lang/String;Ljava/lang/String;[BLjava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$File; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()[B - public final fun getContentType ()Ljava/lang/String; - public final fun getFileName ()Ljava/lang/String; - public final fun getParam ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Text : com/trendyol/stove/testing/e2e/http/StoveMultiPartContent { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Text; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Text;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/StoveMultiPartContent$Text; - public fun equals (Ljava/lang/Object;)Z - public final fun getParam ()Ljava/lang/String; - public final fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/StreamingKt { - public static final fun readJsonContentStream (Lio/ktor/client/statement/HttpStatement;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; - public static final fun readJsonTextStream (Lio/ktor/client/statement/HttpStatement;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; - public static final fun serializeToStreamJson (Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Ljava/util/List;)[B -} - diff --git a/lib/stove-testing-e2e-http/build.gradle.kts b/lib/stove-testing-e2e-http/build.gradle.kts index 4a8207a5..6ffeca63 100644 --- a/lib/stove-testing-e2e-http/build.gradle.kts +++ b/lib/stove-testing-e2e-http/build.gradle.kts @@ -16,7 +16,3 @@ dependencies { testImplementation(libs.jackson.jsr310) testImplementation(testFixtures(projects.lib.stoveTestingE2e)) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.http.Stove") -} 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 c5cbe91d..6617d7a4 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,7 +3,8 @@ package com.trendyol.stove.testing.e2e.http import arrow.core.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import com.fasterxml.jackson.databind.ObjectMapper +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -16,14 +17,10 @@ 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 io.ktor.util.reflect.* -import kotlinx.coroutines.flow.Flow import org.slf4j.LoggerFactory import java.net.http.HttpClient -import java.nio.charset.Charset import kotlin.time.* import kotlin.time.Duration.Companion.seconds @@ -32,14 +29,14 @@ private val httpSystemLogger = LoggerFactory.getLogger(HttpSystem::class.java) @HttpDsl data class HttpClientSystemOptions( val baseUrl: String, - val contentConverter: ContentConverter = JacksonConverter(StoveSerde.jackson.default), + val objectMapper: ObjectMapper = StoveObjectMapper.Default, val timeout: Duration = 30.seconds, - val createClient: () -> io.ktor.client.HttpClient = { jsonHttpClient(timeout, contentConverter) } + val createClient: () -> io.ktor.client.HttpClient = { jsonHttpClient(timeout, objectMapper) } ) : SystemOptions { companion object { internal fun jsonHttpClient( timeout: Duration, - converter: ContentConverter + objectMapper: ObjectMapper ): io.ktor.client.HttpClient = HttpClient(OkHttp) { engine { config { @@ -60,9 +57,9 @@ data class HttpClientSystemOptions( } install(ContentNegotiation) { - register(ContentType.Application.Json, converter) - register(ContentType.Application.ProblemJson, converter) - register(ContentType.parse("application/x-ndjson"), converter) + register(ContentType.Application.Json, JacksonConverter(objectMapper)) + register(ContentType.Application.ProblemJson, JacksonConverter(objectMapper)) + register(ContentType.parse("application/x-ndjson"), JacksonConverter(objectMapper)) } defaultRequest { @@ -107,10 +104,9 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: suspend (StoveHttpResponse) -> Unit - ): HttpSystem = get(uri, headers, queryParams, token) - .also { - expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) - }.let { this } + ): HttpSystem = get(uri, headers, queryParams, token).also { + expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) + }.let { this } @HttpDsl suspend inline fun getResponse( @@ -119,10 +115,9 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (StoveHttpResponse.WithBody) -> Unit - ): HttpSystem = get(uri, headers, queryParams, token) - .also { - expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) - }.let { this } + ): HttpSystem = get(uri, headers, queryParams, token).also { + expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) + }.let { this } @HttpDsl suspend inline fun get( @@ -131,11 +126,10 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (TExpected) -> Unit - ): HttpSystem = get(uri, headers, queryParams, token) - .also { - check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } - expect(it.body()) - }.let { this } + ): HttpSystem = get(uri, headers, queryParams, token).also { + check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } + expect(it.body()) + }.let { this } @HttpDsl suspend inline fun getMany( @@ -144,28 +138,10 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (List) -> Unit - ): HttpSystem = get(uri, headers, queryParams, token) - .also { - check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } - expect(it.body()) - }.let { this } - - suspend inline fun readJsonStream( - uri: String, - queryParams: Map = mapOf(), - headers: Map = mapOf(), - token: Option = None, - expect: (Flow) -> Unit - ): HttpSystem = ktorHttpClient - .prepareGet(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - header(HttpHeaders.Accept, "application/x-ndjson") - queryParams.forEach { (key, value) -> parameter(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - }.readJsonContentStream { - options.contentConverter.deserialize(Charset.defaultCharset(), typeInfo(), it) as TExpected - }.also { expect(it) } - .let { return this } + ): HttpSystem = get(uri, headers, queryParams, token).also { + check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } + expect(it.body()) + }.let { this } @HttpDsl suspend fun postAndExpectBodilessResponse( @@ -174,13 +150,11 @@ class HttpSystem( token: Option = None, headers: Map = mapOf(), expect: suspend (StoveHttpResponse) -> Unit - ): HttpSystem = ktorHttpClient - .post(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } - .let { this } + ): HttpSystem = ktorHttpClient.post(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) }.let { this } @HttpDsl suspend inline fun postAndExpectJson( @@ -189,15 +163,14 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: TExpected) -> Unit - ): HttpSystem = ktorHttpClient - .post(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { - check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } - expect(it.body()) - }.let { this } + ): HttpSystem = ktorHttpClient.post(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { + check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } + expect(it.body()) + }.let { this } /** * Posts the given [body] to the given [uri] and expects the response to have a body. @@ -209,13 +182,11 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: StoveHttpResponse.WithBody) -> Unit - ): HttpSystem = ktorHttpClient - .post(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } - .let { this } + ): HttpSystem = ktorHttpClient.post(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) }.let { this } @HttpDsl suspend fun putAndExpectBodilessResponse( @@ -224,12 +195,11 @@ class HttpSystem( token: Option = None, headers: Map = mapOf(), expect: suspend (StoveHttpResponse) -> Unit - ): HttpSystem = ktorHttpClient - .put(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } + ): HttpSystem = ktorHttpClient.put(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } .let { this } @HttpDsl @@ -239,15 +209,14 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: TExpected) -> Unit - ): HttpSystem = ktorHttpClient - .put(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { - check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } - expect(it.body()) - }.let { this } + ): HttpSystem = ktorHttpClient.put(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { + check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } + expect(it.body()) + }.let { this } @HttpDsl suspend inline fun putAndExpectBody( @@ -256,12 +225,11 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: StoveHttpResponse.WithBody) -> Unit - ): HttpSystem = ktorHttpClient - .put(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } + ): HttpSystem = ktorHttpClient.put(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } .let { this } @HttpDsl @@ -271,12 +239,11 @@ class HttpSystem( token: Option = None, headers: Map = mapOf(), expect: suspend (StoveHttpResponse) -> Unit - ): HttpSystem = ktorHttpClient - .patch(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } + ): HttpSystem = ktorHttpClient.patch(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } .let { this } @HttpDsl @@ -286,15 +253,14 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: TExpected) -> Unit - ): HttpSystem = ktorHttpClient - .patch(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { - check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } - expect(it.body()) - }.let { this } + ): HttpSystem = ktorHttpClient.patch(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { + check(it.status.isSuccess()) { "Expected a successful response, but got ${it.status}" } + expect(it.body()) + }.let { this } @HttpDsl suspend inline fun patchAndExpectBody( @@ -303,12 +269,11 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (actual: StoveHttpResponse.WithBody) -> Unit - ): HttpSystem = ktorHttpClient - .patch(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - body.map { setBody(it) } - }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } + ): HttpSystem = ktorHttpClient.patch(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + body.map { setBody(it) } + }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } .let { this } @HttpDsl @@ -317,11 +282,10 @@ class HttpSystem( token: Option = None, headers: Map = mapOf(), expect: suspend (StoveHttpResponse) -> Unit - ): HttpSystem = ktorHttpClient - .delete(relative(uri)) { - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } + ): HttpSystem = ktorHttpClient.delete(relative(uri)) { + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + }.also { expect(StoveHttpResponse.Bodiless(it.status.value, it.headers.toMap())) } .let { this } @HttpDsl @@ -331,14 +295,12 @@ class HttpSystem( headers: Map = mapOf(), token: Option = None, expect: (StoveHttpResponse.WithBody) -> Unit - ): HttpSystem = ktorHttpClient - .submitForm { - url(relative(uri)) - headers.forEach { (key, value) -> header(key, value) } - token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } - setBody(MultiPartFormDataContent(toFormData(body))) - }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) } - .let { this } + ): HttpSystem = ktorHttpClient.submitForm { + url(relative(uri)) + headers.forEach { (key, value) -> header(key, value) } + token.map { header(HeaderConstants.AUTHORIZATION, HeaderConstants.bearer(it)) } + setBody(MultiPartFormDataContent(toFormData(body))) + }.also { expect(StoveHttpResponse.WithBody(it.status.value, it.headers.toMap()) { it.body() }) }.let { this } @HttpDsl override fun then(): TestSystem = testSystem @@ -357,8 +319,7 @@ class HttpSystem( @PublishedApi internal fun relative(uri: String): Url = URLBuilder(options.baseUrl) - .apply { appendEncodedPathSegments(uri) } - .build() + .apply { appendEncodedPathSegments(uri) }.build() @PublishedApi internal fun toFormData( diff --git a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/StoveMultiPartContent.kt b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/StoveMultiPartContent.kt index a1500e3e..b51d0bfc 100644 --- a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/StoveMultiPartContent.kt +++ b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/StoveMultiPartContent.kt @@ -9,10 +9,7 @@ sealed class StoveMultiPartContent { /** * Represents a text content for a multi-part request. */ - data class Text( - val param: String, - val value: String - ) : StoveMultiPartContent() + data class Text(val param: String, val value: String) : StoveMultiPartContent() /** * Represents a file content for a multi-part request. @@ -27,8 +24,5 @@ sealed class StoveMultiPartContent { /** * Represents a binary content for a multi-part request. */ - data class Binary( - val param: String, - val content: ByteArray - ) : StoveMultiPartContent() + data class Binary(val param: String, val content: ByteArray) : StoveMultiPartContent() } diff --git a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/streaming.kt b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/streaming.kt index d8ce593f..7219d44e 100644 --- a/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/streaming.kt +++ b/lib/stove-testing-e2e-http/src/main/kotlin/com/trendyol/stove/testing/e2e/http/streaming.kt @@ -1,7 +1,5 @@ package com.trendyol.stove.testing.e2e.http -import arrow.core.toOption -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.util.* @@ -10,33 +8,13 @@ import kotlinx.coroutines.flow.* @OptIn(InternalAPI::class) @Suppress("unused") -fun HttpStatement.readJsonTextStream(transform: suspend (line: String) -> T): Flow = flow { +suspend fun HttpStatement.readJsonStream(transform: (line: String) -> T): Flow = flow { execute { check(it.status.isSuccess()) { "Request failed with status: ${it.status}" } while (!it.content.isClosedForRead) { - it.content.readUTF8LineNonEmpty { line -> emit(transform(line)) } + it.content.readUTF8Line()?.let { line -> + emit(transform(line)) + } } } } - -@OptIn(InternalAPI::class) -@Suppress("unused") -fun HttpStatement.readJsonContentStream(transform: suspend (line: ByteReadChannel) -> T): Flow = flow { - execute { - check(it.status.isSuccess()) { "Request failed with status: ${it.status}" } - while (!it.content.isClosedForRead) { - it.content.readUTF8LineNonEmpty { line -> emit(transform(ByteReadChannel(line.toByteArray()))) } - } - } -} - -private suspend fun ByteReadChannel.readUTF8LineNonEmpty(onRead: suspend (String) -> Unit) { - readUTF8Line().toOption().filter { it.isNotBlank() }.map { onRead(it) } -} - -/** - * Serializes the items to a stream of JSON strings. - */ -fun StoveSerde.serializeToStreamJson(items: List): ByteArray = items - .joinToString("\n") { String(serialize(it)) } - .toByteArray() 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 42e4913d..1b48d566 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,6 +6,7 @@ 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.* @@ -16,7 +17,6 @@ import io.kotest.matchers.* import io.kotest.matchers.string.shouldContain import io.ktor.client.request.* import io.ktor.http.* -import kotlinx.coroutines.flow.toList import java.time.Instant import java.util.* @@ -30,13 +30,16 @@ class NoApplication : ApplicationUnderTest { } } -class Stove : AbstractProjectConfig() { +class TestConfig : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { httpClient { HttpClientSystemOptions( - baseUrl = "http://localhost:8086" + baseUrl = "http://localhost:8086", + objectMapper = StoveObjectMapper.byConfiguring { + findAndRegisterModules() + } ) } @@ -50,400 +53,372 @@ class Stove : AbstractProjectConfig() { override suspend fun afterProject(): Unit = TestSystem.stop() } -class HttpSystemTests : - FunSpec({ - test("DELETE and expect bodiless response") { - TestSystem.validate { - wiremock { - mockDelete("/delete-success", statusCode = 200) - mockDelete("/delete-fail", statusCode = 400) - } +class HttpSystemTests : FunSpec({ + test("DELETE and expect bodiless response") { + TestSystem.validate { + wiremock { + mockDelete("/delete-success", statusCode = 200) + mockDelete("/delete-fail", statusCode = 400) + } - http { - deleteAndExpectBodilessResponse("/delete-success", None) { actual -> - actual.status shouldBe 200 - } - deleteAndExpectBodilessResponse("/delete-fail", None) { actual -> - actual.status shouldBe 400 - } + http { + deleteAndExpectBodilessResponse("/delete-success", None) { actual -> + actual.status shouldBe 200 + } + deleteAndExpectBodilessResponse("/delete-fail", None) { actual -> + actual.status shouldBe 400 } } } + } - test("PUT and expect bodiless/JSON response") { - val expectedPutDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPut("/put-with-response-body", 200, None, responseBody = TestDto(expectedPutDtoName).some()) - mockPut("/put-without-response-body", 200, None, responseBody = None) - } + test("PUT and expect bodiless/JSON response") { + val expectedPutDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPut("/put-with-response-body", 200, None, responseBody = TestDto(expectedPutDtoName).some()) + mockPut("/put-without-response-body", 200, None, responseBody = None) + } - http { + http { - putAndExpectBodilessResponse("/put-without-response-body", None, None) { actual -> - actual.status shouldBe 200 - } - putAndExpectJson("/put-with-response-body") { actual -> - actual.name shouldBe expectedPutDtoName - } + putAndExpectBodilessResponse("/put-without-response-body", None, None) { actual -> + actual.status shouldBe 200 + } + putAndExpectJson("/put-with-response-body") { actual -> + actual.name shouldBe expectedPutDtoName } } } + } - test("POST and expect bodiless/JSON response") { - val expectedPOSTDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPost("/post-with-response-body", 200, None, responseBody = TestDto(expectedPOSTDtoName).some()) - mockPost("/post-without-response-body", 200, None, responseBody = None) - } + test("POST and expect bodiless/JSON response") { + val expectedPOSTDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPost("/post-with-response-body", 200, None, responseBody = TestDto(expectedPOSTDtoName).some()) + mockPost("/post-without-response-body", 200, None, responseBody = None) + } - http { - postAndExpectBodilessResponse("/post-without-response-body", None, None) { actual -> - actual.status shouldBe 200 - } - postAndExpectJson("/post-with-response-body") { actual -> - actual.name shouldBe expectedPOSTDtoName - } + http { + postAndExpectBodilessResponse("/post-without-response-body", None, None) { actual -> + actual.status shouldBe 200 + } + postAndExpectJson("/post-with-response-body") { actual -> + actual.name shouldBe expectedPOSTDtoName } } } + } - test("PATCH and expect bodiless/JSON response") { - val expectedPatchDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPatch("/patch-with-response-body", 200, None, responseBody = TestDto(expectedPatchDtoName).some()) - mockPatch("/patch-without-response-body", 200, None, responseBody = None) - } + test("PATCH and expect bodiless/JSON response") { + val expectedPatchDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPatch("/patch-with-response-body", 200, None, responseBody = TestDto(expectedPatchDtoName).some()) + mockPatch("/patch-without-response-body", 200, None, responseBody = None) + } - http { + http { - patchAndExpectBodilessResponse("/patch-without-response-body", None, None) { actual -> - actual.status shouldBe 200 - } - patchAndExpectJson("/patch-with-response-body") { actual -> - actual.name shouldBe expectedPatchDtoName - } + patchAndExpectBodilessResponse("/patch-without-response-body", None, None) { actual -> + actual.status shouldBe 200 + } + patchAndExpectJson("/patch-with-response-body") { actual -> + actual.name shouldBe expectedPatchDtoName } } } + } - test("GET and expect JSON response") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) - mockGet("/get-many", 200, responseBody = listOf(TestDto(expectedGetDtoName)).some()) - } + test("GET and expect JSON response") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) + mockGet("/get-many", 200, responseBody = listOf(TestDto(expectedGetDtoName)).some()) + } - http { - get("/get") { actual -> - actual.name shouldBe expectedGetDtoName - } + http { + get("/get") { actual -> + actual.name shouldBe expectedGetDtoName + } - getMany("/get-many") { actual -> - actual[0] shouldBe TestDto(expectedGetDtoName) - } + getMany("/get-many") { actual -> + actual[0] shouldBe TestDto(expectedGetDtoName) + } - get>("/get-many") { actual -> - actual[0] shouldBe TestDto(expectedGetDtoName) - } + get>("/get-many") { actual -> + actual[0] shouldBe TestDto(expectedGetDtoName) } } } + } - test("getResponse and expect body") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) - } + test("getResponse and expect body") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) + } - http { - getResponse("/get") { actual -> - actual.body().name shouldBe expectedGetDtoName - } + http { + getResponse("/get") { actual -> + actual.body().name shouldBe expectedGetDtoName } } } + } - test("getResponse and expect bodiless") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) - } + test("getResponse and expect bodiless") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get", 200, responseBody = TestDto(expectedGetDtoName).some()) + } - http { - getResponse("/get") { actual -> - actual.status shouldBe 200 - actual::class shouldBe StoveHttpResponse.Bodiless::class - } + http { + getResponse("/get") { actual -> + actual.status shouldBe 200 + actual::class shouldBe StoveHttpResponse.Bodiless::class } } } + } - test("put and expect body") { - val expectedPutDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPut("/put-with-response-body", 200, None, responseBody = TestDto(expectedPutDtoName).some()) - } + test("put and expect body") { + val expectedPutDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPut("/put-with-response-body", 200, None, responseBody = TestDto(expectedPutDtoName).some()) + } - http { - putAndExpectBody("/put-with-response-body") { actual -> - actual.body().name shouldBe expectedPutDtoName - } + http { + putAndExpectBody("/put-with-response-body") { actual -> + actual.body().name shouldBe expectedPutDtoName } } } + } - test("post and expect body") { - val expectedPostDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPost("/post-with-response-body", 200, None, responseBody = TestDto(expectedPostDtoName).some()) - } + test("post and expect body") { + val expectedPostDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPost("/post-with-response-body", 200, None, responseBody = TestDto(expectedPostDtoName).some()) + } - http { - postAndExpectBody("/post-with-response-body") { actual -> - actual.body().name shouldBe expectedPostDtoName - } + http { + postAndExpectBody("/post-with-response-body") { actual -> + actual.body().name shouldBe expectedPostDtoName } } } + } - test("patch and expect body") { - val expectedPatchDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPatch("/patch-with-response-body", 200, None, responseBody = TestDto(expectedPatchDtoName).some()) - } + test("patch and expect body") { + val expectedPatchDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPatch("/patch-with-response-body", 200, None, responseBody = TestDto(expectedPatchDtoName).some()) + } - http { - patchAndExpectBody("/patch-with-response-body") { actual -> - actual.body().name shouldBe expectedPatchDtoName - } + http { + patchAndExpectBody("/patch-with-response-body") { actual -> + actual.body().name shouldBe expectedPatchDtoName } } } + } - test("get with query params should work") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get?param=1", 200, responseBody = TestDto(expectedGetDtoName).some()) - } + test("get with query params should work") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get?param=1", 200, responseBody = TestDto(expectedGetDtoName).some()) + } - http { - get("/get", queryParams = mapOf("param" to "1")) { actual -> - actual.name shouldBe expectedGetDtoName - } + http { + get("/get", queryParams = mapOf("param" to "1")) { actual -> + actual.name shouldBe expectedGetDtoName } } } + } - test("multipart post should work") { - val expectedPostDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPostConfigure("/post-with-multipart") { req, _ -> - req.withMultipartRequestBody( - aMultipart() - .matchingType(MultipartValuePattern.MatchingType.ANY) - .withHeader("Content-Disposition", equalTo("form-data; name=name")) - .withBody(equalTo(expectedPostDtoName)) - ) - req.withMultipartRequestBody( - aMultipart() - .matchingType(MultipartValuePattern.MatchingType.ANY) - .withHeader("Content-Disposition", equalTo("form-data; name=file; filename=file.png")) - .withBody(equalTo("file")) - ) - req.willReturn(aResponse().withStatus(200).withBody("hoi!")) - } + test("multipart post should work") { + val expectedPostDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPostConfigure("/post-with-multipart") { req, _ -> + req.withMultipartRequestBody( + aMultipart() + .matchingType(MultipartValuePattern.MatchingType.ANY) + .withHeader("Content-Disposition", equalTo("form-data; name=name")) + .withBody(equalTo(expectedPostDtoName)) + ) + req.withMultipartRequestBody( + aMultipart() + .matchingType(MultipartValuePattern.MatchingType.ANY) + .withHeader("Content-Disposition", equalTo("form-data; name=file; filename=file.png")) + .withBody(equalTo("file")) + ) + req.willReturn(aResponse().withStatus(200).withBody("hoi!")) } + } - http { - postMultipartAndExpectResponse( - "/post-with-multipart", - body = listOf( - StoveMultiPartContent.Text("name", expectedPostDtoName), - StoveMultiPartContent.File( - param = "file", - fileName = "file.png", - content = "file".toByteArray(), - contentType = "application/octet-stream" - ) + http { + postMultipartAndExpectResponse( + "/post-with-multipart", + body = listOf( + StoveMultiPartContent.Text("name", expectedPostDtoName), + StoveMultiPartContent.File( + param = "file", + fileName = "file.png", + content = "file".toByteArray(), + contentType = "application/octet-stream" ) - ) { actual -> - actual.body() shouldBe "hoi!" - actual.status shouldBe 200 - } + ) + ) { actual -> + actual.body() shouldBe "hoi!" + actual.status shouldBe 200 } } } + } - test("java time instant should work") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get", 200, responseBody = TestDtoWithInstant(expectedGetDtoName, Instant.now()).some()) - } + test("java time instant should work") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get", 200, responseBody = TestDtoWithInstant(expectedGetDtoName, Instant.now()).some()) + } - http { - get("/get") { actual -> - actual.name shouldBe expectedGetDtoName - } + http { + get("/get") { actual -> + actual.name shouldBe expectedGetDtoName } } } + } - test("keep path segments as is") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockGet("/get?path=1", 200, responseBody = TestDto(expectedGetDtoName).some()) - } + test("keep path segments as is") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockGet("/get?path=1", 200, responseBody = TestDto(expectedGetDtoName).some()) + } - http { - get("/get?path=1") { actual -> - actual.name shouldBe expectedGetDtoName - } + http { + get("/get?path=1") { actual -> + actual.name shouldBe expectedGetDtoName + } - client() shouldNotBe null + client() shouldNotBe null - client { baseUrl -> - val resp = get( - baseUrl - .apply { - path("/get") - parameters.append("path", "1") - }.build() - ) - resp.status shouldBe HttpStatusCode.OK - } + client { baseUrl -> + val resp = get( + baseUrl.apply { + path("/get") + parameters.append("path", "1") + }.build() + ) + resp.status shouldBe HttpStatusCode.OK } } } + } - test("behavioural tests") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - behaviourFor("/get-behaviour", WireMock::get) { - initially { - aResponse() - .withStatus(503) - .withBody("Service unavailable") - } - then { - aResponse() - .withHeader("Content-Type", "application/json") - .withStatus(200) - .withBody(it.serialize(TestDto(expectedGetDtoName))) - } + test("behavioural tests") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + behaviourFor("/get-behaviour", WireMock::get) { + initially { + aResponse() + .withStatus(503) + .withBody("Service unavailable") + } + then { + aResponse() + .withHeader("Content-Type", "application/json") + .withStatus(200) + .withBody(it.writeValueAsString(TestDto(expectedGetDtoName))) } } + } - http { - this.getResponse("/get-behaviour") { actual -> - actual.status shouldBe 503 - } + http { + this.getResponse("/get-behaviour") { actual -> + actual.status shouldBe 503 + } - get("/get-behaviour") { actual -> - actual.name shouldBe expectedGetDtoName - } + get("/get-behaviour") { actual -> + actual.name shouldBe expectedGetDtoName } } } + } - test("if there is no initial step, can not place `then`") { - TestSystem.validate { - wiremock { - behaviourFor("/get-behaviour", WireMock::get) { - shouldThrow { - then { - aResponse() - .withHeader("Content-Type", "application/json") - .withStatus(200) - .withBody(it.serialize(TestDto(UUID.randomUUID().toString()))) - } + test("if there is no initial step, can not place `then`") { + TestSystem.validate { + wiremock { + behaviourFor("/get-behaviour", WireMock::get) { + shouldThrow { + then { + aResponse() + .withHeader("Content-Type", "application/json") + .withStatus(200) + .withBody(it.writeValueAsString(TestDto(UUID.randomUUID().toString()))) } } } } } + } - test("should only call initially once") { - TestSystem.validate { - wiremock { - behaviourFor("/get-behaviour", WireMock::get) { + test("should only call initially once") { + TestSystem.validate { + wiremock { + behaviourFor("/get-behaviour", WireMock::get) { + initially { + aResponse() + .withStatus(503) + .withBody("Service unavailable") + } + shouldThrow { initially { aResponse() .withStatus(503) .withBody("Service unavailable") } - shouldThrow { - initially { - aResponse() - .withStatus(503) - .withBody("Service unavailable") - } - } } } } } + } +}) + +class HttpConsoleTesting : ConsoleSpec({ capturedOutput -> + test("should return error when request bodies do not match") { + val expectedGetDtoName = UUID.randomUUID().toString() + TestSystem.validate { + wiremock { + mockPost("/post-with-response-body", 200, requestBody = TestDto("lol").some(), responseBody = TestDto(expectedGetDtoName).some()) + } - test("serialize to application/x-ndjson") { - val expectedGetDtoName = UUID.randomUUID().toString() - val items = (1..10).map { TestDto(expectedGetDtoName) } - - TestSystem.validate { - wiremock { - mockGetConfigure("/get-ndjson") { builder, serde -> - builder.willReturn( - aResponse() - .withHeader("Content-Type", "application/x-ndjson") - .withBody(serde.serializeToStreamJson(items)) - ) - } - } - + shouldThrow { http { - readJsonStream("/get-ndjson") { actual -> - val collected = actual.toList() - collected.size shouldBe 10 - collected.forEach { it.name shouldBe expectedGetDtoName } + postAndExpectJson("/post-with-response-body2", body = TestDto("no-match").some()) { actual -> + actual.name shouldBe expectedGetDtoName } } } - } - }) -class HttpConsoleTesting : - ConsoleSpec({ capturedOutput -> - test("should return error when request bodies do not match") { - val expectedGetDtoName = UUID.randomUUID().toString() - TestSystem.validate { - wiremock { - mockPost("/post-with-response-body", 200, requestBody = TestDto("lol").some(), responseBody = TestDto(expectedGetDtoName).some()) - } - - shouldThrow { - http { - postAndExpectJson("/post-with-response-body2", body = TestDto("no-match").some()) { actual -> - actual.name shouldBe expectedGetDtoName - } - } - } - - capturedOutput.out shouldContain "[equalToJson] | <<<<< Body does not match\n" - } + capturedOutput.out shouldContain "[equalToJson] | <<<<< Body does not match\n" } - }) + } +}) data class TestDto( val name: String diff --git a/lib/stove-testing-e2e-kafka/api/stove-testing-e2e-kafka.api b/lib/stove-testing-e2e-kafka/api/stove-testing-e2e-kafka.api deleted file mode 100644 index b7c032dd..00000000 --- a/lib/stove-testing-e2e-kafka/api/stove-testing-e2e-kafka.api +++ /dev/null @@ -1,564 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getException ()Ljava/lang/String; - public final fun getId ()Ljava/lang/String; - public final fun getOffset ()J - public final fun getPartition ()I - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/Caching { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/standalone/kafka/Caching; - public final fun of ()Lcom/github/benmanes/caffeine/cache/Cache; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getMetadata ()Ljava/lang/String; - public final fun getOffset ()J - public final fun getPartition ()I - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/CommittedRecord { - public fun (Ljava/lang/String;Ljava/lang/String;JI)V - public final fun getMetadata ()Ljava/lang/String; - public final fun getOffset ()J - public final fun getPartition ()I - public final fun getTopic ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage$Companion; - public fun ()V - public fun (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;IJLjava/lang/String;Ljava/util/Map;Lokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;IJLjava/lang/String;Ljava/util/Map;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;IJLjava/lang/String;Ljava/util/Map;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;IJLjava/lang/String;Ljava/util/Map;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Ljava/util/Map; - public final fun getId ()Ljava/lang/String; - public final fun getKey ()Ljava/lang/String; - public final fun getMessage ()Lokio/ByteString; - public final fun getOffset ()J - public final fun getPartition ()I - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/ConsumedRecord { - public fun (Ljava/lang/String;Ljava/lang/String;[BLjava/util/Map;JI)V - public final fun getHeaders ()Ljava/util/Map; - public final fun getKey ()Ljava/lang/String; - public final fun getOffset ()J - public final fun getPartition ()I - public final fun getTopic ()Ljava/lang/String; - public final fun getValue ()[B -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/CoroutinesKt { - public static final fun getAsExecutor (Lkotlinx/coroutines/CoroutineScope;)Ljava/util/concurrent/Executor; - public static final fun getAsExecutorService (Lkotlinx/coroutines/CoroutineScope;)Ljava/util/concurrent/ExecutorService; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/ExtensionsKt { - public static final fun metadata (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public static final fun metadata (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;)Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public static final fun toProperties (Ljava/util/Map;)Ljava/util/Properties; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/GrpcStoveKafkaObserverServiceClient : com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceClient { - public fun (Lcom/squareup/wire/GrpcClient;)V - public fun healthCheck ()Lcom/squareup/wire/GrpcCall; - public fun onAcknowledgedMessage ()Lcom/squareup/wire/GrpcCall; - public fun onCommittedMessage ()Lcom/squareup/wire/GrpcCall; - public fun onConsumedMessage ()Lcom/squareup/wire/GrpcCall; - public fun onPublishedMessage ()Lcom/squareup/wire/GrpcCall; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest$Companion; - public fun ()V - public fun (Ljava/lang/String;Lokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Ljava/lang/String;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getService ()Ljava/lang/String; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$Companion; - public fun ()V - public fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus;Lokio/ByteString;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse;Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getStatus ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus : java/lang/Enum, com/squareup/wire/WireEnum { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus$Companion; - public static final field NOT_SERVING Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static final field SERVICE_UNKNOWN Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static final field SERVING Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static final field UNKNOWN Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static final fun fromValue (I)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; - public static fun values ()[Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus$Companion { - public final fun fromValue (I)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse$ServingStatus; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/util/List; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun component7 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public final fun getPorts ()Ljava/util/List; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions$Companion { - public final fun getDEFAULT_KAFKA_PORTS ()Ljava/util/List; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext { - public fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions; - public final fun copy (Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext;Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContextKt { - public static final fun kafka-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun kafka-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaExposedConfiguration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getBootstrapServers ()Ljava/lang/String; - public final fun getInterceptorClass ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem : com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAware, com/trendyol/stove/testing/e2e/system/abstractions/BeforeRunAware, com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem$Companion; - public field sink Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext;)V - public final fun adminOperations (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun afterRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun beforeRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun close ()V - public fun configuration ()Ljava/util/List; - public final fun consumer-IY8X1Ik (Ljava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;Lorg/apache/kafka/common/serialization/Deserializer;Lorg/apache/kafka/common/serialization/Deserializer;JJLjava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun consumer-IY8X1Ik$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem;Ljava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;Lorg/apache/kafka/common/serialization/Deserializer;Lorg/apache/kafka/common/serialization/Deserializer;JJLjava/lang/String;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getSink ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun messageStore ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageStore; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem; - public final fun peekCommittedMessages-rnQQ1Ag (JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun peekCommittedMessages-rnQQ1Ag$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem;JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun peekConsumedMessages-rnQQ1Ag (JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun peekConsumedMessages-rnQQ1Ag$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem;JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun peekPublishedMessages-rnQQ1Ag (JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun peekPublishedMessages-rnQQ1Ag$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem;JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun publish (Ljava/lang/String;Ljava/lang/Object;Larrow/core/Option;Ljava/util/Map;ILarrow/core/Option;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun publish$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem;Ljava/lang/String;Ljava/lang/Object;Larrow/core/Option;Ljava/util/Map;ILarrow/core/Option;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun setSink (Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink;)V - public final fun shouldBeConsumedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBeFailedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBePublishedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBeRetriedInternal-WPwdCS8 (Lkotlin/reflect/KClass;JILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystem$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemKt { - public static final field STOVE_KAFKA_BRIDGE_PORT Ljava/lang/String; - public static final fun getStoveKafkaBridgePortDefault ()Ljava/lang/String; - public static final fun getStoveSerdeRef ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static final fun setStoveKafkaBridgePortDefault (Ljava/lang/String;)V - public static final fun setStoveSerdeRef (Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;)V -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes;ZILcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/apache/kafka/common/serialization/Serializer;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes;ZILcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/apache/kafka/common/serialization/Serializer;Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getBridgeGrpcServerPort ()I - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainerOptions ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions; - public final fun getListenPublishedMessagesFromStove ()Z - public final fun getSerde ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun getTopicSuffixes ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes; - public final fun getValueSerializer ()Lorg/apache/kafka/common/serialization/Serializer; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage$Companion; - public fun ()V - public fun (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Ljava/lang/String;Lokio/ByteString;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Ljava/util/Map; - public final fun getId ()Ljava/lang/String; - public final fun getKey ()Ljava/lang/String; - public final fun getMessage ()Lokio/ByteString; - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage$Companion { -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/PublishedRecord { - public fun (Ljava/lang/String;Ljava/lang/String;[BLjava/util/Map;)V - public final fun getHeaders ()Ljava/util/Map; - public final fun getKey ()Ljava/lang/String; - public final fun getTopic ()Ljava/lang/String; - public final fun getValue ()[B -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/Reply : com/squareup/wire/Message { - public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; - public static final field Companion Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply$Companion; - public fun ()V - public fun (ILokio/ByteString;)V - public synthetic fun (ILokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (ILokio/ByteString;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply;ILokio/ByteString;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply; - public fun equals (Ljava/lang/Object;)Z - public final fun getStatus ()I - public fun hashCode ()I - public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; - public synthetic fun newBuilder ()Ljava/lang/Void; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/Reply$Companion { -} - -public class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaContainer : org/testcontainers/kafka/ConfluentKafkaContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - -public abstract interface class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceClient : com/squareup/wire/Service { - public abstract fun healthCheck ()Lcom/squareup/wire/GrpcCall; - public abstract fun onAcknowledgedMessage ()Lcom/squareup/wire/GrpcCall; - public abstract fun onCommittedMessage ()Lcom/squareup/wire/GrpcCall; - public abstract fun onConsumedMessage ()Lcom/squareup/wire/GrpcCall; - public abstract fun onPublishedMessage ()Lcom/squareup/wire/GrpcCall; -} - -public abstract interface class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceServer : com/squareup/wire/Service { - public abstract fun healthCheck (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun onAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun onCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun onConsumedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun onPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc; - public static final field SERVICE_NAME Ljava/lang/String; - public final fun getServiceDescriptor ()Lio/grpc/ServiceDescriptor; - public final fun gethealthCheckMethod ()Lio/grpc/MethodDescriptor; - public final fun getonAcknowledgedMessageMethod ()Lio/grpc/MethodDescriptor; - public final fun getonCommittedMessageMethod ()Lio/grpc/MethodDescriptor; - public final fun getonConsumedMessageMethod ()Lio/grpc/MethodDescriptor; - public final fun getonPublishedMessageMethod ()Lio/grpc/MethodDescriptor; - public final fun newStub (Lio/grpc/Channel;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceStub; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$BindableAdapter : com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase { - public fun (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lkotlin/jvm/functions/Function0;)V - public fun healthCheck (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onConsumedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase : com/squareup/wire/kotlin/grpcserver/WireBindableService { - public fun ()V - public fun (Lkotlin/coroutines/CoroutineContext;)V - public synthetic fun (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun bindService ()Lio/grpc/ServerServiceDefinition; - protected final fun getContext ()Lkotlin/coroutines/CoroutineContext; - public fun healthCheck (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onConsumedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$AcknowledgedMessageMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$CommittedMessageMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$ConsumedMessageMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$HealthCheckRequestMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$HealthCheckResponseMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckResponse;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$PublishedMessageMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase$ReplyMarshaller : com/squareup/wire/kotlin/grpcserver/WireMethodMarshaller { - public fun ()V - public fun marshalledClass ()Ljava/lang/Class; - public fun parse (Ljava/io/InputStream;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply; - public synthetic fun parse (Ljava/io/InputStream;)Ljava/lang/Object; - public fun stream (Lcom/trendyol/stove/testing/e2e/standalone/kafka/Reply;)Ljava/io/InputStream; - public synthetic fun stream (Ljava/lang/Object;)Ljava/io/InputStream; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceStub : io/grpc/kotlin/AbstractCoroutineStub { - public synthetic fun build (Lio/grpc/Channel;Lio/grpc/CallOptions;)Lio/grpc/stub/AbstractStub; - public final fun healthCheck (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun onAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun onCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun onConsumedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun onPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaValueDeserializer : org/apache/kafka/common/serialization/Deserializer { - public fun ()V - public fun deserialize (Ljava/lang/String;[B)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaValueSerializer : org/apache/kafka/common/serialization/Serializer { - public fun ()V - public fun serialize (Ljava/lang/String;Ljava/lang/Object;)[B -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes { - public fun ()V - public fun (Ljava/util/List;Ljava/util/List;)V - public synthetic fun (Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/util/List;Ljava/util/List;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes; - public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/util/List; - public final fun getRetry ()Ljava/util/List; - public fun hashCode ()I - public final fun isErrorTopic (Ljava/lang/String;)Z - public final fun isRetryTopic (Ljava/lang/String;)Z - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils; - public final fun createClient (Ljava/lang/String;Lkotlinx/coroutines/CoroutineScope;)Lcom/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceClient; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageStore { - public fun ()V - public final fun committedMessages ()Ljava/util/Collection; - public final fun consumedMessages ()Ljava/util/Collection; - public final fun failedMessages ()Ljava/util/Collection; - public final fun publishedMessages ()Ljava/util/Collection; - public final fun retriedMessages ()Ljava/util/Collection; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaBridge : org/apache/kafka/clients/consumer/ConsumerInterceptor, org/apache/kafka/clients/producer/ProducerInterceptor { - public fun ()V - public fun close ()V - public fun configure (Ljava/util/Map;)V - public fun onAcknowledgement (Lorg/apache/kafka/clients/producer/RecordMetadata;Ljava/lang/Exception;)V - public fun onCommit (Ljava/util/Map;)V - public fun onConsume (Lorg/apache/kafka/clients/consumer/ConsumerRecords;)Lorg/apache/kafka/clients/consumer/ConsumerRecords; - public fun onSend (Lorg/apache/kafka/clients/producer/ProducerRecord;)Lorg/apache/kafka/clients/producer/ProducerRecord; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaObserverGrpcServer : com/trendyol/stove/testing/e2e/standalone/kafka/StoveKafkaObserverServiceWireGrpc$StoveKafkaObserverServiceImplBase { - public fun (Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink;)V - public fun healthCheck (Lcom/trendyol/stove/testing/e2e/standalone/kafka/HealthCheckRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onConsumedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink : com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/CommonOps, com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkOps { - public fun (Lorg/apache/kafka/clients/admin/Admin;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes;)V - public fun dumpMessages ()Ljava/lang/String; - public fun getAdminClient ()Lorg/apache/kafka/clients/admin/Admin; - public fun getLogger ()Lorg/slf4j/Logger; - public fun getSerde ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public fun getStore ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageStore; - public fun getTopicSuffixes ()Lcom/trendyol/stove/testing/e2e/standalone/kafka/TopicSuffixes; - public final fun onMessageAcknowledged (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;)V - public final fun onMessageCommitted (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;)V - public final fun onMessageConsumed (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)V - public final fun onMessagePublished (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;)V - public fun readCatching-gIAlu-s ([BLkotlin/reflect/KClass;)Ljava/lang/Object; - public fun recordAcknowledgedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/AcknowledgedMessage;)V - public fun recordCommittedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/CommittedMessage;)V - public fun recordConsumed (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)V - public fun recordError (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)V - public fun recordPublishedMessage (Lcom/trendyol/stove/testing/e2e/standalone/kafka/PublishedMessage;)V - public fun recordRetry (Lcom/trendyol/stove/testing/e2e/standalone/kafka/ConsumedMessage;)V - public fun throwIfFailed (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public fun throwIfRetried (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public fun waitUntilConditionMet-WPwdCS8 (Lkotlin/jvm/functions/Function0;JLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun waitUntilConsumed-rnQQ1Ag (JLkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun waitUntilCount-dWUq8MI (Lkotlin/jvm/functions/Function1;JILkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun waitUntilFailed-rnQQ1Ag (JLkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun waitUntilPublished-rnQQ1Ag (JLkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun waitUntilRetried-gRj5Bb8 (JILkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - diff --git a/lib/stove-testing-e2e-kafka/build.gradle.kts b/lib/stove-testing-e2e-kafka/build.gradle.kts index 30835660..c5b256c9 100644 --- a/lib/stove-testing-e2e-kafka/build.gradle.kts +++ b/lib/stove-testing-e2e-kafka/build.gradle.kts @@ -33,17 +33,13 @@ buildscript { } } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.standalone.kafka.setup.Stove") -} - wire { sourcePath("src/main/proto") kotlin { rpcRole = "client" rpcCallStyle = "suspending" exclusive = false - javaInterop = false + javaInterop = true } kotlin { custom { diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions.kt index a8c33d96..53d082a7 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContainerOptions.kt @@ -6,8 +6,7 @@ import org.testcontainers.utility.DockerImageName open class StoveKafkaContainer( override val imageNameAccess: DockerImageName -) : ConfluentKafkaContainer(imageNameAccess), - StoveContainer +) : ConfluentKafkaContainer(imageNameAccess), StoveContainer data class KafkaContainerOptions( override val registry: String = DEFAULT_REGISTRY, diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext.kt index d1d970a8..34abb899 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaContext.kt @@ -21,8 +21,7 @@ internal fun TestSystem.withKafka(options: KafkaSystemOptions): TestSystem { options.containerOptions.registry, options.containerOptions.compatibleSubstitute ) { - options.containerOptions - .useContainerFn(it) + options.containerOptions.useContainerFn(it) .withExposedPorts(*options.containerOptions.ports.toTypedArray()) .withReuse(this.options.keepDependenciesRunning) .let { c -> c as StoveKafkaContainer } 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 477fe1bb..2ee3cc01 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 @@ -1,9 +1,10 @@ package com.trendyol.stove.testing.e2e.standalone.kafka 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.StoveSerde +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper 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.* @@ -23,21 +24,17 @@ import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -var stoveSerdeRef: StoveSerde = StoveSerde.jackson.anyByteArraySerde() +var stoveKafkaObjectMapperRef: ObjectMapper = StoveObjectMapper.Default var stoveKafkaBridgePortDefault = "50051" const val STOVE_KAFKA_BRIDGE_PORT = "STOVE_KAFKA_BRIDGE_PORT" internal val StoveKafkaCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) -@Suppress("TooManyFunctions", "unused") +@Suppress("TooManyFunctions") @StoveDsl class KafkaSystem( override val testSystem: TestSystem, private val context: KafkaContext -) : PluggedSystem, - ExposesConfiguration, - RunAware, - AfterRunAware, - BeforeRunAware { +) : PluggedSystem, ExposesConfiguration, RunAware, AfterRunAware { private lateinit var exposedConfiguration: KafkaExposedConfiguration private lateinit var adminClient: Admin private lateinit var kafkaPublisher: KafkaProducer @@ -49,10 +46,6 @@ class KafkaSystem( private val state: StateStorage = testSystem.options.createStateStorage() - override suspend fun beforeRun() { - stoveSerdeRef = context.options.serde - } - override suspend fun run() { exposedConfiguration = state.capture { context.container.start() @@ -69,7 +62,7 @@ class KafkaSystem( ) sink = TestSystemMessageSink( adminClient, - context.options.serde, + context.options.objectMapper, context.options.topicSuffixes ) grpcServer = startGrpcServer() @@ -148,11 +141,10 @@ class KafkaSystem( var offset = -1L var loop = true while (loop) { - sink.store - .consumedMessages() + sink.store.consumedMessages() .filter { it.topic == topic && it.offset > offset } .onEach { offset = it.offset } - .map { ConsumedRecord(it.topic, it.key, it.message.toByteArray(), it.headers, it.offset, it.partition) } + .map { ConsumedRecord(it.topic, it.key, it.message, it.headers, it.offset, it.partition) } .forEach { loop = !condition(it) } @@ -176,8 +168,7 @@ class KafkaSystem( var offset = -1L var loop = true while (loop) { - sink.store - .committedMessages() + sink.store.committedMessages() .filter { it.topic == topic && it.offset > offset } .onEach { offset = it.offset } .map { CommittedRecord(it.topic, it.metadata, it.offset, it.partition) } @@ -204,11 +195,10 @@ class KafkaSystem( val seenIds = mutableMapOf() var loop = true while (loop) { - sink.store - .publishedMessages() + sink.store.publishedMessages() .filter { it.topic == topic && !seenIds.containsKey(it.id) } .onEach { seenIds[it.id] = it } - .map { PublishedRecord(it.topic, it.key, it.message.toByteArray(), it.headers) } + .map { PublishedRecord(it.topic, it.key, it.message, it.headers) } .forEach { loop = !condition(it) } @@ -368,7 +358,9 @@ class KafkaSystem( ProducerConfig.ACKS_CONFIG to "1" ) + ( if (listenKafkaSystemPublishedMessages) { - mapOf(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG to exposedConfiguration.interceptorClass) + mapOf( + ProducerConfig.INTERCEPTOR_CLASSES_CONFIG to exposedConfiguration.interceptorClass + ) } else { emptyMap() } @@ -385,8 +377,7 @@ class KafkaSystem( private suspend fun startGrpcServer(): Server { System.setProperty(STOVE_KAFKA_BRIDGE_PORT, context.options.bridgeGrpcServerPort.toString()) return Try { - ServerBuilder - .forPort(context.options.bridgeGrpcServerPort) + ServerBuilder.forPort(context.options.bridgeGrpcServerPort) .executor(StoveKafkaCoroutineScope.also { it.ensureActive() }.asExecutor) .addService(StoveKafkaObserverGrpcServer(sink)) .handshakeTimeout(GRPC_TIMEOUT_IN_SECONDS, java.util.concurrent.TimeUnit.SECONDS) diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions.kt index c749e7f4..a36d6c0a 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/KafkaSystemOptions.kt @@ -1,6 +1,6 @@ package com.trendyol.stove.testing.e2e.standalone.kafka -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.system.abstractions.* import org.apache.kafka.common.serialization.Serializer @@ -18,20 +18,9 @@ class KafkaSystemOptions( */ val bridgeGrpcServerPort: Int = stoveKafkaBridgePortDefault.toInt(), /** - * The Serde that is used while asserting the messages, - * serializing while bridging the messages. Take a look at the [serde] property for more information. - * - * The default value is [StoveSerde.jackson]'s anyByteArraySerde. - * Depending on your application's needs you might want to change this value. - * - * The places where it was used listed below: - * - * @see [com.trendyol.stove.testing.e2e.standalone.kafka.intercepting.StoveKafkaBridge] for bridging the messages. - * @see StoveKafkaValueSerializer for serializing the messages. - * @see StoveKafkaValueDeserializer for deserializing the messages. - * @see valueSerializer for serializing the messages. + * The object mapper that is used to serialize and deserialize messages. */ - val serde: StoveSerde = stoveSerdeRef, + val objectMapper: ObjectMapper = stoveKafkaObjectMapperRef, /** * The Value serializer that is used to serialize messages. */ @@ -44,8 +33,7 @@ class KafkaSystemOptions( * The options for the Kafka system that is exposed to the application */ override val configureExposedConfiguration: (KafkaExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration +) : SystemOptions, ConfiguresExposedConfiguration /** * Suffixes for error and retry topics in the application. diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/SerDe.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/SerDe.kt index 55d127da..347ccb01 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/SerDe.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/SerDe.kt @@ -1,5 +1,6 @@ package com.trendyol.stove.testing.e2e.standalone.kafka +import com.fasterxml.jackson.module.kotlin.readValue import org.apache.kafka.common.serialization.* @Suppress("UNCHECKED_CAST") @@ -7,12 +8,12 @@ class StoveKafkaValueDeserializer : Deserializer { override fun deserialize( topic: String, data: ByteArray - ): T = stoveSerdeRef.deserialize(data, Any::class.java) as T + ): T = stoveKafkaObjectMapperRef.readValue(data) as T } class StoveKafkaValueSerializer : Serializer { override fun serialize( topic: String, data: T - ): ByteArray = stoveSerdeRef.serialize(data) + ): ByteArray = stoveKafkaObjectMapperRef.writeValueAsBytes(data) } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/coroutines.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/coroutines.kt index 04cb90fc..179e20ef 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/coroutines.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/coroutines.kt @@ -9,9 +9,7 @@ val CoroutineScope.asExecutor: Executor val CoroutineScope.asExecutorService: ExecutorService get() = CoroutineExecutorService(this) -internal class CoroutineExecutorService( - private val coroutineScope: CoroutineScope -) : AbstractExecutorService() { +internal class CoroutineExecutorService(private val coroutineScope: CoroutineScope) : AbstractExecutorService() { override fun execute(command: Runnable) { coroutineScope.launch { command.run() } } @@ -25,9 +23,13 @@ internal class CoroutineExecutorService( return emptyList() } - override fun isShutdown(): Boolean = coroutineScope.coroutineContext[Job]?.isCancelled ?: true + override fun isShutdown(): Boolean { + return coroutineScope.coroutineContext[Job]?.isCancelled ?: true + } - override fun isTerminated(): Boolean = coroutineScope.coroutineContext[Job]?.isCompleted ?: true + override fun isTerminated(): Boolean { + return coroutineScope.coroutineContext[Job]?.isCompleted ?: true + } override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean { // Coroutine jobs don't support await termination out of the box @@ -36,9 +38,7 @@ internal class CoroutineExecutorService( } } -internal class StoveCoroutineExecutor( - private val scope: CoroutineScope -) : Executor { +internal class StoveCoroutineExecutor(private val scope: CoroutineScope) : Executor { override fun execute(command: Runnable) { scope.launch { command.run() } } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/CommonOps.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/CommonOps.kt index 1b3394c8..44c3e28b 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/CommonOps.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/CommonOps.kt @@ -1,8 +1,8 @@ package com.trendyol.stove.testing.e2e.standalone.kafka.intercepting import arrow.core.toOption +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.messaging.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.standalone.kafka.* import kotlinx.coroutines.* import org.apache.kafka.clients.admin.Admin @@ -12,7 +12,7 @@ import kotlin.time.Duration internal interface CommonOps { val store: MessageStore - val serde: StoveSerde + val serde: ObjectMapper val adminClient: Admin val topicSuffixes: TopicSuffixes val logger: Logger @@ -28,9 +28,8 @@ internal interface CommonOps { ): Collection = runCatching { val collectionFunc = this withTimeout(duration) { - while (!collectionFunc().any { condition(it) }) { + while (!collectionFunc().any { condition(it) }) delay(DELAY_MS) - } } collectionFunc().filter { condition(it) } }.fold( @@ -44,9 +43,8 @@ internal interface CommonOps { ): Collection = runCatching { val collectionFunc = this withTimeout(duration) { - while (collectionFunc().size < count) { + while (collectionFunc().size < count) delay(DELAY_MS) - } } collectionFunc() }.getOrElse { @@ -59,10 +57,9 @@ internal interface CommonOps { fun throwIfFailed( clazz: KClass, selector: (message: ParsedMessage) -> Boolean - ): Unit = store - .failedMessages() + ): Unit = store.failedMessages() .filter { - selector(SuccessfulParsedMessage(readCatching(it.message.toByteArray(), clazz).getOrNull().toOption(), it.metadata())) + selector(SuccessfulParsedMessage(readCatching(it.message, clazz).getOrNull().toOption(), it.metadata())) }.forEach { throw AssertionError("Message was expected to be consumed successfully, but failed: $it \n ${dumpMessages()}") } @@ -70,12 +67,11 @@ internal interface CommonOps { fun throwIfRetried( clazz: KClass, selector: (message: ParsedMessage) -> Boolean - ): Unit = store - .retriedMessages() + ): Unit = store.retriedMessages() .filter { selector( SuccessfulParsedMessage( - readCatching(it.message.toByteArray(), clazz).getOrNull().toOption(), + readCatching(it.message, clazz).getOrNull().toOption(), MessageMetadata(it.topic, it.key, it.headers) ) ) @@ -83,11 +79,28 @@ internal interface CommonOps { throw AssertionError("Message was expected to be consumed successfully, but was retried: $it \n ${dumpMessages()}") } + fun throwIfSucceeded( + clazz: KClass, + selector: (message: ParsedMessage) -> Boolean + ): Unit = store.consumedMessages() + .filter { + selector( + SuccessfulParsedMessage( + readCatching(it.message, clazz).getOrNull().toOption(), + it.metadata() + ) + ) && store.isCommitted(it.topic, it.offset, it.partition) + }.forEach { throw AssertionError("Message was expected to fail, but was consumed: $it \n ${dumpMessages()}") } + fun readCatching( - value: ByteArray, + json: Any, clazz: KClass - ): Result = runCatching { serde.deserialize(value, clazz.java) } - .onFailure { exception -> logger.error("Failed to deserialize message: ${String(value)}", exception) } + ): Result = runCatching { + when (json) { + is String -> serde.readValue(json, clazz.java) + else -> serde.convertValue(json, clazz.java) + } + } fun dumpMessages(): String } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils.kt index effd8559..a8e1f79d 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/GrpcUtils.kt @@ -9,8 +9,7 @@ import kotlin.time.toJavaDuration object GrpcUtils { private val getClient = { scope: CoroutineScope -> - OkHttpClient - .Builder() + OkHttpClient.Builder() .protocols(listOf(Protocol.H2_PRIOR_KNOWLEDGE)) .callTimeout(30.seconds.toJavaDuration()) .readTimeout(30.seconds.toJavaDuration()) @@ -20,8 +19,7 @@ object GrpcUtils { .build() } - fun createClient(onPort: String, scope: CoroutineScope): StoveKafkaObserverServiceClient = GrpcClient - .Builder() + fun createClient(onPort: String, scope: CoroutineScope): StoveKafkaObserverServiceClient = GrpcClient.Builder() .client(getClient(scope)) .baseUrl("http://0.0.0.0:$onPort".toHttpUrl()) .build() diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkOps.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkOps.kt index 903dbffe..0967c287 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkOps.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkOps.kt @@ -7,9 +7,7 @@ import kotlinx.coroutines.runBlocking import kotlin.reflect.KClass import kotlin.time.Duration -internal interface MessageSinkOps : - MessageSinkPublishOps, - CommonOps { +internal interface MessageSinkOps : MessageSinkPublishOps, CommonOps { fun recordConsumed(record: ConsumedMessage): Unit = runBlocking { store.record(record) logger.info( @@ -50,15 +48,13 @@ internal interface MessageSinkOps : ) { val getRecords = { store.consumedMessages() } getRecords.waitUntilConditionMet(atLeastIn, "While expecting consuming of ${clazz.java.simpleName}") { - val outcome = readCatching(it.message.toByteArray(), clazz) - outcome.isSuccess && - condition( - SuccessfulParsedMessage( - outcome.getOrNull().toOption(), - it.metadata() - ) - ) && - store.isCommitted(it.topic, it.offset, it.partition) + val outcome = readCatching(it.message, clazz) + outcome.isSuccess && condition( + SuccessfulParsedMessage( + outcome.getOrNull().toOption(), + it.metadata() + ) + ) && store.isCommitted(it.topic, it.offset, it.partition) } throwIfFailed(clazz, condition) @@ -70,17 +66,13 @@ internal interface MessageSinkOps : clazz: KClass, condition: (ParsedMessage) -> Boolean ) { - class FailedMessage( - val message: ByteArray, - val metadata: MessageMetadata - ) + data class FailedMessage(val message: String, val metadata: MessageMetadata) val getRecords = { - store.failedMessages().map { FailedMessage(it.message.toByteArray(), it.metadata()) } + - store - .publishedMessages() + store.failedMessages().map { FailedMessage(it.message, it.metadata()) } + + store.publishedMessages() .filter { topicSuffixes.isErrorTopic(it.topic) } - .map { FailedMessage(it.message.toByteArray(), it.metadata()) } + .map { FailedMessage(it.message, it.metadata()) } } getRecords.waitUntilConditionMet(atLeastIn, "While expecting Failure of ${clazz.java.simpleName}") { val outcome = readCatching(it.message, clazz) @@ -97,14 +89,13 @@ internal interface MessageSinkOps : val getRecords = { store.retriedMessages() } val failedFunc = suspend { getRecords.waitUntilConditionMet(atLeastIn, "While expecting Retrying of ${clazz.java.simpleName}") { - val outcome = readCatching(it.message.toByteArray(), clazz) - outcome.isSuccess && - condition( - SuccessfulParsedMessage( - outcome.getOrNull().toOption(), - MessageMetadata(it.topic, it.key, it.headers) - ) + val outcome = readCatching(it.message, clazz) + outcome.isSuccess && condition( + SuccessfulParsedMessage( + outcome.getOrNull().toOption(), + MessageMetadata(it.topic, it.key, it.headers) ) + ) } } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkPublishOps.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkPublishOps.kt index bfc81c4f..faba585f 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkPublishOps.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/MessageSinkPublishOps.kt @@ -15,7 +15,7 @@ internal interface MessageSinkPublishOps : CommonOps { ) { val getRecords = { store.publishedMessages().map { it } } getRecords.waitUntilConditionMet(atLeastIn, "While expecting Publishing of ${clazz.java.simpleName}") { - val outcome = readCatching(it.message.toByteArray(), clazz) + val outcome = readCatching(it.message, clazz) outcome.isSuccess && condition(SuccessfulParsedMessage(outcome.getOrNull().toOption(), MessageMetadata(it.topic, it.key, it.headers))) } } diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaBridge.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaBridge.kt index 79809438..26575c5b 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaBridge.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/StoveKafkaBridge.kt @@ -1,11 +1,10 @@ package com.trendyol.stove.testing.e2e.standalone.kafka.intercepting +import com.fasterxml.jackson.databind.ObjectMapper import com.squareup.wire.GrpcException import com.trendyol.stove.functional.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.standalone.kafka.* import kotlinx.coroutines.runBlocking -import okio.ByteString.Companion.toByteString import org.apache.kafka.clients.consumer.* import org.apache.kafka.clients.producer.* import org.apache.kafka.common.TopicPartition @@ -14,12 +13,10 @@ import java.nio.charset.Charset import java.util.* @Suppress("UNUSED") -class StoveKafkaBridge : - ConsumerInterceptor, - ProducerInterceptor { +class StoveKafkaBridge : ConsumerInterceptor, ProducerInterceptor { private val logger: Logger = org.slf4j.LoggerFactory.getLogger(StoveKafkaBridge::class.java) private val client: StoveKafkaObserverServiceClient by lazy { startGrpcClient() } - private val serde: StoveSerde by lazy { stoveSerdeRef } + private val mapper: ObjectMapper by lazy { stoveKafkaObjectMapperRef } override fun onSend(record: ProducerRecord): ProducerRecord = runBlocking { record.also { send(publishedMessage(it)) } @@ -108,7 +105,7 @@ class StoveKafkaBridge : ConsumedMessage( id = UUID.randomUUID().toString(), key = record.key().toString(), - message = serializeIfNotYet(record.value()).toByteString(), + message = serializeIfNotString(record.value()), topic = record.topic(), offset = record.offset(), partition = record.partition(), @@ -119,7 +116,7 @@ class StoveKafkaBridge : private fun publishedMessage(record: ProducerRecord) = PublishedMessage( id = UUID.randomUUID().toString(), key = record.key().toString(), - message = serializeIfNotYet(record.value()).toByteString(), + message = serializeIfNotString(record.value()), topic = record.topic(), headers = record.headers().associate { it.key() to it.value().toString(Charset.defaultCharset()) } ) @@ -136,9 +133,9 @@ class StoveKafkaBridge : ) } - private fun serializeIfNotYet(value: V): ByteArray = when (value) { - is ByteArray -> value - else -> serde.serialize(value as Any) + private fun serializeIfNotString(value: V): String = when (value) { + is String -> value + else -> mapper.writeValueAsString(value) } private fun startGrpcClient(): StoveKafkaObserverServiceClient { diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink.kt index cf31383b..dcc4ff59 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/intercepting/TestSystemMessageSink.kt @@ -1,16 +1,15 @@ package com.trendyol.stove.testing.e2e.standalone.kafka.intercepting -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.standalone.kafka.* import org.apache.kafka.clients.admin.Admin import org.slf4j.* class TestSystemMessageSink( override val adminClient: Admin, - override val serde: StoveSerde, + override val serde: ObjectMapper, override val topicSuffixes: TopicSuffixes -) : MessageSinkOps, - CommonOps { +) : MessageSinkOps, CommonOps { override val logger: Logger = LoggerFactory.getLogger(javaClass) override val store: MessageStore = MessageStore() diff --git a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/messages.kt b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/messages.kt index db57a9ec..0136ea69 100644 --- a/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/messages.kt +++ b/lib/stove-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/messages.kt @@ -1,23 +1,23 @@ package com.trendyol.stove.testing.e2e.standalone.kafka -class PublishedRecord( +data class PublishedRecord( val topic: String, val key: String, - val value: ByteArray, + val value: String, val headers: Map ) -class CommittedRecord( +data class CommittedRecord( val topic: String, val metadata: String, val offset: Long, val partition: Int ) -class ConsumedRecord( +data class ConsumedRecord( val topic: String, val key: String, - val value: ByteArray, + val value: String, val headers: Map, val offset: Long, val partition: Int diff --git a/lib/stove-testing-e2e-kafka/src/main/proto/messages.proto b/lib/stove-testing-e2e-kafka/src/main/proto/messages.proto index 245d5476..8a3f587e 100644 --- a/lib/stove-testing-e2e-kafka/src/main/proto/messages.proto +++ b/lib/stove-testing-e2e-kafka/src/main/proto/messages.proto @@ -5,7 +5,7 @@ package com.trendyol.stove.testing.e2e.standalone.kafka; message ConsumedMessage { string id = 1; - bytes message = 2; + string message = 2; string topic = 3; int32 partition = 4; int64 offset = 5; @@ -15,7 +15,7 @@ message ConsumedMessage { message PublishedMessage { string id = 1; - bytes message = 2; + string message = 2; string topic = 3; string key = 4; map headers = 5; diff --git a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/Stove.kt b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/ProjectConfig.kt similarity index 94% rename from lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/Stove.kt rename to lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/ProjectConfig.kt index 8112bc2e..6687c1d0 100644 --- a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/Stove.kt +++ b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/ProjectConfig.kt @@ -25,10 +25,9 @@ class KafkaApplicationUnderTest : ApplicationUnderTest { AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers ).let { AdminClient.create(it) } - val newTopics = KafkaTestShared.topics - .flatMap { - listOf(it.topic, it.retryTopic, it.deadLetterTopic) - }.map { NewTopic(it, 1, 1) } + val newTopics = KafkaTestShared.topics.flatMap { + listOf(it.topic, it.retryTopic, it.deadLetterTopic) + }.map { NewTopic(it, 1, 1) } client.createTopics(newTopics).all().get() startConsumers(bootstrapServers) } @@ -69,7 +68,7 @@ class KafkaApplicationUnderTest : ApplicationUnderTest { } @ExperimentalKotest -class Stove : AbstractProjectConfig() { +class ProjectConfig : AbstractProjectConfig() { init { stoveKafkaBridgePortDefault = "50052" } diff --git a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/DomainEvents.kt b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/DomainEvents.kt index 6bf176ac..4edd94bc 100644 --- a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/DomainEvents.kt +++ b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/DomainEvents.kt @@ -3,9 +3,7 @@ package com.trendyol.stove.testing.e2e.standalone.kafka.setup.example import kotlin.random.Random object DomainEvents { - data class ProductCreated( - val productId: String - ) { + data class ProductCreated(val productId: String) { companion object { val randomString = { Random.nextInt(0, Int.MAX_VALUE).toString() } @@ -13,7 +11,5 @@ object DomainEvents { } } - data class ProductFailingCreated( - val productId: String - ) + data class ProductFailingCreated(val productId: String) } diff --git a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/StoveListener.kt b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/StoveListener.kt index b35de5b5..2709d7a4 100644 --- a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/StoveListener.kt +++ b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/StoveListener.kt @@ -39,7 +39,8 @@ abstract class StoveListener( .map { logger.info("Message COMMITTED on the application side: ${message.value()}") consumer.commitAsync() - }.recover { + } + .recover { logger.warn("CONSUMER GOT an ERROR on the application side, exception: $it") if (message.getRetryCount() < 3) { logger.warn("CONSUMER GOT an ERROR, retrying...") diff --git a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/consumers/ProductFailingConsumer.kt b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/consumers/ProductFailingConsumer.kt index c070ad87..cace7cc3 100644 --- a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/consumers/ProductFailingConsumer.kt +++ b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/setup/example/consumers/ProductFailingConsumer.kt @@ -19,18 +19,14 @@ class ProductFailingConsumer( ) override suspend fun listen(record: ConsumerRecord) { - record - .headers() - .firstOrNone { it.key() == "doNotFail" } + record.headers().firstOrNone { it.key() == "doNotFail" } .onSome { return } .onNone { throw Exception("exception occurred on purpose") } } } fun ConsumerRecord.getRetryCount(): Int = - this - .headers() - .firstOrNone { it.key() == "retry" } + this.headers().firstOrNone { it.key() == "retry" } .map { it.value().toString(Charsets.UTF_8).toInt() } .getOrElse { 0 } diff --git a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/tests/KafkaSystemTests.kt b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/tests/KafkaSystemTests.kt index a0a4a93a..93a3ee92 100644 --- a/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/tests/KafkaSystemTests.kt +++ b/lib/stove-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/standalone/kafka/tests/KafkaSystemTests.kt @@ -16,146 +16,145 @@ import kotlin.random.Random import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds -class KafkaSystemTests : - FunSpec({ - val randomString = { Random.nextInt(0, Int.MAX_VALUE).toString() } - - test("When publish then it should work") { - validate { - kafka { - val key = randomString() - val productId = "$key[productCreated]" - publish("product", message = ProductCreated(productId), key = key.some()) - shouldBePublished { - actual.productId == productId - } - - peekPublishedMessages(topic = "product") { - it.key == key - } - - shouldBeConsumed(1.minutes) { - actual.productId == productId - } - - peekConsumedMessages(topic = "product") { - it.key == key - } +class KafkaSystemTests : FunSpec({ + val randomString = { Random.nextInt(0, Int.MAX_VALUE).toString() } + + test("When publish then it should work") { + validate { + kafka { + val key = randomString() + val productId = "$key[productCreated]" + publish("product", message = ProductCreated(productId), key = key.some()) + shouldBePublished { + actual.productId == productId + } + + peekPublishedMessages(topic = "product") { + it.key == key + } + + shouldBeConsumed(1.minutes) { + actual.productId == productId + } + + peekConsumedMessages(topic = "product") { + it.key == key } } } - test("lots of messages") { - validate { - kafka { - val messages = ProductCreated.randoms(100) - messages.map { async { publish("product", it, key = randomString().some()) } }.awaitAll() - messages.map { async { shouldBePublished { actual.productId == it.productId } } }.awaitAll() - messages.map { async { shouldBeConsumed(1.minutes) { actual.productId == it.productId } } }.awaitAll() - - peekConsumedMessages(topic = "product") { - it.offset == 100L - } - - peekCommittedMessages(topic = "product") { record -> - record.offset == 101L // next offset - } + } + test("lots of messages") { + validate { + kafka { + val messages = ProductCreated.randoms(100) + messages.map { async { publish("product", it, key = randomString().some()) } }.awaitAll() + messages.map { async { shouldBePublished { actual.productId == it.productId } } }.awaitAll() + messages.map { async { shouldBeConsumed(1.minutes) { actual.productId == it.productId } } }.awaitAll() + + peekConsumedMessages(topic = "product") { + it.offset == 100L + } + + peekCommittedMessages(topic = "product") { record -> + record.offset == 101L // next offset } } } + } + + test("When publish to a failing consumer should end-up throwing exception") { + validate { + kafka { + val string = randomString() + val productId = "$string[productFailingCreated]" + val key = string.some() + publish("productFailing", ProductFailingCreated(productId), key = key) + shouldBeRetried(atLeastIn = 1.minutes, times = 3) { + actual.productId == productId + } + + shouldBePublished(atLeastIn = 1.minutes) { + this.metadata.topic == "productFailing.error" + } - test("When publish to a failing consumer should end-up throwing exception") { - validate { - kafka { - val string = randomString() - val productId = "$string[productFailingCreated]" - val key = string.some() - publish("productFailing", ProductFailingCreated(productId), key = key) - shouldBeRetried(atLeastIn = 1.minutes, times = 3) { - actual.productId == productId - } - - shouldBePublished(atLeastIn = 1.minutes) { - this.metadata.topic == "productFailing.error" - } - - peekPublishedMessages(topic = "productFailing.error") { - it.key == string - } + peekPublishedMessages(topic = "productFailing.error") { + it.key == string } } } + } - test("in-flight consumer should commit the message after consuming it successfully") { - validate { - kafka { - val key = randomString() - val productId = "$key[productCreated]" - val topic = randomString() + test("in-flight consumer should commit the message after consuming it successfully") { + validate { + kafka { + val key = randomString() + val productId = "$key[productCreated]" + val topic = randomString() - adminOperations { - createTopic(NewTopic(topic, 1, 1)) - } + adminOperations { + createTopic(NewTopic(topic, 1, 1)) + } - publish(topic, message = ProductCreated(productId), key = key.some()) - shouldBePublished { - actual.productId == productId - } + publish(topic, message = ProductCreated(productId), key = key.some()) + shouldBePublished { + actual.productId == productId + } - consumer(topic, readOnly = false) { - println(it) // it should commit - } + consumer(topic, readOnly = false) { + println(it) // it should commit + } - shouldBeConsumed { - actual.productId == productId - } + shouldBeConsumed { + actual.productId == productId } } } + } + + test("in-flight consumer: same consumer group after consuming successfully should not consume the same message again") { + validate { + kafka { + val key = randomString() + val productId = "$key[productCreated]" + val topic = randomString() + + adminOperations { + createTopic(NewTopic(topic, 1, 1)) + } + + publish(topic, message = ProductCreated(productId), key = key.some()) + shouldBePublished { + actual.productId == productId + } + + val consumerGroup1 = randomString() + consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { + println(it) + } + + delay(3.seconds) + val consumedMessages = this.messageStore().consumedMessages().filter { it.topic == topic } + val committedMessages = this.messageStore().committedMessages().filter { it.topic == topic } + committedMessages.map { it.offset } shouldNotContainAll consumedMessages.map { it.offset } + + // and consumer can re-read + val reReadMessages = mutableListOf>() + consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { + reReadMessages.add(it) + } + reReadMessages.size shouldBe consumedMessages.size + + // and consumer can commit + consumer(topic, readOnly = false, autoOffsetReset = "earliest", groupId = consumerGroup1) { + println(it) + } - test("in-flight consumer: same consumer group after consuming successfully should not consume the same message again") { - validate { - kafka { - val key = randomString() - val productId = "$key[productCreated]" - val topic = randomString() - - adminOperations { - createTopic(NewTopic(topic, 1, 1)) - } - - publish(topic, message = ProductCreated(productId), key = key.some()) - shouldBePublished { - actual.productId == productId - } - - val consumerGroup1 = randomString() - consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { - println(it) - } - - delay(3.seconds) - val consumedMessages = this.messageStore().consumedMessages().filter { it.topic == topic } - val committedMessages = this.messageStore().committedMessages().filter { it.topic == topic } - committedMessages.map { it.offset } shouldNotContainAll consumedMessages.map { it.offset } - - // and consumer can re-read - val reReadMessages = mutableListOf>() - consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { - reReadMessages.add(it) - } - reReadMessages.size shouldBe consumedMessages.size - - // and consumer can commit - consumer(topic, readOnly = false, autoOffsetReset = "earliest", groupId = consumerGroup1) { - println(it) - } - - val committedMessagesAfterCommit = mutableListOf>() - consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { - committedMessagesAfterCommit.add(it) - } - committedMessagesAfterCommit.size shouldBe 0 + val committedMessagesAfterCommit = mutableListOf>() + consumer(topic, readOnly = true, autoOffsetReset = "earliest", groupId = consumerGroup1) { + committedMessagesAfterCommit.add(it) } + committedMessagesAfterCommit.size shouldBe 0 } } - }) + } +}) diff --git a/lib/stove-testing-e2e-mongodb/api/stove-testing-e2e-mongodb.api b/lib/stove-testing-e2e-mongodb/api/stove-testing-e2e-mongodb.api deleted file mode 100644 index 1b2b1139..00000000 --- a/lib/stove-testing-e2e-mongodb/api/stove-testing-e2e-mongodb.api +++ /dev/null @@ -1,171 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/mongodb/DatabaseOptions { - public fun ()V - public fun (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase; - public final fun copy (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase;)Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions;Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getDefault ()Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions$DefaultDatabase; - public fun equals (Ljava/lang/Object;)Z - public final fun getCollection ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/mongodb/MongoDsl : java/lang/annotation/Annotation { -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongodbContext { - public fun (Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer;Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions; - public final fun copy (Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer;Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbContext;Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer;Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongodbExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()I - public final fun component4 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbExposedConfiguration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getConnectionString ()Ljava/lang/String; - public final fun getHost ()Ljava/lang/String; - public final fun getPort ()I - public final fun getReplicaSetUrl ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongodbSystem : com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem$Companion; - public static final field RESERVED_ID Ljava/lang/String; - public field mongoClient Lcom/mongodb/kotlin/client/coroutine/MongoClient; - public fun close ()V - public fun configuration ()Ljava/util/List; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getContext ()Lcom/trendyol/stove/testing/e2e/mongodb/MongodbContext; - public final fun getMongoClient ()Lcom/mongodb/kotlin/client/coroutine/MongoClient; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun setMongoClient (Lcom/mongodb/kotlin/client/coroutine/MongoClient;)V - public final fun shouldDelete (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun shouldDelete$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun shouldNotExist (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun shouldNotExist$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongodbSystem$Companion { - public final fun client (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystem;)Lcom/mongodb/kotlin/client/coroutine/MongoClient; - public final fun filterById (Ljava/lang/String;)Lorg/bson/conversions/Bson; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions;Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions;Lkotlin/jvm/functions/Function1;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/bson/json/JsonWriterSettings;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions;Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions;Lkotlin/jvm/functions/Function1;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/bson/json/JsonWriterSettings;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions; - public final fun component3 ()Lkotlin/jvm/functions/Function1; - public final fun component4 ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun component5 ()Lorg/bson/json/JsonWriterSettings; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions;Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions;Lkotlin/jvm/functions/Function1;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/bson/json/JsonWriterSettings;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions;Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions;Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions;Lkotlin/jvm/functions/Function1;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lorg/bson/json/JsonWriterSettings;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/mongodb/MongodbSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getConfigureClient ()Lkotlin/jvm/functions/Function1; - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/mongodb/MongoContainerOptions; - public final fun getDatabaseOptions ()Lcom/trendyol/stove/testing/e2e/mongodb/DatabaseOptions; - public final fun getJsonWriterSettings ()Lorg/bson/json/JsonWriterSettings; - public final fun getSerde ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/ObjectIdDeserializer : com/fasterxml/jackson/databind/deser/std/StdDeserializer { - public fun ()V - public synthetic fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/lang/Object; - public fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Lorg/bson/types/ObjectId; -} - -public final class com/trendyol/stove/testing/e2e/mongodb/ObjectIdModule : com/fasterxml/jackson/databind/module/SimpleModule { - public fun ()V -} - -public final class com/trendyol/stove/testing/e2e/mongodb/ObjectIdSerializer : com/fasterxml/jackson/databind/ser/std/StdSerializer { - public fun ()V - public synthetic fun serialize (Ljava/lang/Object;Lcom/fasterxml/jackson/core/JsonGenerator;Lcom/fasterxml/jackson/databind/SerializerProvider;)V - public fun serialize (Lorg/bson/types/ObjectId;Lcom/fasterxml/jackson/core/JsonGenerator;Lcom/fasterxml/jackson/databind/SerializerProvider;)V -} - -public final class com/trendyol/stove/testing/e2e/mongodb/OptionsKt { - public static final fun mongodb-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun mongodb-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public class com/trendyol/stove/testing/e2e/mongodb/StoveMongoContainer : org/testcontainers/containers/MongoDBContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - -public final class com/trendyol/stove/testing/e2e/mongodb/StoveMongoJsonWriterSettings { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/mongodb/StoveMongoJsonWriterSettings; - public final fun getObjectIdAsString ()Lorg/bson/json/JsonWriterSettings; -} - diff --git a/lib/stove-testing-e2e-mongodb/build.gradle.kts b/lib/stove-testing-e2e-mongodb/build.gradle.kts index d614f695..899d56dc 100644 --- a/lib/stove-testing-e2e-mongodb/build.gradle.kts +++ b/lib/stove-testing-e2e-mongodb/build.gradle.kts @@ -8,10 +8,6 @@ dependencies { implementation(libs.kotlinx.core) } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.mongodb.Stove") -} - dependencies { - testImplementation(libs.logback.classic) + testImplementation(libs.slf4j.simple) } 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 new file mode 100644 index 00000000..80406c42 --- /dev/null +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/MongodbOptionsDsl.kt @@ -0,0 +1,44 @@ +package com.trendyol.stove.testing.e2e.mongodb + +import com.fasterxml.jackson.databind.ObjectMapper +import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl + +@StoveDsl +fun mongodb(init: MongodbOptionsDsl.() -> Unit): MongodbSystemOptions = MongodbOptionsDsl(init).invoke() + +@StoveDsl +class MongodbOptionsDsl internal constructor(private val init: MongodbOptionsDsl.() -> Unit) { + private var options: MongodbSystemOptions = MongodbSystemOptions(configureExposedConfiguration = { listOf() }) + + fun defaultDatabase(name: String) { + options = options.copy( + databaseOptions = options.databaseOptions.copy( + default = options.databaseOptions.default.copy(name = name) + ) + ) + } + + fun image(image: String) { + options = options.copy(container = options.container.copy(image = image)) + } + + fun registry(registry: String) { + options = options.copy(container = options.container.copy(registry = registry)) + } + + fun tag(tag: String) { + options = options.copy(container = options.container.copy(tag = tag)) + } + + fun exposedConfiguration(configureExposedConfiguration: (MongodbExposedConfiguration) -> List) { + 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 6656b852..f8672c47 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,5 +1,6 @@ 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 @@ -13,14 +14,13 @@ import org.bson.* import org.bson.conversions.Bson import org.bson.types.ObjectId import org.slf4j.* +import kotlin.collections.set @MongoDsl class MongodbSystem internal constructor( override val testSystem: TestSystem, val context: MongodbContext -) : PluggedSystem, - RunAware, - ExposesConfiguration { +) : PluggedSystem, RunAware, ExposesConfiguration { @PublishedApi internal lateinit var mongoClient: MongoClient private lateinit var exposedConfiguration: MongodbExposedConfiguration @@ -50,11 +50,11 @@ class MongodbSystem internal constructor( query: String, collection: String = context.options.databaseOptions.default.collection, assertion: (List) -> Unit - ): MongodbSystem = mongoClient - .getDatabase(context.options.databaseOptions.default.name) + ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) + .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(T::class).build()) } .getCollection(collection) .find(BsonDocument.parse(query)) - .map { context.options.serde.deserialize(it.toJson(context.options.jsonWriterSettings), T::class.java) } + .map { context.options.objectMapper.convertValue(it, T::class.java) } .toList() .also(assertion) .let { this } @@ -64,42 +64,21 @@ class MongodbSystem internal constructor( objectId: String, collection: String = context.options.databaseOptions.default.collection, assertion: (T) -> Unit - ): MongodbSystem = mongoClient - .getDatabase(context.options.databaseOptions.default.name) + ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) .getCollection(collection) + .let { it.withCodecRegistry(PojoRegistry(it.codecRegistry).register(T::class).build()) } .find(filterById(objectId)) - .map { context.options.serde.deserialize(it.toJson(context.options.jsonWriterSettings), T::class.java) } + .map { context.options.objectMapper.convertValue(it, T::class.java) } .first() .also(assertion) .let { this } - /** - * Saves the [instance] with given [objectId] to the [collection] - */ - @MongoDsl - 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) - .getCollection(collection) - .also { coll -> - context.options.serde - .serialize(instance) - .let { BsonDocument.parse(it) } - .let { doc -> Document(doc) } - .append(RESERVED_ID, ObjectId(objectId)) - .let { coll.insertOne(it) } - }.let { this } - @MongoDsl suspend fun shouldNotExist( objectId: String, collection: String = context.options.databaseOptions.default.collection ): MongodbSystem { - val exists = mongoClient - .getDatabase(context.options.databaseOptions.default.name) + val exists = mongoClient.getDatabase(context.options.databaseOptions.default.name) .getCollection(collection) .find(filterById(objectId)) .firstOrNull() != null @@ -113,10 +92,26 @@ class MongodbSystem internal constructor( suspend fun shouldDelete( objectId: String, collection: String = context.options.databaseOptions.default.collection - ): MongodbSystem = mongoClient - .getDatabase(context.options.databaseOptions.default.name) + ): MongodbSystem = mongoClient.getDatabase(context.options.databaseOptions.default.name) .getCollection(collection) - .deleteOne(filterById(objectId)) + .deleteOne(filterById(objectId)).let { this } + + /** + * Saves the [instance] with given [objectId] to the [collection] + */ + @MongoDsl + suspend 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()) } + .getCollection(collection) + .let { + val map = context.options.objectMapper.convertValue>(instance) + map[RESERVED_ID] = ObjectId(objectId) + it.insertOne(Document(map)) + } .let { this } /** @@ -147,18 +142,15 @@ class MongodbSystem internal constructor( private fun createClient( exposedConfiguration: MongodbExposedConfiguration - ): MongoClient = MongoClientSettings - .builder() + ): MongoClient = MongoClientSettings.builder() .applyConnectionString(ConnectionString(exposedConfiguration.connectionString)) .retryWrites(true) .readConcern(ReadConcern.MAJORITY) .writeConcern(WriteConcern.MAJORITY) - .apply(context.options.configureClient) - .build() - .let { MongoClient.create(it) } + .build().let { MongoClient.create(it) } companion object { - const val RESERVED_ID = "_id" + private 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 5d4f98b7..08fe83a1 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,34 +1,15 @@ package com.trendyol.stove.testing.e2e.mongodb -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.mongodb.MongoClientSettings -import com.trendyol.stove.testing.e2e.serialization.StoveSerde -import com.trendyol.stove.testing.e2e.system.abstractions.* +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.system.annotations.StoveDsl -import org.bson.json.JsonWriterSettings @StoveDsl data class MongodbSystemOptions( val databaseOptions: DatabaseOptions = DatabaseOptions(), val container: MongoContainerOptions = MongoContainerOptions(), - val configureClient: (MongoClientSettings.Builder) -> Unit = { }, - val serde: StoveSerde = StoveSerde.jackson.anyJsonStringSerde( - StoveSerde.jackson.byConfiguring { - disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - enable(MapperFeature.DEFAULT_VIEW_INCLUSION) - addModule(ObjectIdModule()) - addModule(KotlinModule.Builder().build()) - } - ), - val jsonWriterSettings: JsonWriterSettings = StoveMongoJsonWriterSettings.objectIdAsString, - override val configureExposedConfiguration: (MongodbExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration - -object StoveMongoJsonWriterSettings { - val objectIdAsString: JsonWriterSettings = JsonWriterSettings - .builder() - .objectIdConverter { value, writer -> writer.writeString(value.toHexString()) } - .build() -} + override val configureExposedConfiguration: (MongodbExposedConfiguration) -> List, + val objectMapper: ObjectMapper = StoveObjectMapper.byConfiguring { registerModule(ObjectIdModule()) } +) : SystemOptions, ConfiguresExposedConfiguration diff --git a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/Options.kt b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/Options.kt index ad1ad280..45ffaac2 100644 --- a/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/Options.kt +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/Options.kt @@ -24,8 +24,7 @@ data class MongodbContext( open class StoveMongoContainer( override val imageNameAccess: DockerImageName -) : MongoDBContainer(imageNameAccess), - StoveContainer +) : MongoDBContainer(imageNameAccess), StoveContainer @StoveDsl data class MongoContainerOptions( @@ -53,8 +52,7 @@ internal fun TestSystem.withMongodb(options: MongodbSystemOptions): TestSystem { options.container.registry, options.container.compatibleSubstitute ) { - options.container - .useContainerFn(it) + options.container.useContainerFn(it) .withReuse(this.options.keepDependenciesRunning) .let { c -> c as StoveMongoContainer } .apply(options.container.containerFn) @@ -73,6 +71,11 @@ internal fun TestSystem.mongodb(): MongodbSystem = @StoveDsl fun WithDsl.mongodb(configure: () -> MongodbSystemOptions): TestSystem = this.testSystem.withMongodb(configure()) +@StoveDsl +fun WithDsl.mongodbDsl( + configure: @StoveDsl MongodbOptionsDsl.() -> Unit +): TestSystem = this.testSystem.withMongodb(MongodbOptionsDsl(configure)()) + @StoveDsl suspend fun ValidationDsl.mongodb( validation: @MongoDsl suspend MongodbSystem.() -> Unit 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 new file mode 100644 index 00000000..78af08e4 --- /dev/null +++ b/lib/stove-testing-e2e-mongodb/src/main/kotlin/com/trendyol/stove/testing/e2e/mongodb/PojoRegistry.kt @@ -0,0 +1,24 @@ +package com.trendyol.stove.testing.e2e.mongodb + +import com.mongodb.reactivestreams.client.MongoClients.getDefaultCodecRegistry +import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import org.bson.codecs.configuration.CodecRegistries.* +import org.bson.codecs.configuration.CodecRegistry +import org.bson.codecs.pojo.* +import kotlin.reflect.KClass + +@StoveDsl +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) + + fun register(clazz: KClass): PojoRegistry = builder.register(clazz.java).let { this } + + fun build(): CodecRegistry = fromRegistries(registry, getDefaultCodecRegistry(), fromProviders(builder.build())) +} 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 21ad27fb..180db471 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 @@ -13,14 +13,12 @@ import org.bson.codecs.pojo.annotations.* import org.bson.types.ObjectId import org.junit.jupiter.api.assertThrows -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject() { TestSystem() .with { - mongodb { - MongodbSystemOptions { - listOf() - } + mongodbDsl { + tag("latest") } applicationUnderTest(NoOpApplication()) }.run() @@ -52,9 +50,11 @@ class NoOpApplication : ApplicationUnderTest { override suspend fun stop() = Unit } -class MongodbTestSystemTests : - FunSpec({ - data class ExampleInstanceWithObjectId( +class MongodbTestSystemTests : FunSpec({ + + data class ExampleInstanceWithObjectId + @BsonCreator + constructor( @BsonId @JsonAlias("_id") val id: ObjectId, @@ -62,195 +62,151 @@ class MongodbTestSystemTests : @BsonProperty("description") val description: String ) - data class ExampleInstanceWithStringObjectId( + data class ExampleInstanceWithStringObjectId + @BsonCreator + constructor( + @BsonId @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() - validate { - mongodb { - save( - ExampleInstanceWithObjectId( - id = id, - aggregateId = id.toHexString(), - description = testCase.name.testName - ), - id.toHexString() - ) - shouldGet(id.toHexString()) { actual -> - actual.aggregateId shouldBe id.toHexString() - actual.description shouldBe testCase.name.testName - } - } - } - } + test("should save and get with objectId") { - test("should save and get with string objectId") { - val id = ObjectId() - validate { - mongodb { - save( - ExampleInstanceWithStringObjectId( - id = id.toHexString(), - aggregateId = id.toHexString(), - description = testCase.name.testName - ), - id.toHexString() - ) - shouldGet(id.toHexString()) { actual -> - actual.aggregateId shouldBe id.toHexString() - actual.description shouldBe testCase.name.testName - } + val id = ObjectId() + TestSystem.validate { + mongodb { + save( + ExampleInstanceWithObjectId( + id = id, + aggregateId = id.toHexString(), + description = testCase.name.testName + ), + id.toHexString() + ) + shouldGet(id.toHexString()) { actual -> + actual.aggregateId shouldBe id.toHexString() + actual.description shouldBe testCase.name.testName } } } + } - 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" - validate { - mongodb { - save( - ExampleInstanceWithObjectId( - id = id1, - aggregateId = id1.toHexString(), - description = firstDesc - ), - id1.toHexString() - ) - save( - ExampleInstanceWithObjectId( - id = id2, - aggregateId = id2.toHexString(), - description = secondDesc - ), - id2.toHexString() - ) - save( - ExampleInstanceWithObjectId( - id = id3, - aggregateId = id3.toHexString(), - description = secondDesc - ), - id3.toHexString() - ) - shouldQuery("{\"description\": \"$secondDesc\"}") { actual -> - actual.count() shouldBe 2 - actual.forAny { it.id shouldBe id2.toHexString() } - actual.forAny { it.id shouldBe id3.toHexString() } - } - shouldQuery("{\"description\": \"$firstDesc\"}") { actual -> - actual.count() shouldBe 1 - actual.first().id shouldBe id1 - } + test("should save and get with string objectId") { + val id = ObjectId() + TestSystem.validate { + mongodb { + save( + ExampleInstanceWithStringObjectId( + id = id.toHexString(), + aggregateId = id.toHexString(), + description = testCase.name.testName + ), + id.toHexString() + ) + shouldGet(id.toHexString()) { actual -> + actual.aggregateId shouldBe id.toHexString() + actual.description shouldBe testCase.name.testName } } } + } - test("should throw assertion error when document does exist") { - val id1 = ObjectId() - validate { - mongodb { - save( - ExampleInstanceWithObjectId( - id = id1, - aggregateId = id1.toHexString(), - description = testCase.name.testName + "1" - ), - id1.toHexString() - ) - shouldGet(id1.toHexString()) { actual -> - actual.aggregateId shouldBe id1.toHexString() - } - assertThrows { shouldNotExist(id1.toHexString()) } + 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 { + mongodb { + save( + ExampleInstanceWithObjectId( + id = id1, + aggregateId = id1.toHexString(), + description = firstDesc + ), + id1.toHexString() + ) + save( + ExampleInstanceWithObjectId( + id = id2, + aggregateId = id2.toHexString(), + description = secondDesc + ), + id2.toHexString() + ) + save( + ExampleInstanceWithObjectId( + id = id3, + aggregateId = id3.toHexString(), + description = secondDesc + ), + id3.toHexString() + ) + shouldQuery("{\"description\": \"$secondDesc\"}") { actual -> + actual.count() shouldBe 2 + actual.forAny { it.id shouldBe id2 } + actual.forAny { it.id shouldBe id3 } + } + shouldQuery("{\"description\": \"$firstDesc\"}") { actual -> + actual.count() shouldBe 1 + actual.first().id shouldBe id1 } } } + } - test("should not throw exception when given does not exist id") { - val notExistDocId = ObjectId() - validate { - mongodb { - shouldNotExist(notExistDocId.toHexString()) + test("should throw assertion error when document does exist") { + val id1 = ObjectId() + TestSystem.validate { + mongodb { + save( + ExampleInstanceWithObjectId( + id = id1, + aggregateId = id1.toHexString(), + description = testCase.name.testName + "1" + ), + id1.toHexString() + ) + shouldGet(id1.toHexString()) { actual -> + actual.aggregateId shouldBe id1.toHexString() } + assertThrows { shouldNotExist(id1.toHexString()) } } } + } - test("should delete") { - val id = ObjectId() - validate { - mongodb { - save( - ExampleInstanceWithObjectId( - id = id, - aggregateId = id.toHexString(), - description = testCase.name.testName - ), - id.toHexString() - ) - shouldQuery("{\"aggregateId\": \"${id.toHexString()}\"}") { actual -> - actual.size shouldBe 1 - } - shouldDelete(id.toHexString()) - shouldQuery("{\"aggregateId\": \"${id.toHexString()}\"}") { actual -> - actual.size shouldBe 0 - } - } + test("should not throw exception when given does not exist id") { + val notExistDocId = ObjectId() + TestSystem.validate { + mongodb { + shouldNotExist(notExistDocId.toHexString()) } } + } - test("complex type") { - data class Nested( - val id: String, - val name: String - ) - - data class ComplexType( - val id: String, - val name: String, - val nested: Nested - ) - - val id = ObjectId() - val nestedId = ObjectId() - validate { - mongodb { - save( - ComplexType( - id = id.toHexString(), - name = "name", - nested = Nested( - id = nestedId.toHexString(), - name = "nested" - ) - ), - id.toHexString() - ) - shouldGet(id.toHexString()) { actual -> - actual.id shouldBe id.toHexString() - actual.name shouldBe "name" - actual.nested.id shouldBe actual.nested.id - actual.nested.name shouldBe "nested" - } - - shouldQuery( - query = "{\"nested.id\": \"${nestedId.toHexString()}\"}" - ) { actual -> - actual.size shouldBe 1 - actual.first().id shouldBe id.toHexString() - } + test("should delete") { + val id = ObjectId() + TestSystem.validate { + mongodb { + save( + ExampleInstanceWithObjectId( + id = id, + aggregateId = id.toHexString(), + description = testCase.name.testName + ), + id.toHexString() + ) + shouldQuery("{\"aggregateId\": \"${id.toHexString()}\"}") { actual -> + actual.size shouldBe 1 + } + shouldDelete(id.toHexString()) + shouldQuery("{\"aggregateId\": \"${id.toHexString()}\"}") { actual -> + actual.size shouldBe 0 } } } - }) + } +}) diff --git a/lib/stove-testing-e2e-mongodb/src/test/resources/logback-test.xml b/lib/stove-testing-e2e-mongodb/src/test/resources/logback-test.xml deleted file mode 100644 index a1e9ff6e..00000000 --- a/lib/stove-testing-e2e-mongodb/src/test/resources/logback-test.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - %white([%t]) %highlight(%-5level) %magenta(%c{1}) %cyan(trace.id:%X{traceId} version:%X{version}) - - %yellow(%m) %n - - - - - - - - - - - - - diff --git a/lib/stove-testing-e2e-rdbms-mssql/api/stove-testing-e2e-rdbms-mssql.api b/lib/stove-testing-e2e-rdbms-mssql/api/stove-testing-e2e-rdbms-mssql.api deleted file mode 100644 index 651b1e61..00000000 --- a/lib/stove-testing-e2e-rdbms-mssql/api/stove-testing-e2e-rdbms-mssql.api +++ /dev/null @@ -1,126 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlContext : com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseContext { - public fun (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/StoveMsSqlContainer;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions;)V - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getApplicationName ()Ljava/lang/String; - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions; - public final fun getDatabaseName ()Ljava/lang/String; - public final fun getMigrationCollection ()Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; - public final fun getPassword ()Ljava/lang/String; - public final fun getUserName ()Ljava/lang/String; - public fun hashCode ()I - public final fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptionsKt { - public static final fun mssql-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun mssql-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlSystem : com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem { - public fun close ()V - public fun configuration ()Ljava/util/List; - public final fun ops (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlSystem; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun component7 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MssqlContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public final fun getToolsPath ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/SqlMigrationContext { - public fun (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - public final fun component3 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/SqlMigrationContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/SqlMigrationContext;Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/SqlMigrationContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getExecuteAsRoot ()Lkotlin/jvm/functions/Function2; - public final fun getOperations ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class com/trendyol/stove/testing/e2e/rdbms/mssql/StoveMsSqlContainer : org/testcontainers/containers/MSSQLServerContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - -public abstract class com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath { - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getPath ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$After2019 : com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$After2019; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Before2019 : com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Before2019; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Custom : com/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Custom; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Custom;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/rdbms/mssql/ToolsPath$Custom; - public fun equals (Ljava/lang/Object;)Z - public fun getPath ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - diff --git a/lib/stove-testing-e2e-rdbms-mssql/build.gradle.kts b/lib/stove-testing-e2e-rdbms-mssql/build.gradle.kts index 7bf8f3e9..63386295 100644 --- a/lib/stove-testing-e2e-rdbms-mssql/build.gradle.kts +++ b/lib/stove-testing-e2e-rdbms-mssql/build.gradle.kts @@ -3,7 +3,3 @@ dependencies { api(libs.testcontainers.mssql) api(libs.microsoft.sqlserver.jdbc) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.rdbms.mssql.Stove") -} diff --git a/lib/stove-testing-e2e-rdbms-mssql/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions.kt b/lib/stove-testing-e2e-rdbms-mssql/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions.kt index 5c3df531..3bfef6b3 100644 --- a/lib/stove-testing-e2e-rdbms-mssql/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions.kt +++ b/lib/stove-testing-e2e-rdbms-mssql/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MsSqlOptions.kt @@ -10,24 +10,19 @@ import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl import org.testcontainers.containers.MSSQLServerContainer import org.testcontainers.utility.DockerImageName -sealed class ToolsPath( - open val path: String -) { +sealed class ToolsPath(open val path: String) { data object Before2019 : ToolsPath("mssql-tools") data object After2019 : ToolsPath("mssql-tools18") - data class Custom( - override val path: String - ) : ToolsPath(path) + data class Custom(override val path: String) : ToolsPath(path) override fun toString(): String = path } open class StoveMsSqlContainer( override val imageNameAccess: DockerImageName -) : MSSQLServerContainer(imageNameAccess), - StoveContainer +) : MSSQLServerContainer(imageNameAccess), StoveContainer data class MssqlContainerOptions( override val registry: String = DEFAULT_REGISTRY, @@ -52,8 +47,7 @@ data class MsSqlOptions( override val configureExposedConfiguration: ( RelationalDatabaseExposedConfiguration ) -> List -) : SystemOptions, - ConfiguresExposedConfiguration { +) : SystemOptions, ConfiguresExposedConfiguration { val migrationCollection: MigrationCollection = MigrationCollection() /** @@ -91,8 +85,7 @@ internal fun TestSystem.withMsSql(options: MsSqlOptions): TestSystem = options.container.registry, options.container.compatibleSubstitute ) { - options.container - .useContainerFn(it) + options.container.useContainerFn(it) .acceptLicense() .withEnv("MSSQL_USER", options.userName) .withEnv("MSSQL_SA_PASSWORD", options.password) diff --git a/lib/stove-testing-e2e-rdbms-mssql/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MssqlSystemTest.kt b/lib/stove-testing-e2e-rdbms-mssql/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MssqlSystemTest.kt index 1201bcde..5930a8ed 100644 --- a/lib/stove-testing-e2e-rdbms-mssql/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MssqlSystemTest.kt +++ b/lib/stove-testing-e2e-rdbms-mssql/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/mssql/MssqlSystemTest.kt @@ -10,30 +10,31 @@ import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import org.slf4j.* -class Stove : AbstractProjectConfig() { - override suspend fun beforeProject(): Unit = TestSystem() - .with { - mssql { - MsSqlOptions( - applicationName = "test", - databaseName = "test", - userName = "sa", - password = "Password12!", - container = MssqlContainerOptions( - toolsPath = ToolsPath.After2019 - ) { - dockerImageName = "mcr.microsoft.com/mssql/server:2022-latest" - withStartupAttempts(3) - }, - configureExposedConfiguration = { _ -> - listOf() +class Setup : AbstractProjectConfig() { + override suspend fun beforeProject(): Unit = + TestSystem() + .with { + mssql { + MsSqlOptions( + applicationName = "test", + databaseName = "test", + userName = "sa", + password = "Password12!", + container = MssqlContainerOptions( + toolsPath = ToolsPath.After2019 + ) { + dockerImageName = "mcr.microsoft.com/mssql/server:2022-latest" + withStartupAttempts(3) + }, + configureExposedConfiguration = { _ -> + listOf() + } + ).migrations { + register() } - ).migrations { - register() } - } - applicationUnderTest(NoOpApplication()) - }.run() + applicationUnderTest(NoOpApplication()) + }.run() override suspend fun afterProject(): Unit = TestSystem.stop() } @@ -78,42 +79,41 @@ data class Person( val city: String ) -class MssqlSystemTests : - ShouldSpec({ - should("work") { - validate { - mssql { - ops { - val result = select("SELECT 1") { - it.getInt(1) - } - result.first() shouldBe 1 +class MssqlSystemTests : ShouldSpec({ + should("work") { + validate { + mssql { + ops { + val result = select("SELECT 1") { + it.getInt(1) } + result.first() shouldBe 1 + } - shouldExecute("insert into Person values (1, 'Doe', 'John', '123 Main St', 'Springfield')") + shouldExecute("insert into Person values (1, 'Doe', 'John', '123 Main St', 'Springfield')") - shouldQuery( - query = "select * from Person", - mapper = { - Person( - it.getInt(1), - it.getString(2), - it.getString(3), - it.getString(4), - it.getString(5) - ) - } - ) { result -> - result.size shouldBe 1 - result.first().apply { - personId shouldBe 1 - lastName shouldBe "Doe" - firstName shouldBe "John" - address shouldBe "123 Main St" - city shouldBe "Springfield" - } + shouldQuery( + query = "select * from Person", + mapper = { + Person( + it.getInt(1), + it.getString(2), + it.getString(3), + it.getString(4), + it.getString(5) + ) + } + ) { result -> + result.size shouldBe 1 + result.first().apply { + personId shouldBe 1 + lastName shouldBe "Doe" + firstName shouldBe "John" + address shouldBe "123 Main St" + city shouldBe "Springfield" } } } } - }) + } +}) diff --git a/lib/stove-testing-e2e-rdbms-postgres/api/stove-testing-e2e-rdbms-postgres.api b/lib/stove-testing-e2e-rdbms-postgres/api/stove-testing-e2e-rdbms-postgres.api deleted file mode 100644 index f1ee7a8f..00000000 --- a/lib/stove-testing-e2e-rdbms-postgres/api/stove-testing-e2e-rdbms-postgres.api +++ /dev/null @@ -1,83 +0,0 @@ -public final class com/trendol/stove/testing/e2e/rdbms/postgres/OptionsKt { - public static final field DEFAULT_POSTGRES_IMAGE_NAME Ljava/lang/String; - public static final fun postgresql-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun postgresql-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendol/stove/testing/e2e/rdbms/postgres/PostgresSqlMigrationContext { - public fun (Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - public final fun component3 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresSqlMigrationContext; - public static synthetic fun copy$default (Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresSqlMigrationContext;Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions;Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresSqlMigrationContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getExecuteAsRoot ()Lkotlin/jvm/functions/Function2; - public final fun getOperations ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - public final fun getOptions ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions; - public static synthetic fun copy$default (Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions;Lkotlin/jvm/functions/Function1;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions; - public static synthetic fun copy$default (Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlContainerOptions; - public final fun getDatabaseName ()Ljava/lang/String; - public final fun getMigrationCollection ()Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; - public final fun getPassword ()Ljava/lang/String; - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public final fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlOptions; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem : com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem { - public final fun pause ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun unpause ()Lcom/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem; -} - -public class com/trendol/stove/testing/e2e/rdbms/postgres/StovePostgresqlContainer : org/testcontainers/containers/PostgreSQLContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - diff --git a/lib/stove-testing-e2e-rdbms-postgres/build.gradle.kts b/lib/stove-testing-e2e-rdbms-postgres/build.gradle.kts index 4209fb63..1cda44fc 100644 --- a/lib/stove-testing-e2e-rdbms-postgres/build.gradle.kts +++ b/lib/stove-testing-e2e-rdbms-postgres/build.gradle.kts @@ -4,7 +4,3 @@ dependencies { api(libs.postgresql) testImplementation(libs.logback.classic) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.rdbms.postgres.Stove") -} diff --git a/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/Options.kt b/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/Options.kt index 1598855c..f7d57019 100644 --- a/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/Options.kt +++ b/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/Options.kt @@ -14,8 +14,7 @@ const val DEFAULT_POSTGRES_IMAGE_NAME = "postgres" open class StovePostgresqlContainer( override val imageNameAccess: DockerImageName -) : PostgreSQLContainer(imageNameAccess), - StoveContainer +) : PostgreSQLContainer(imageNameAccess), StoveContainer data class PostgresqlContainerOptions( override val registry: String = DEFAULT_REGISTRY, @@ -35,8 +34,7 @@ data class PostgresqlOptions( override val configureExposedConfiguration: ( RelationalDatabaseExposedConfiguration ) -> List -) : SystemOptions, - ConfiguresExposedConfiguration { +) : SystemOptions, ConfiguresExposedConfiguration { val migrationCollection: MigrationCollection = MigrationCollection() @StoveDsl @@ -62,8 +60,7 @@ data class PostgresSqlMigrationContext( internal fun TestSystem.withPostgresql(options: PostgresqlOptions): TestSystem = withProvidedRegistry(options.container.imageWithTag, options.container.registry, options.container.compatibleSubstitute) { - options.container - .useContainerFn(it) + options.container.useContainerFn(it) .withDatabaseName(options.databaseName) .withUsername(options.username) .withPassword(options.password) diff --git a/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem.kt b/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem.kt index c1f9162a..e30a9067 100644 --- a/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem.kt +++ b/lib/stove-testing-e2e-rdbms-postgres/src/main/kotlin/com/trendol/stove/testing/e2e/rdbms/postgres/PostgresqlSystem.kt @@ -13,14 +13,13 @@ class PostgresqlSystem internal constructor( override suspend fun run() { super.run() val executeAsRoot = { sql: String -> - postgresContext.container - .execInContainer( - "/bin/bash", - "-c", - "psql -U ${postgresContext.container.username} -d ${postgresContext.container.databaseName} -c \"$sql\"" - ).let { - check(it.exitCode == 0) { "Failed to execute sql: $sql, reason: ${it.stderr}" } - } + postgresContext.container.execInContainer( + "/bin/bash", + "-c", + "psql -U ${postgresContext.container.username} -d ${postgresContext.container.databaseName} -c \"$sql\"" + ).let { + check(it.exitCode == 0) { "Failed to execute sql: $sql, reason: ${it.stderr}" } + } } postgresContext.options.migrationCollection.run( PostgresSqlMigrationContext( diff --git a/lib/stove-testing-e2e-rdbms-postgres/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/postgres/PostgresqlSystemTest.kt b/lib/stove-testing-e2e-rdbms-postgres/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/postgres/PostgresqlSystemTest.kt index 255b2d25..c5b5dddb 100644 --- a/lib/stove-testing-e2e-rdbms-postgres/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/postgres/PostgresqlSystemTest.kt +++ b/lib/stove-testing-e2e-rdbms-postgres/src/test/kotlin/com/trendyol/stove/testing/e2e/rdbms/postgres/PostgresqlSystemTest.kt @@ -10,7 +10,7 @@ import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import org.slf4j.* -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { @@ -58,84 +58,83 @@ class NoOpApplication : ApplicationUnderTest { override suspend fun stop() = Unit } -class PostgresqlSystemTests : - FunSpec({ +class PostgresqlSystemTests : FunSpec({ - data class IdAndDescription( - val id: Long, - val description: String - ) + data class IdAndDescription( + val id: Long, + val description: String + ) - test("initial migration should work") { - TestSystem.validate { - postgresql { - shouldQuery( - "SELECT * FROM MigrationHistory", - mapper = { row -> - IdAndDescription(row.getLong("id"), row.getString("description")) - } - ) { actual -> - actual.size shouldBeGreaterThan 0 - actual.first() shouldBe IdAndDescription(1, "InitialMigration") + test("initial migration should work") { + TestSystem.validate { + postgresql { + shouldQuery( + "SELECT * FROM MigrationHistory", + mapper = { row -> + IdAndDescription(row.getLong("id"), row.getString("description")) } + ) { actual -> + actual.size shouldBeGreaterThan 0 + actual.first() shouldBe IdAndDescription(1, "InitialMigration") } } } + } - test("should save and get with immutable data class") { - TestSystem.validate { - postgresql { - shouldExecute( - """ + test("should save and get with immutable data class") { + TestSystem.validate { + postgresql { + shouldExecute( + """ DROP TABLE IF EXISTS Dummies; CREATE TABLE IF NOT EXISTS Dummies ( id serial PRIMARY KEY, description VARCHAR (50) NOT NULL ); - """.trimIndent() - ) - shouldExecute("INSERT INTO Dummies (description) VALUES ('${testCase.name.testName}')") - shouldQuery("SELECT * FROM Dummies", mapper = { - IdAndDescription(it.getLong("id"), it.getString("description")) - }) { actual -> - actual.size shouldBeGreaterThan 0 - actual.first().description shouldBe testCase.name.testName - } + """.trimIndent() + ) + shouldExecute("INSERT INTO Dummies (description) VALUES ('${testCase.name.testName}')") + shouldQuery("SELECT * FROM Dummies", mapper = { + IdAndDescription(it.getLong("id"), it.getString("description")) + }) { actual -> + actual.size shouldBeGreaterThan 0 + actual.first().description shouldBe testCase.name.testName } } } + } - class NullableIdAndDescription { - var id: Long? = null - var description: String? = null - } + class NullableIdAndDescription { + var id: Long? = null + var description: String? = null + } - test("should save and get with mutable class") { - TestSystem.validate { - postgresql { - shouldExecute( - """ + test("should save and get with mutable class") { + TestSystem.validate { + postgresql { + shouldExecute( + """ DROP TABLE IF EXISTS Dummies; CREATE TABLE IF NOT EXISTS Dummies ( id serial PRIMARY KEY, description VARCHAR (50) NOT NULL ); - """.trimIndent() - ) - shouldExecute("INSERT INTO Dummies (description) VALUES ('${testCase.name.testName}')") - shouldQuery( - "SELECT * FROM Dummies", - mapper = { row -> - val result = NullableIdAndDescription() - result.id = row.getLong("id") - result.description = row.getString("description") - result - } - ) { actual -> - actual.size shouldBeGreaterThan 0 - actual.first().description shouldBe testCase.name.testName + """.trimIndent() + ) + shouldExecute("INSERT INTO Dummies (description) VALUES ('${testCase.name.testName}')") + shouldQuery( + "SELECT * FROM Dummies", + mapper = { row -> + val result = NullableIdAndDescription() + result.id = row.getLong("id") + result.description = row.getString("description") + result } + ) { actual -> + actual.size shouldBeGreaterThan 0 + actual.first().description shouldBe testCase.name.testName } } } - }) + } +}) diff --git a/lib/stove-testing-e2e-rdbms/api/stove-testing-e2e-rdbms.api b/lib/stove-testing-e2e-rdbms/api/stove-testing-e2e-rdbms.api deleted file mode 100644 index b39ee92f..00000000 --- a/lib/stove-testing-e2e-rdbms/api/stove-testing-e2e-rdbms.api +++ /dev/null @@ -1,60 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations : java/lang/AutoCloseable { - public fun (Lorg/jetbrains/exposed/sql/Database;)V - public fun close ()V - public final fun execute (Ljava/lang/String;Ljava/util/Map;)I - public static synthetic fun execute$default (Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)I - public final fun select (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract class com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseContext { - public fun (Lorg/testcontainers/containers/JdbcDatabaseContainer;Lkotlin/jvm/functions/Function1;)V - public final fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lorg/testcontainers/containers/JdbcDatabaseContainer; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()I - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getHost ()Ljava/lang/String; - public final fun getJdbcUrl ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getPort ()I - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem : com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem$Companion; - protected field exposedConfiguration Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration; - protected field sqlOperations Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - protected fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseContext;)V - public fun close ()V - public fun configuration ()Ljava/util/List; - protected abstract fun database (Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration;)Lorg/jetbrains/exposed/sql/Database; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - protected final fun getContext ()Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseContext; - protected final fun getExposedConfiguration ()Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration; - public final fun getInternalSqlOperations ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - protected final fun getSqlOperations ()Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; - public final fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - protected final fun setExposedConfiguration (Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseExposedConfiguration;)V - public final fun setInternalSqlOperations (Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;)V - protected final fun setSqlOperations (Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations;)V - public final fun shouldExecute (Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem$Companion { - public final fun operations (Lcom/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem;)Lcom/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations; -} - diff --git a/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations.kt b/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations.kt index a9d0964a..f634f972 100644 --- a/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations.kt +++ b/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/NativeSqlOperations.kt @@ -4,9 +4,7 @@ import kotlinx.coroutines.flow.* import org.jetbrains.exposed.sql.* import java.sql.ResultSet -class NativeSqlOperations( - database: Database -) : AutoCloseable { +class NativeSqlOperations(database: Database) : AutoCloseable { private val connection = database.connector() fun execute(sql: String, parameters: Map, Any> = mapOf()): Int = connection @@ -14,7 +12,8 @@ class NativeSqlOperations( .apply { timeout = Int.MAX_VALUE fillParameters(parameters.map { it.key to it.value }) - }.executeUpdate() + } + .executeUpdate() suspend fun select( sql: String, diff --git a/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem.kt b/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem.kt index bd6a4be0..e274a0ae 100644 --- a/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem.kt +++ b/lib/stove-testing-e2e-rdbms/src/main/kotlin/com/trendyol/stove/testing/e2e/rdbms/RelationalDatabaseSystem.kt @@ -14,9 +14,7 @@ import java.sql.ResultSet abstract class RelationalDatabaseSystem> protected constructor( final override val testSystem: TestSystem, protected val context: RelationalDatabaseContext<*> -) : PluggedSystem, - RunAware, - ExposesConfiguration { +) : PluggedSystem, RunAware, ExposesConfiguration { private val logger: Logger = LoggerFactory.getLogger(javaClass) protected lateinit var exposedConfiguration: RelationalDatabaseExposedConfiguration diff --git a/lib/stove-testing-e2e-redis/api/stove-testing-e2e-redis.api b/lib/stove-testing-e2e-redis/api/stove-testing-e2e-redis.api deleted file mode 100644 index 76415b32..00000000 --- a/lib/stove-testing-e2e-redis/api/stove-testing-e2e-redis.api +++ /dev/null @@ -1,107 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/redis/RedisContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisContext { - public fun (Lcom/trendyol/stove/testing/e2e/redis/StoveRedisContainer;Lcom/trendyol/stove/testing/e2e/redis/RedisOptions;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/redis/StoveRedisContainer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/redis/RedisOptions; - public final fun copy (Lcom/trendyol/stove/testing/e2e/redis/StoveRedisContainer;Lcom/trendyol/stove/testing/e2e/redis/RedisOptions;)Lcom/trendyol/stove/testing/e2e/redis/RedisContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/redis/RedisContext;Lcom/trendyol/stove/testing/e2e/redis/StoveRedisContainer;Lcom/trendyol/stove/testing/e2e/redis/RedisOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/redis/RedisContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/redis/StoveRedisContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/redis/RedisOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/redis/RedisExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/redis/RedisExposedConfiguration;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/redis/RedisExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getDatabase ()Ljava/lang/String; - public final fun getHost ()Ljava/lang/String; - public final fun getPassword ()Ljava/lang/String; - public final fun getPort ()I - public final fun getRedisUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun (ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()I - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions; - public final fun component4 ()Lkotlin/jvm/functions/Function1; - public final fun copy (ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/redis/RedisOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/redis/RedisOptions;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/redis/RedisOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/redis/RedisContainerOptions; - public final fun getDatabase ()I - public final fun getPassword ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisOptionsKt { - public static final fun redis-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun redis-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisSystem : com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public static final field Companion Lcom/trendyol/stove/testing/e2e/redis/RedisSystem$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/redis/RedisContext;)V - public fun close ()V - public fun configuration ()Ljava/util/List; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/redis/RedisSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/redis/RedisSystem; -} - -public final class com/trendyol/stove/testing/e2e/redis/RedisSystem$Companion { - public final fun client (Lcom/trendyol/stove/testing/e2e/redis/RedisSystem;)Lio/lettuce/core/RedisClient; -} - -public class com/trendyol/stove/testing/e2e/redis/StoveRedisContainer : com/redis/testcontainers/RedisContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - diff --git a/lib/stove-testing-e2e-redis/build.gradle.kts b/lib/stove-testing-e2e-redis/build.gradle.kts index 5a376489..0f0b385e 100644 --- a/lib/stove-testing-e2e-redis/build.gradle.kts +++ b/lib/stove-testing-e2e-redis/build.gradle.kts @@ -3,8 +3,3 @@ dependencies { api(libs.lettuce.core) api(libs.testcontainers.redis) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.redis.Stove") -} - diff --git a/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisOptions.kt b/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisOptions.kt index c5f5f03c..50c62d08 100644 --- a/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisOptions.kt +++ b/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisOptions.kt @@ -10,8 +10,7 @@ import org.testcontainers.utility.DockerImageName open class StoveRedisContainer( override val imageNameAccess: DockerImageName -) : RedisContainer(imageNameAccess), - StoveContainer +) : RedisContainer(imageNameAccess), StoveContainer data class RedisContainerOptions( override val registry: String = DEFAULT_REGISTRY, @@ -28,8 +27,7 @@ data class RedisOptions( val password: String = "password", val container: RedisContainerOptions = RedisContainerOptions(), override val configureExposedConfiguration: (RedisExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration +) : SystemOptions, ConfiguresExposedConfiguration @StoveDsl data class RedisExposedConfiguration( @@ -61,8 +59,7 @@ internal fun TestSystem.redis(): RedisSystem = internal fun TestSystem.withRedis(options: RedisOptions): TestSystem = withProvidedRegistry(options.container.image, options.container.registry, options.container.compatibleSubstitute) { - options.container - .useContainerFn(it) + options.container.useContainerFn(it) .withCommand("redis-server", "--requirepass", options.password) .withReuse(this.options.keepDependenciesRunning) .let { c -> c as StoveRedisContainer } diff --git a/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystem.kt b/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystem.kt index 2b8dca02..b61cf19a 100644 --- a/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystem.kt +++ b/lib/stove-testing-e2e-redis/src/main/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystem.kt @@ -13,9 +13,7 @@ import reactor.core.publisher.Mono class RedisSystem( override val testSystem: TestSystem, private val context: RedisContext -) : PluggedSystem, - RunAware, - ExposesConfiguration { +) : PluggedSystem, RunAware, ExposesConfiguration { private lateinit var client: RedisClient private lateinit var exposedConfiguration: RedisExposedConfiguration private val logger: Logger = LoggerFactory.getLogger(javaClass) @@ -35,17 +33,16 @@ class RedisSystem( ) } client = RedisClient.create( - RedisURI - .create( - exposedConfiguration.host, - exposedConfiguration.port - ).apply { - setCredentialsProvider { - Mono.just( - RedisCredentials.just(null, exposedConfiguration.password) - ) - } + RedisURI.create( + exposedConfiguration.host, + exposedConfiguration.port + ).apply { + setCredentialsProvider { + Mono.just( + RedisCredentials.just(null, exposedConfiguration.password) + ) } + } ) } diff --git a/lib/stove-testing-e2e-redis/src/test/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystemTests.kt b/lib/stove-testing-e2e-redis/src/test/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystemTests.kt index 48468be1..a04e7a43 100644 --- a/lib/stove-testing-e2e-redis/src/test/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystemTests.kt +++ b/lib/stove-testing-e2e-redis/src/test/kotlin/com/trendyol/stove/testing/e2e/redis/RedisSystemTests.kt @@ -8,7 +8,7 @@ import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import kotlinx.coroutines.future.await -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { @@ -30,18 +30,13 @@ class NoOpApplication : ApplicationUnderTest { override suspend fun stop() = Unit } -class RedisSystemTests : - ShouldSpec({ +class RedisSystemTests : ShouldSpec({ - should("work") { - TestSystem.validate { - redis { - client() - .connect() - .async() - .ping() - .await() shouldBe "PONG" - } + should("work") { + TestSystem.validate { + redis { + client().connect().async().ping().await() shouldBe "PONG" } } - }) + } +}) diff --git a/lib/stove-testing-e2e-wiremock/api/stove-testing-e2e-wiremock.api b/lib/stove-testing-e2e-wiremock/api/stove-testing-e2e-wiremock.api deleted file mode 100644 index af6e042e..00000000 --- a/lib/stove-testing-e2e-wiremock/api/stove-testing-e2e-wiremock.api +++ /dev/null @@ -1,116 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/wiremock/ExtensionsKt { - public static final fun containsKey (Lcom/github/benmanes/caffeine/cache/Cache;Ljava/lang/Object;)Z -} - -public final class com/trendyol/stove/testing/e2e/wiremock/OptionsKt { - public static final fun wiremock-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun wiremock-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/StubBehaviourBuilder { - public fun (Lcom/github/tomakehurst/wiremock/WireMockServer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public final fun initially (Lkotlin/jvm/functions/Function0;)V - public final fun then (Lkotlin/jvm/functions/Function0;)V -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockContext { - public fun (IZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lkotlin/jvm/functions/Function1;)V - public final fun component1 ()I - public final fun component2 ()Z - public final fun component3 ()Lkotlin/jvm/functions/Function2; - public final fun component4 ()Lkotlin/jvm/functions/Function2; - public final fun component5 ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (IZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockContext;IZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getAfterRequest ()Lkotlin/jvm/functions/Function2; - public final fun getAfterStubRemoved ()Lkotlin/jvm/functions/Function2; - public final fun getConfigure ()Lkotlin/jvm/functions/Function1; - public final fun getPort ()I - public final fun getRemoveStubAfterRequestMatched ()Z - public final fun getSerde ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockRequestListener : com/github/tomakehurst/wiremock/extension/ServeEventListener { - public fun (Lcom/github/benmanes/caffeine/cache/Cache;Lkotlin/jvm/functions/Function2;)V - public fun beforeResponseSent (Lcom/github/tomakehurst/wiremock/stubbing/ServeEvent;Lcom/github/tomakehurst/wiremock/extension/Parameters;)V - public fun getName ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockSystem : com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunAware, com/trendyol/stove/testing/e2e/system/abstractions/ValidatedSystem { - public static final field Companion Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/wiremock/WireMockContext;)V - public final fun behaviourFor (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V - public fun close ()V - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun mockDelete (Ljava/lang/String;ILjava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockDelete$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILjava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockDeleteConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockDeleteConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockGet (Ljava/lang/String;ILarrow/core/Option;Ljava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockGet$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILarrow/core/Option;Ljava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockGetConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockGetConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockHead (Ljava/lang/String;ILjava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockHead$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILjava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockHeadConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockHeadConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPatch (Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPatch$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPatchConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPatchConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPost (Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPost$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPostConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPostConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPut (Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPut$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;ILarrow/core/Option;Larrow/core/Option;Ljava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public final fun mockPutConfigure (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public static synthetic fun mockPutConfigure$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun validate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockSystem$Companion { - public final fun server (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystem;)Lcom/github/tomakehurst/wiremock/WireMockServer; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public fun ()V - public fun (ILkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;)V - public synthetic fun (ILkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()I - public final fun component2 ()Lkotlin/jvm/functions/Function1; - public final fun component3 ()Z - public final fun component4 ()Lkotlin/jvm/functions/Function2; - public final fun component5 ()Lkotlin/jvm/functions/Function2; - public final fun component6 ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun copy (ILkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystemOptions;ILkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/wiremock/WireMockSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getAfterRequest ()Lkotlin/jvm/functions/Function2; - public final fun getAfterStubRemoved ()Lkotlin/jvm/functions/Function2; - public final fun getConfigure ()Lkotlin/jvm/functions/Function1; - public final fun getPort ()I - public final fun getRemoveStubAfterRequestMatched ()Z - public final fun getSerde ()Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/wiremock/WireMockVacuumCleaner : com/github/tomakehurst/wiremock/extension/ServeEventListener { - public fun (Lcom/github/benmanes/caffeine/cache/Cache;Lkotlin/jvm/functions/Function2;)V - public fun beforeResponseSent (Lcom/github/tomakehurst/wiremock/stubbing/ServeEvent;Lcom/github/tomakehurst/wiremock/extension/Parameters;)V - public fun getName ()Ljava/lang/String; - public final fun wireMock (Lcom/github/tomakehurst/wiremock/WireMockServer;)V -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/wiremock/WiremockDsl : java/lang/annotation/Annotation { -} - diff --git a/lib/stove-testing-e2e-wiremock/build.gradle.kts b/lib/stove-testing-e2e-wiremock/build.gradle.kts index 72f83fcc..0c151181 100644 --- a/lib/stove-testing-e2e-wiremock/build.gradle.kts +++ b/lib/stove-testing-e2e-wiremock/build.gradle.kts @@ -3,7 +3,3 @@ dependencies { api(libs.wiremock.standalone) api(libs.caffeine) } - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.wiremock.Stove") -} 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 9b5661c5..e0a5a1a2 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,9 +1,10 @@ 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.StoveSerde +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl @@ -33,7 +34,7 @@ data class WireMockSystemOptions( /** * ObjectMapper for serialization/deserialization */ - val serde: StoveSerde = StoveSerde.jackson.anyByteArraySerde() + val objectMapper: ObjectMapper = StoveObjectMapper.Default ) : SystemOptions data class WireMockContext( @@ -41,7 +42,7 @@ data class WireMockContext( val removeStubAfterRequestMatched: Boolean, val afterStubRemoved: AfterStubRemoved, val afterRequest: AfterRequestHandler, - val serde: StoveSerde, + val objectMapper: ObjectMapper, val configure: WireMockConfiguration.() -> WireMockConfiguration ) @@ -53,7 +54,7 @@ internal fun TestSystem.withWireMock(options: WireMockSystemOptions = WireMockSy options.removeStubAfterRequestMatched, options.afterStubRemoved, options.afterRequest, - options.serde, + options.objectMapper, 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 d3d63328..f19397a7 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,18 +1,16 @@ -@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.* import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig import com.github.tomakehurst.wiremock.extension.Extension -import com.github.tomakehurst.wiremock.matching.* +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 @@ -26,12 +24,10 @@ typealias AfterRequestHandler = (ServeEvent, Cache) -> Unit class WireMockSystem( override val testSystem: TestSystem, ctx: WireMockContext -) : PluggedSystem, - ValidatedSystem, - RunAware { +) : PluggedSystem, ValidatedSystem, RunAware { private val stubLog: Cache = Caffeine.newBuilder().build() private var wireMock: WireMockServer - private val serde: StoveSerde = ctx.serde + private val json: ObjectMapper = ctx.objectMapper private val logger: Logger = LoggerFactory.getLogger(javaClass) init { @@ -92,7 +88,7 @@ class WireMockSystem( aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { res.withBody(serde.serialize(it)) } + responseBody.map { res.withBody(json.writeValueAsBytes(it)) } val req = put(urlEqualTo(url)) configureBodyAndMetadata(req, metadata, requestBody) val stub = wireMock.stubFor(req.willReturn(res).withId(UUID.randomUUID())) @@ -112,7 +108,7 @@ class WireMockSystem( aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { res.withBody(serde.serialize(it)) } + responseBody.map { res.withBody(json.writeValueAsBytes(it)) } val req = patch(urlEqualTo(url)) configureBodyAndMetadata(req, metadata, requestBody) val stub = wireMock.stubFor(req.willReturn(res).withId(UUID.randomUUID())) @@ -153,11 +149,10 @@ class WireMockSystem( @WiremockDsl fun mockPutConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = put(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = put(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -165,11 +160,10 @@ class WireMockSystem( @WiremockDsl fun mockPatchConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = patch(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = patch(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -177,11 +171,10 @@ class WireMockSystem( @WiremockDsl fun mockGetConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = get(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = get(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -189,11 +182,10 @@ class WireMockSystem( @WiremockDsl fun mockHeadConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = head(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = head(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -201,11 +193,10 @@ class WireMockSystem( @WiremockDsl fun mockDeleteConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = delete(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = delete(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -213,11 +204,10 @@ class WireMockSystem( @WiremockDsl fun mockPostConfigure( url: String, - urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) }, - configure: (MappingBuilder, StoveSerde) -> MappingBuilder + configure: (MappingBuilder, ObjectMapper) -> MappingBuilder ): WireMockSystem { - val req = post(urlPatternFn(url)) - val stub = wireMock.stubFor(configure(req, serde).withId(UUID.randomUUID())) + val req = post(urlEqualTo(url)) + val stub = wireMock.stubFor(configure(req, json).withId(UUID.randomUUID())) stubLog.put(stub.id, stub) return this } @@ -226,9 +216,9 @@ class WireMockSystem( fun behaviourFor( url: String, method: (String) -> MappingBuilder, - block: StubBehaviourBuilder.(StoveSerde) -> Unit + block: StubBehaviourBuilder.(ObjectMapper) -> Unit ) { - stubBehaviour(wireMock, serde = serde, url, method, block) + stubBehaviour(wireMock, objectMapper = json, url, method, block) } @WiremockDsl @@ -250,7 +240,7 @@ class WireMockSystem( ValidationResult( "${it.method.value()} ${it.url}", it.bodyAsString, - serde.serialize(it.queryParams).decodeToString() + json.writeValueAsString(it.queryParams) ).toString() } throw AssertionError( @@ -272,14 +262,13 @@ class WireMockSystem( ) { request.withMetadata(metadata) body.map { - request - .withRequestBody( - equalToJson( - serde.serialize(it).decodeToString(), - true, - false - ) - ).withHeader("Content-Type", ContainsPattern("application/json")) + request.withRequestBody( + equalToJson( + json.writeValueAsString(it), + true, + false + ) + ).withHeader("Content-Type", ContainsPattern("application/json")) } } @@ -290,7 +279,7 @@ class WireMockSystem( val mockResponse = aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json; charset=UTF-8") - responseBody.map { mockResponse.withBody(serde.serialize(it)) } + responseBody.map { mockResponse.withBody(json.writeValueAsBytes(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 f7e18e46..2d843112 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, - serde: StoveSerde, + objectMapper: ObjectMapper, url: String, method: (String) -> MappingBuilder, - block: StubBehaviourBuilder.(StoveSerde) -> Unit + block: StubBehaviourBuilder.(ObjectMapper) -> Unit ) { val builder = StubBehaviourBuilder(wireMockServer, url, method) - builder.block(serde) + builder.block(objectMapper) } class StubBehaviourBuilder( diff --git a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.kt similarity index 93% rename from lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt rename to lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.kt index 8cb263d5..b568330f 100644 --- a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/Stove.kt +++ b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/TestSystemConfig.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 Stove : AbstractProjectConfig() { +class TestSystemConfig : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { diff --git a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockDeletionTest.kt b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockDeletionTest.kt index e293beaf..c73eb479 100644 --- a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockDeletionTest.kt +++ b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockDeletionTest.kt @@ -11,100 +11,89 @@ import java.net.http.* import java.net.http.HttpRequest.BodyPublishers import java.net.http.HttpResponse.BodyHandlers -class WireMockDeletionTest : - FunSpec({ - /** - * Check [WireMockContext.removeStubAfterRequestMatched] - */ - test("Remove stub from wiremock when request is matched") { - val reqBody = "{\"req\": 1}" - val responseBody = "{\"res\": 1}" - TestSystem.validate { - wiremock { - mockPostConfigure("/post-url") { req, _ -> - req - .withRequestBody(equalToJson(reqBody)) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody(responseBody) - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } +class WireMockDeletionTest : FunSpec({ + /** + * Check [WireMockContext.removeStubAfterRequestMatched] + */ + test("Remove stub from wiremock when request is matched") { + val reqBody = "{\"req\": 1}" + val responseBody = "{\"res\": 1}" + TestSystem.validate { + wiremock { + mockPostConfigure("/post-url") { req, _ -> + req.withRequestBody(equalToJson(reqBody)) + .withHeader("Content-Type", ContainsPattern("application/json")) + .willReturn( + aResponse().withBody(responseBody).withStatus(200) + .withHeader("Content-Type", "application/json; charset=UTF-8") + ) } } + } - val client = HttpClient.newBuilder().build() - val reqBuilder = HttpRequest - .newBuilder(URI("http://localhost:9098/post-url")) - .header("Content-Type", "application/json") + val client = HttpClient.newBuilder().build() + val reqBuilder = HttpRequest.newBuilder(URI("http://localhost:9098/post-url")) + .header("Content-Type", "application/json") - withContext(Dispatchers.IO) { - val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe responseBody + withContext(Dispatchers.IO) { + val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() + val response = client.send(request, BodyHandlers.ofString()) + response.body() shouldBe responseBody - val request2 = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.statusCode() shouldBe 404 - } + val request2 = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() + val response2 = client.send(request2, BodyHandlers.ofString()) + response2.statusCode() shouldBe 404 } + } - /** - * Check [WireMockContext.removeStubAfterRequestMatched] - */ - test("Removes the stub after request completes, and can be added again") { - val reqBody = "{\"req\": 1}" - val responseBody = "{\"res\": 1}" - val url = "/post-url-2" - TestSystem.validate { - wiremock { - mockPostConfigure(url) { req, _ -> - req - .withRequestBody(equalToJson(reqBody)) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody(responseBody) - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } + /** + * Check [WireMockContext.removeStubAfterRequestMatched] + */ + test("Removes the stub after request completes, and can be added again") { + val reqBody = "{\"req\": 1}" + val responseBody = "{\"res\": 1}" + val url = "/post-url-2" + TestSystem.validate { + wiremock { + mockPostConfigure(url) { req, _ -> + req.withRequestBody(equalToJson(reqBody)) + .withHeader("Content-Type", ContainsPattern("application/json")) + .willReturn( + aResponse().withBody(responseBody).withStatus(200) + .withHeader("Content-Type", "application/json; charset=UTF-8") + ) } } + } - val client = HttpClient.newBuilder().build() - val reqBuilder = HttpRequest - .newBuilder(URI("http://localhost:9098$url")) - .header("Content-Type", "application/json") + val client = HttpClient.newBuilder().build() + val reqBuilder = HttpRequest.newBuilder(URI("http://localhost:9098$url")) + .header("Content-Type", "application/json") - withContext(Dispatchers.IO) { - val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe responseBody - } + withContext(Dispatchers.IO) { + val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() + val response = client.send(request, BodyHandlers.ofString()) + response.body() shouldBe responseBody + } - TestSystem.validate { - wiremock { - mockPostConfigure(url) { req, _ -> - req - .withRequestBody(equalToJson(reqBody)) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody(responseBody) - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } + TestSystem.validate { + wiremock { + mockPostConfigure(url) { req, _ -> + req + .withRequestBody(equalToJson(reqBody)) + .withHeader("Content-Type", ContainsPattern("application/json")) + .willReturn( + aResponse().withBody(responseBody).withStatus(200) + .withHeader("Content-Type", "application/json; charset=UTF-8") + ) } } + } - withContext(Dispatchers.IO) { - val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe responseBody - } + withContext(Dispatchers.IO) { + val request = reqBuilder.POST(BodyPublishers.ofString(reqBody)).build() + val response = client.send(request, BodyHandlers.ofString()) + response.body() shouldBe responseBody } - }) + } +}) diff --git a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockOperationsTest.kt b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockOperationsTest.kt deleted file mode 100644 index 425964f4..00000000 --- a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockOperationsTest.kt +++ /dev/null @@ -1,548 +0,0 @@ -package com.trendyol.stove.testing.e2e.wiremock - -import com.github.tomakehurst.wiremock.client.WireMock.* -import com.github.tomakehurst.wiremock.matching.ContainsPattern -import com.trendyol.stove.testing.e2e.system.TestSystem -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.* -import java.net.URI -import java.net.http.* -import java.net.http.HttpRequest.BodyPublishers -import java.net.http.HttpResponse.BodyHandlers - -class WireMockOperationsTest : - FunSpec({ - - /** - * Configures a POST request mock using [WireMockSystem.mockPostConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockPostConfigure]. - */ - test("Wiremock mockPostConfigure should mock urls with urlEqualTo(url) pattern in default") { - val url = "/post-url" - val client = HttpClient.newBuilder().build() - val reqBuilder = HttpRequest - .newBuilder(URI("http://localhost:9098/$url")) - .header("Content-Type", "application/json") - - TestSystem.validate { - wiremock { - mockPostConfigure("/$url") { req, _ -> - req - .withRequestBody(equalTo("request2")) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("response2") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - mockPostConfigure("/$url") { req, _ -> - req - .withRequestBody(equalTo("request1")) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("response1") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - - withContext(Dispatchers.IO) { - val request2 = reqBuilder.POST(BodyPublishers.ofString("request2")).build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.body() shouldBe "response2" - - val request1 = reqBuilder.POST(BodyPublishers.ofString("request1")).build() - val response1 = client.send(request1, BodyHandlers.ofString()) - response1.body() shouldBe "response1" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockPostConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockPostConfigure]. - */ - test("Wiremock mockPostConfigure should accept overridden urlMatcher") { - val url = "categories/createCategory" - val client = HttpClient.newBuilder().build() - val reqBuilder = HttpRequest - .newBuilder(URI("http://localhost:9098/$url")) - .header("Content-Type", "application/json") - - TestSystem.validate { - wiremock { - mockPostConfigure("/categories/.*", { urlPathMatching(it) }) { req, _ -> - req - .withRequestBody(equalTo("request2")) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("response2") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - mockPostConfigure("/categories/.*", { urlPathMatching(it) }) { req, _ -> - req - .withRequestBody(equalTo("request1")) - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("response1") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - - withContext(Dispatchers.IO) { - val request2 = reqBuilder.POST(BodyPublishers.ofString("request2")).build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.body() shouldBe "response2" - - val request1 = reqBuilder.POST(BodyPublishers.ofString("request1")).build() - val response1 = client.send(request1, BodyHandlers.ofString()) - response1.body() shouldBe "response1" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockGetConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockGetConfigure]. - */ - test("Wiremock mockGetConfigure should mock urls with urlEqualTo(url) pattern in default") { - val client = HttpClient.newBuilder().build() - var id = 1 - var active = true - TestSystem.validate { - wiremock { - mockGetConfigure("/suppliers/1?active=true") { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("Supplier1Response") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - mockGetConfigure("/suppliers/2?active=false") { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("Supplier2Response") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/suppliers/$id?active=$active") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Content-Type", "application/json") - - val request2 = reqBuilder.GET().build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.body() shouldBe "Supplier1Response" - - id = 2 - active = false - val uri2 = URI.create("http://localhost:9098/suppliers/$id?active=$active") - val reqBuilder2 = HttpRequest - .newBuilder(uri2) - .header("Content-Type", "application/json") - val request1 = reqBuilder2.GET().build() - val response1 = client.send(request1, BodyHandlers.ofString()) - response1.body() shouldBe "Supplier2Response" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockGetConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockGetConfigure]. - */ - test("Wiremock mockGetConfigure should accept overridden urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockGetConfigure("/suppliers/1.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .withQueryParam("active", matching("true|false")) - .willReturn( - aResponse() - .withBody("Supplier1Response") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - mockGetConfigure("/suppliers/2.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .withQueryParam("active", matching("true|false")) - .willReturn( - aResponse() - .withBody("Supplier2Response") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - - var id = 1 - var active = true - withContext(Dispatchers.IO) { - val uri1 = URI.create("http://localhost:9098/suppliers/$id?active=$active") - val request1 = HttpRequest - .newBuilder(uri1) - .header("Content-Type", "application/json") - .GET() - .build() - val response1 = client.send(request1, BodyHandlers.ofString()) - response1.body() shouldBe "Supplier1Response" - - id = 2 - active = false - val uri2 = URI.create("http://localhost:9098/suppliers/$id?active=$active") - val request2 = HttpRequest - .newBuilder(uri2) - .header("Content-Type", "application/json") - .GET() - .build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.body() shouldBe "Supplier2Response" - TestSystem.validate { - wiremock { - mockGetConfigure("/suppliers/2.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .withQueryParam("active", matching("true|false")) - .willReturn( - aResponse() - .withBody("Supplier2Response") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - active = true - val uri3 = URI.create("http://localhost:9098/suppliers/$id?active=$active") - val request3 = HttpRequest - .newBuilder(uri3) - .header("Content-Type", "application/json") - .GET() - .build() - val response3 = client.send(request3, BodyHandlers.ofString()) - response3.body() shouldBe "Supplier2Response" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockPutConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockPutConfigure]. - */ - test("Wiremock mockPutConfigure should accept default urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockPutConfigure("/resources/1") { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("PutResource1") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/1") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Content-Type", "application/json") - .PUT(BodyPublishers.ofString("{\"name\":\"test\"}")) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe "PutResource1" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockPutConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockPutConfigure]. - */ - test("Wiremock mockPutConfigure should accept overridden urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockPutConfigure("/resources/.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("PutResourceMatched") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/123") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Content-Type", "application/json") - .PUT(BodyPublishers.ofString("{\"name\":\"test\"}")) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe "PutResourceMatched" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockDeleteConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockDeleteConfigure]. - */ - test("Wiremock mockDeleteConfigure should accept default urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - withContext(Dispatchers.IO) { - wiremock { - mockDeleteConfigure("/resources/1") { req, _ -> - req - .withHeader("Authorization", equalTo("Bearer token")) - .willReturn( - aResponse().withStatus(204) - ) - } - } - - val uri = URI.create("http://localhost:9098/resources/1") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Authorization", "Bearer token") - .DELETE() - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - - response.statusCode() shouldBe 204 - } - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockDeleteConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockDeleteConfigure]. - */ - test("Wiremock mockDeleteConfigure should accept overridden urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - withContext(Dispatchers.IO) { - wiremock { - mockDeleteConfigure("/resources/.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Authorization", equalTo("Bearer token")) - .willReturn( - aResponse().withStatus(204) - ) - } - } - - val uri = URI.create("http://localhost:9098/resources/123") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Authorization", "Bearer token") - .DELETE() - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - - response.statusCode() shouldBe 204 - } - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockPatchConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockPatchConfigure]. - */ - test("Wiremock mockPatchConfigure should accept default urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockPatchConfigure("/resources/1") { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("PatchResource1") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/1") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Content-Type", "application/json") - .method("PATCH", BodyPublishers.ofString("{\"name\":\"updated\"}")) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe "PatchResource1" - } - } - /** - * Configures a POST request mock using [WireMockSystem.mockPatchConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockPatchConfigure]. - */ - test("Wiremock mockPatchConfigure should accept overridden urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockPatchConfigure("/resources/.*", { (urlPathMatching(it)) }) { req, _ -> - req - .withHeader("Content-Type", ContainsPattern("application/json")) - .willReturn( - aResponse() - .withBody("PatchResourceMatched") - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=UTF-8") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/123") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Content-Type", "application/json") - .method("PATCH", BodyPublishers.ofString("{\"name\":\"updated\"}")) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.body() shouldBe "PatchResourceMatched" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockHeadConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to use the default URL pattern for [WireMockSystem.mockHeadConfigure]. - */ - test("Wiremock mockHeadConfigure should accept default urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockHeadConfigure("/resources/1") { req, _ -> - req - .withHeader("Authorization", equalTo("Bearer token")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("X-Custom-Header", "CustomValue") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/1") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Authorization", "Bearer token") - .method("HEAD", BodyPublishers.noBody()) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.statusCode() shouldBe 200 - response.headers().firstValue("X-Custom-Header").orElse("") shouldBe "CustomValue" - } - } - - /** - * Configures a POST request mock using [WireMockSystem.mockHeadConfigure]. - * - * @param urlMatcher A [UrlPattern] used to match the request URL. Defaults to [urlEqualTo] with the provided [url]. - * - * This test demonstrates how to configure and override the default URL matcher for [WireMockSystem.mockHeadConfigure]. - */ - test("Wiremock mockHeadConfigure should accept overridden urlMatcher") { - val client = HttpClient.newBuilder().build() - TestSystem.validate { - wiremock { - mockHeadConfigure("/resources/.*", { urlPathMatching(it) }) { req, _ -> - req - .withHeader("Authorization", equalTo("Bearer token")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("X-Overridden-Header", "OverriddenValue") - ) - } - } - } - withContext(Dispatchers.IO) { - val uri = URI.create("http://localhost:9098/resources/123") - val reqBuilder = HttpRequest - .newBuilder(uri) - .header("Authorization", "Bearer token") - .method("HEAD", BodyPublishers.noBody()) - - val request = reqBuilder.build() - val response = client.send(request, BodyHandlers.ofString()) - response.statusCode() shouldBe 200 - response.headers().firstValue("X-Overridden-Header").orElse("") shouldBe "OverriddenValue" - } - } - }) diff --git a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystemTests.kt b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystemTests.kt index 18fe3284..4eb44d68 100644 --- a/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystemTests.kt +++ b/lib/stove-testing-e2e-wiremock/src/test/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystemTests.kt @@ -16,75 +16,72 @@ import java.net.http.HttpRequest import java.net.http.HttpRequest.BodyPublishers import java.net.http.HttpResponse.BodyHandlers -class WireMockSystemTests : - FunSpec({ - lateinit var wireMock: WireMockServer - lateinit var client: HttpClient - lateinit var reqBuilder: HttpRequest.Builder - val url = "post-url" - beforeSpec { - wireMock = WireMockServer(9090) - wireMock.start() - client = HttpClient.newBuilder().build() - reqBuilder = HttpRequest.newBuilder(URI("http://localhost:9090/$url")) - } +class WireMockSystemTests : FunSpec({ + lateinit var wireMock: WireMockServer + lateinit var client: HttpClient + lateinit var reqBuilder: HttpRequest.Builder + val url = "post-url" + beforeSpec { + wireMock = WireMockServer(9090) + wireMock.start() + client = HttpClient.newBuilder().build() + reqBuilder = HttpRequest.newBuilder(URI("http://localhost:9090/$url")) + } - test("Single thread stubbing") { - wireMock.stubFor( - post("/$url") - .withRequestBody(equalTo("request1")) - .willReturn( - aResponse() - .withBody("response1") - ) - ) + test("Single thread stubbing") { + wireMock.stubFor( + post("/$url") + .withRequestBody(equalTo("request1")) + .willReturn( + aResponse() + .withBody("response1") + ) + ) - wireMock.stubFor( - post("/$url") - .withRequestBody(equalTo("request2")) - .willReturn( - aResponse() - .withBody("response2") - ) - ) + wireMock.stubFor( + post("/$url") + .withRequestBody(equalTo("request2")) + .willReturn( + aResponse() + .withBody("response2") + ) + ) - withContext(Dispatchers.IO) { - val request2 = reqBuilder.POST(BodyPublishers.ofString("request2")).build() - val response2 = client.send(request2, BodyHandlers.ofString()) - response2.body() shouldBe "response2" + withContext(Dispatchers.IO) { + val request2 = reqBuilder.POST(BodyPublishers.ofString("request2")).build() + val response2 = client.send(request2, BodyHandlers.ofString()) + response2.body() shouldBe "response2" - val request1 = reqBuilder.POST(BodyPublishers.ofString("request1")).build() - val response1 = client.send(request1, BodyHandlers.ofString()) - response1.body() shouldBe "response1" - } + val request1 = reqBuilder.POST(BodyPublishers.ofString("request1")).build() + val response1 = client.send(request1, BodyHandlers.ofString()) + response1.body() shouldBe "response1" } + } - test("Multi thread stubbing") { + test("Multi thread stubbing") { - (1..20) - .map { i -> - async { - wireMock.stubFor( - post("/$url") - .withRequestBody(equalTo("request$i")) - .willReturn( - aResponse() - .withBody("response$i") - ) + (1..20).map { i -> + async { + wireMock.stubFor( + post("/$url") + .withRequestBody(equalTo("request$i")) + .willReturn( + aResponse() + .withBody("response$i") ) - } - }.awaitAll() + ) + } + }.awaitAll() - (1..20) - .map { i -> - async { - val request = reqBuilder.POST(BodyPublishers.ofString("request$i")).build() - val response = - withContext(Dispatchers.IO) { - client.send(request, BodyHandlers.ofString()) - } - response.body() shouldBe "response$i" + (1..20).map { i -> + async { + val request = reqBuilder.POST(BodyPublishers.ofString("request$i")).build() + val response = + withContext(Dispatchers.IO) { + client.send(request, BodyHandlers.ofString()) } - }.awaitAll() - } - }) + response.body() shouldBe "response$i" + } + }.awaitAll() + } +}) diff --git a/lib/stove-testing-e2e/api/stove-testing-e2e.api b/lib/stove-testing-e2e/api/stove-testing-e2e.api deleted file mode 100644 index 3eed0fa7..00000000 --- a/lib/stove-testing-e2e/api/stove-testing-e2e.api +++ /dev/null @@ -1,655 +0,0 @@ -public final class com/trendyol/stove/functional/ExtensionsKt { - public static final fun evert (Larrow/core/Option;)Lcom/trendyol/stove/functional/Try; - public static final fun evert (Lcom/trendyol/stove/functional/Try;)Larrow/core/Option; - public static final fun flatten (Larrow/core/Option;)Larrow/core/Option; - public static final fun flatten (Larrow/core/Option;)Ljava/util/List; - public static final fun flatten (Lcom/trendyol/stove/functional/Try;)Larrow/core/Option; - public static final fun flatten (Ljava/lang/Iterable;)Ljava/util/List; - public static final fun get (Larrow/core/Option;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/functional/Failure : com/trendyol/stove/functional/Try { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lcom/trendyol/stove/functional/Failure; - public static synthetic fun copy$default (Lcom/trendyol/stove/functional/Failure;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/trendyol/stove/functional/Failure; - public fun equals (Ljava/lang/Object;)Z - public synthetic fun get ()Ljava/lang/Object; - public fun get ()Ljava/lang/Void; - public final fun getException ()Ljava/lang/Throwable; - public fun getFailed ()Lcom/trendyol/stove/functional/Try; - public synthetic fun getOrNull ()Ljava/lang/Object; - public fun getOrNull ()Ljava/lang/Void; - public fun hashCode ()I - public fun isFailure ()Z - public fun isSuccess ()Z - public fun toEither ()Larrow/core/Either; - public fun toOption ()Larrow/core/Option; - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/functional/Reflect { - public static final field Companion Lcom/trendyol/stove/functional/Reflect$Companion; - public fun (Ljava/lang/Object;)V - public final fun getInstance ()Ljava/lang/Object; -} - -public final class com/trendyol/stove/functional/Reflect$Companion { -} - -public final class com/trendyol/stove/functional/Reflect$OnGoingReflect { - public fun (Lcom/trendyol/stove/functional/Reflect;Ljava/lang/Object;Ljava/lang/String;)V - public final fun then (Ljava/lang/Object;)V -} - -public final class com/trendyol/stove/functional/Success : com/trendyol/stove/functional/Try { - public fun (Ljava/lang/Object;)V - public final fun component1 ()Ljava/lang/Object; - public final fun copy (Ljava/lang/Object;)Lcom/trendyol/stove/functional/Success; - public static synthetic fun copy$default (Lcom/trendyol/stove/functional/Success;Ljava/lang/Object;ILjava/lang/Object;)Lcom/trendyol/stove/functional/Success; - public fun equals (Ljava/lang/Object;)Z - public fun get ()Ljava/lang/Object; - public fun getFailed ()Lcom/trendyol/stove/functional/Try; - public fun getOrNull ()Ljava/lang/Object; - public final fun getValue ()Ljava/lang/Object; - public fun hashCode ()I - public fun isFailure ()Z - public fun isSuccess ()Z - public fun toEither ()Larrow/core/Either; - public fun toOption ()Larrow/core/Option; - public fun toString ()Ljava/lang/String; -} - -public abstract class com/trendyol/stove/functional/Try { - public static final field Companion Lcom/trendyol/stove/functional/Try$Companion; - public final fun filter (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public final fun filterNot (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public final fun filterOrElse (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public final fun flatMap (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public final fun fold (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public final fun forEach (Lkotlin/jvm/functions/Function1;)V - public abstract fun get ()Ljava/lang/Object; - public abstract fun getFailed ()Lcom/trendyol/stove/functional/Try; - public abstract fun getOrNull ()Ljava/lang/Object; - public abstract fun isFailure ()Z - public abstract fun isSuccess ()Z - public final fun map (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public abstract fun toEither ()Larrow/core/Either; - public abstract fun toOption ()Larrow/core/Option; - public final fun transform (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public final fun zip (Lcom/trendyol/stove/functional/Try;)Lcom/trendyol/stove/functional/Try; - public final fun zip (Lcom/trendyol/stove/functional/Try;Lkotlin/jvm/functions/Function2;)Lcom/trendyol/stove/functional/Try; -} - -public final class com/trendyol/stove/functional/Try$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/functional/Try; -} - -public final class com/trendyol/stove/functional/TryKt { - public static final fun filterNotNull (Lcom/trendyol/stove/functional/Try;)Lcom/trendyol/stove/functional/Try; - public static final fun flatten (Lcom/trendyol/stove/functional/Try;)Lcom/trendyol/stove/functional/Try; - public static final fun getOrElse (Lcom/trendyol/stove/functional/Try;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; - public static final fun orElse (Lcom/trendyol/stove/functional/Try;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/functional/Try; - public static final fun recover (Lcom/trendyol/stove/functional/Try;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; - public static final fun recoverWith (Lcom/trendyol/stove/functional/Try;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/functional/Try; -} - -public abstract interface class com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public abstract fun getCompatibleSubstitute ()Ljava/lang/String; - public abstract fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public abstract fun getImage ()Ljava/lang/String; - public abstract fun getImageWithTag ()Ljava/lang/String; - public abstract fun getRegistry ()Ljava/lang/String; - public abstract fun getTag ()Ljava/lang/String; - public abstract fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; -} - -public final class com/trendyol/stove/testing/e2e/containers/ContainerOptions$DefaultImpls { - public static fun getImageWithTag (Lcom/trendyol/stove/testing/e2e/containers/ContainerOptions;)Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/containers/ProvidedRegistryKt { - public static final fun getDEFAULT_REGISTRY ()Ljava/lang/String; - public static final fun setDEFAULT_REGISTRY (Ljava/lang/String;)V - public static final fun withProvidedRegistry (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public static synthetic fun withProvidedRegistry$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/containers/StoveContainer { - public abstract fun getContainerIdAccess ()Ljava/lang/String; - public abstract fun getDockerClientAccess ()Lkotlin/Lazy; - public abstract fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public abstract fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public abstract fun pause ()V - public abstract fun unpause ()V -} - -public final class com/trendyol/stove/testing/e2e/containers/StoveContainer$DefaultImpls { - public static fun getContainerIdAccess (Lcom/trendyol/stove/testing/e2e/containers/StoveContainer;)Ljava/lang/String; - public static fun getDockerClientAccess (Lcom/trendyol/stove/testing/e2e/containers/StoveContainer;)Lkotlin/Lazy; - public static fun inspect (Lcom/trendyol/stove/testing/e2e/containers/StoveContainer;)Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public static fun pause (Lcom/trendyol/stove/testing/e2e/containers/StoveContainer;)V - public static fun unpause (Lcom/trendyol/stove/testing/e2e/containers/StoveContainer;)V -} - -public final class com/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation { - public fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;JLjava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()J - public final fun component11 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Z - public final fun component6 ()Z - public final fun component7 ()Z - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;JLjava/lang/String;)Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;JLjava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/lang/String; - public final fun getExitCode ()J - public final fun getFinishedAt ()Ljava/lang/String; - public final fun getId ()Ljava/lang/String; - public final fun getLabels ()Ljava/util/Map; - public final fun getName ()Ljava/lang/String; - public final fun getPaused ()Z - public final fun getRestarting ()Z - public final fun getRunning ()Z - public final fun getStartedAt ()Ljava/lang/String; - public final fun getState ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class com/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration { - public abstract fun execute (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun getOrder ()I -} - -public final class com/trendyol/stove/testing/e2e/database/migrations/MigrationCollection { - public fun ()V - public final fun register (Lkotlin/reflect/KClass;)Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; - public final fun register (Lkotlin/reflect/KClass;Lcom/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration;)Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; - public final fun replace (Lkotlin/reflect/KClass;Lcom/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration;)Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; - public final fun run (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/database/migrations/MigrationPriority : java/lang/Enum { - public static final field HIGHEST Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationPriority; - public static final field LOWEST Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationPriority; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationPriority; - public static fun values ()[Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationPriority; -} - -public abstract class com/trendyol/stove/testing/e2e/http/StoveHttpResponse { - public synthetic fun (ILjava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getHeaders ()Ljava/util/Map; - public fun getStatus ()I -} - -public final class com/trendyol/stove/testing/e2e/http/StoveHttpResponse$Bodiless : com/trendyol/stove/testing/e2e/http/StoveHttpResponse { - public fun (ILjava/util/Map;)V - public final fun component1 ()I - public final fun component2 ()Ljava/util/Map; - public final fun copy (ILjava/util/Map;)Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$Bodiless; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$Bodiless;ILjava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$Bodiless; - public fun equals (Ljava/lang/Object;)Z - public fun getHeaders ()Ljava/util/Map; - public fun getStatus ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/http/StoveHttpResponse$WithBody : com/trendyol/stove/testing/e2e/http/StoveHttpResponse { - public fun (ILjava/util/Map;Lkotlin/jvm/functions/Function1;)V - public final fun component1 ()I - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Lkotlin/jvm/functions/Function1; - public final fun copy (ILjava/util/Map;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$WithBody; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$WithBody;ILjava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/http/StoveHttpResponse$WithBody; - public fun equals (Ljava/lang/Object;)Z - public final fun getBody ()Lkotlin/jvm/functions/Function1; - public fun getHeaders ()Ljava/util/Map; - public fun getStatus ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/messaging/FailedObservedMessage : com/trendyol/stove/testing/e2e/messaging/ObservedMessage { - public fun (Ljava/lang/Object;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Object; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public final fun component3 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Object;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;Ljava/lang/Throwable;)Lcom/trendyol/stove/testing/e2e/messaging/FailedObservedMessage; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/messaging/FailedObservedMessage;Ljava/lang/Object;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/messaging/FailedObservedMessage; - public fun equals (Ljava/lang/Object;)Z - public fun getActual ()Ljava/lang/Object; - public fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public final fun getReason ()Ljava/lang/Throwable; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/messaging/FailedParsedMessage : com/trendyol/stove/testing/e2e/messaging/ParsedMessage { - public fun (Larrow/core/Option;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;Ljava/lang/Throwable;)V - public fun getMessage ()Larrow/core/Option; - public fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public final fun getReason ()Ljava/lang/Throwable; -} - -public final class com/trendyol/stove/testing/e2e/messaging/Failure { - public fun (Lcom/trendyol/stove/testing/e2e/messaging/ObservedMessage;Ljava/lang/Throwable;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/messaging/ObservedMessage; - public final fun component2 ()Ljava/lang/Throwable; - public final fun copy (Lcom/trendyol/stove/testing/e2e/messaging/ObservedMessage;Ljava/lang/Throwable;)Lcom/trendyol/stove/testing/e2e/messaging/Failure; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/messaging/Failure;Lcom/trendyol/stove/testing/e2e/messaging/ObservedMessage;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/messaging/Failure; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Lcom/trendyol/stove/testing/e2e/messaging/ObservedMessage; - public final fun getReason ()Ljava/lang/Throwable; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/messaging/MessageMetadata { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Ljava/util/Map; - public final fun getKey ()Ljava/lang/String; - public final fun getTopic ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class com/trendyol/stove/testing/e2e/messaging/ObservedMessage { - public fun (Ljava/lang/Object;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;)V - public fun getActual ()Ljava/lang/Object; - public fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; -} - -public abstract interface class com/trendyol/stove/testing/e2e/messaging/ParsedMessage { - public abstract fun getMessage ()Larrow/core/Option; - public abstract fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; -} - -public final class com/trendyol/stove/testing/e2e/messaging/SuccessfulParsedMessage : com/trendyol/stove/testing/e2e/messaging/ParsedMessage { - public fun (Larrow/core/Option;Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata;)V - public fun getMessage ()Larrow/core/Option; - public fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; -} - -public final class com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig; - public final fun createObjectMapperWithDefaults ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer : com/fasterxml/jackson/databind/JsonDeserializer { - public fun ()V - public synthetic fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/lang/Object; - public fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/time/Instant; -} - -public final class com/trendyol/stove/testing/e2e/serialization/IsoInstantSerializer : com/fasterxml/jackson/databind/JsonSerializer { - public fun ()V - public synthetic fun serialize (Ljava/lang/Object;Lcom/fasterxml/jackson/core/JsonGenerator;Lcom/fasterxml/jackson/databind/SerializerProvider;)V - public fun serialize (Ljava/time/Instant;Lcom/fasterxml/jackson/core/JsonGenerator;Lcom/fasterxml/jackson/databind/SerializerProvider;)V -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveGson { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/serialization/StoveGson; - public final fun anyByteArraySerde (Lcom/google/gson/Gson;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyByteArraySerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveGson;Lcom/google/gson/Gson;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun anyJsonStringSerde (Lcom/google/gson/Gson;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyJsonStringSerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveGson;Lcom/google/gson/Gson;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun byConfiguring (Lkotlin/jvm/functions/Function1;)Lcom/google/gson/Gson; - public final fun getDefault ()Lcom/google/gson/Gson; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveGsonByteArraySerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lcom/google/gson/Gson;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize ([BLjava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither ([BLjava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)[B -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveGsonStringSerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lcom/google/gson/Gson;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither (Ljava/lang/String;Ljava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveJackson { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/serialization/StoveJackson; - public final fun anyByteArraySerde (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyByteArraySerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveJackson;Lcom/fasterxml/jackson/databind/ObjectMapper;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun anyJsonStringSerde (Lcom/fasterxml/jackson/databind/ObjectMapper;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyJsonStringSerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveJackson;Lcom/fasterxml/jackson/databind/ObjectMapper;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun byConfiguring (Lkotlin/jvm/functions/Function1;)Lcom/fasterxml/jackson/databind/ObjectMapper; - public final fun getDefault ()Lcom/fasterxml/jackson/databind/ObjectMapper; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveJacksonByteArraySerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lcom/fasterxml/jackson/databind/ObjectMapper;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize ([BLjava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither ([BLjava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)[B -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveJacksonStringSerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lcom/fasterxml/jackson/databind/ObjectMapper;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither (Ljava/lang/String;Ljava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveKotlinx { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/serialization/StoveKotlinx; - public final fun anyByteArraySerde (Lkotlinx/serialization/json/Json;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyByteArraySerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveKotlinx;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun anyJsonStringSerde (Lkotlinx/serialization/json/Json;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public static synthetic fun anyJsonStringSerde$default (Lcom/trendyol/stove/testing/e2e/serialization/StoveKotlinx;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde; - public final fun byConfiguring (Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/Json; - public final fun getDefault ()Lkotlinx/serialization/json/Json; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveKotlinxByteArraySerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lkotlinx/serialization/json/Json;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize ([BLjava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither ([BLjava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)[B -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveKotlinxStringSerializer : com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public fun (Lkotlinx/serialization/json/Json;)V - public synthetic fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public fun deserialize (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; - public synthetic fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public fun deserializeEither (Ljava/lang/String;Ljava/lang/Class;)Larrow/core/Either; - public synthetic fun serialize (Ljava/lang/Object;)Ljava/lang/Object; - public fun serialize (Ljava/lang/Object;)Ljava/lang/String; -} - -public abstract interface class com/trendyol/stove/testing/e2e/serialization/StoveSerde { - public static final field Companion Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde$Companion; - public abstract fun deserialize (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; - public abstract fun deserializeEither (Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; - public abstract fun serialize (Ljava/lang/Object;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveSerde$Companion { - public final fun getGson ()Lcom/trendyol/stove/testing/e2e/serialization/StoveGson; - public final fun getJackson ()Lcom/trendyol/stove/testing/e2e/serialization/StoveJackson; - public final fun getKotlinx ()Lcom/trendyol/stove/testing/e2e/serialization/StoveKotlinx; -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveSerde$DefaultImpls { - public static fun deserializeEither (Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;Ljava/lang/Object;Ljava/lang/Class;)Larrow/core/Either; -} - -public abstract class com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem : java/lang/RuntimeException { - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem$BecauseOfDeserialization : com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem { - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem$BecauseOfDeserializationButExpected : com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem { - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem$BecauseOfSerialization : com/trendyol/stove/testing/e2e/serialization/StoveSerde$StoveSerdeProblem { - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public abstract class com/trendyol/stove/testing/e2e/system/BridgeSystem : com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAwareWithContext, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem { - protected field ctx Ljava/lang/Object; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)V - public fun afterRun (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun close ()V - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun get (Lkotlin/reflect/KClass;)Ljava/lang/Object; - protected final fun getCtx ()Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - protected final fun setCtx (Ljava/lang/Object;)V - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/system/BridgeSystemKt { - public static final fun bridge (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/BridgeSystem; - public static final fun bridge-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/system/BridgeSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public static final fun withBridgeSystem (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/system/BridgeSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/system/PropertiesFile { - public static final field Companion Lcom/trendyol/stove/testing/e2e/system/PropertiesFile$Companion; - public static final field REUSE_ENABLED Ljava/lang/String; - public fun ()V - public final fun detectAndLogStatus ()V - public final fun enable ()V -} - -public final class com/trendyol/stove/testing/e2e/system/PropertiesFile$Companion { -} - -public final class com/trendyol/stove/testing/e2e/system/TestSystem : com/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem, java/lang/AutoCloseable { - public static final field Companion Lcom/trendyol/stove/testing/e2e/system/TestSystem$Companion; - public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun applicationUnderTest (Lcom/trendyol/stove/testing/e2e/system/abstractions/ApplicationUnderTest;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun applicationUnderTestContext ()Ljava/lang/Object; - public fun close ()V - public final fun getActiveSystems ()Ljava/util/Map; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions; - public final fun registerForDispose (Ljava/lang/AutoCloseable;)Ljava/lang/AutoCloseable; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun with (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/system/TestSystem$Companion { - public final fun stop ()V - public final fun validate (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/system/TestSystemOptions { - public fun ()V - public fun (ZLcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;Z)V - public synthetic fun (ZLcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory; - public final fun component3 ()Z - public final fun copy (ZLcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;Z)Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions;ZLcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;ZILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getKeepDependenciesRunning ()Z - public final fun getRunMigrationsAlways ()Z - public final fun getStateStorageFactory ()Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/system/TestSystemOptionsDsl { - public fun ()V - public final fun enableReuseForTestContainers ()V - public final fun isRunningLocally ()Z - public final fun keepDependenciesRunning ()Lcom/trendyol/stove/testing/e2e/system/TestSystemOptionsDsl; - public final fun runMigrationsAlways ()Lcom/trendyol/stove/testing/e2e/system/TestSystemOptionsDsl; - public final fun stateStorage (Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;)Lcom/trendyol/stove/testing/e2e/system/TestSystemOptionsDsl; -} - -public final class com/trendyol/stove/testing/e2e/system/ValidationDsl { - public static final synthetic fun box-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/ValidationDsl; - public static fun constructor-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Z - public final fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun hashCode ()I - public static fun hashCode-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/system/WithDsl { - public static final fun applicationUnderTest-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/system/abstractions/ApplicationUnderTest;)V - public static final synthetic fun box-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/WithDsl; - public static fun constructor-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Z - public final fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public fun hashCode ()I - public static fun hashCode-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAware { - public abstract fun afterRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAwareWithContext { - public abstract fun afterRun (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ApplicationUnderTest { - public abstract fun start (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/BeforeRunAware { - public abstract fun beforeRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration { - public abstract fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration { - public abstract fun configuration ()Ljava/util/List; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem : com/trendyol/stove/testing/e2e/system/abstractions/ThenSystemContinuation, java/lang/AutoCloseable { -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem$DefaultImpls { - public static fun executeWithReuseCheck (Lcom/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static fun then (Lcom/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem { - public abstract fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/RunAware { - public abstract fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext : com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAwareWithContext, com/trendyol/stove/testing/e2e/system/abstractions/BeforeRunAware, com/trendyol/stove/testing/e2e/system/abstractions/RunAware, java/lang/AutoCloseable { - public abstract fun close ()V -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext$DefaultImpls { - public static fun close (Lcom/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext;)V -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/StateStorage { - public abstract fun capture (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun isSubsequentRun ()Z -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory { - public static final field Companion Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory$Companion; - public abstract fun DefaultStateStorage (Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions;Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorage; - public abstract fun invoke (Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions;Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorage; -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory$Companion { - public final fun Default ()Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory; -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory$DefaultImpls { - public static fun DefaultStateStorage (Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorageFactory;Lcom/trendyol/stove/testing/e2e/system/TestSystemOptions;Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/trendyol/stove/testing/e2e/system/abstractions/StateStorage; -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/StateWithProcess { - public fun (Ljava/lang/Object;J)V - public final fun component1 ()Ljava/lang/Object; - public final fun component2 ()J - public final fun copy (Ljava/lang/Object;J)Lcom/trendyol/stove/testing/e2e/system/abstractions/StateWithProcess; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/system/abstractions/StateWithProcess;Ljava/lang/Object;JILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/system/abstractions/StateWithProcess; - public fun equals (Ljava/lang/Object;)Z - public final fun getProcessId ()J - public final fun getState ()Ljava/lang/Object; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/SystemConfigurationException : java/lang/Throwable { - public fun (Lkotlin/reflect/KClass;Ljava/lang/String;)V -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/SystemNotInitializedException : java/lang/Throwable { - public fun (Lkotlin/reflect/KClass;)V -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/SystemNotRegisteredException : java/lang/Throwable { - public fun (Lkotlin/reflect/KClass;)V -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ThenSystemContinuation { - public abstract fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public abstract fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/system/abstractions/ThenSystemContinuation$DefaultImpls { - public static fun executeWithReuseCheck (Lcom/trendyol/stove/testing/e2e/system/abstractions/ThenSystemContinuation;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static fun then (Lcom/trendyol/stove/testing/e2e/system/abstractions/ThenSystemContinuation;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public abstract interface class com/trendyol/stove/testing/e2e/system/abstractions/ValidatedSystem { - public abstract fun validate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/system/annotations/StoveDsl : java/lang/annotation/Annotation { -} - diff --git a/lib/stove-testing-e2e/build.gradle.kts b/lib/stove-testing-e2e/build.gradle.kts index f35a16c8..2fb9a37f 100644 --- a/lib/stove-testing-e2e/build.gradle.kts +++ b/lib/stove-testing-e2e/build.gradle.kts @@ -1,16 +1,10 @@ plugins { `java-test-fixtures` - alias(libs.plugins.kotlinx.serialization) } dependencies { api(libs.kotlinx.core) api(libs.jackson.kotlin) - api(libs.jackson.arrow) { - exclude(group = "io.arrow", module = "arrow-core") - } - api(libs.google.gson) - api(libs.kotlinx.serialization.json) api(libs.testcontainers) { version { require(libs.testcontainers.asProvider().get().version!!) diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt index 9336a0fe..ce387df2 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt @@ -2,9 +2,7 @@ package com.trendyol.stove.functional import kotlin.reflect.KProperty -class Reflect( - val instance: T -) { +class Reflect(val instance: T) { inner class OnGoingReflect( private val instance: T, private val property: String diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Try.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Try.kt index 5f630d41..d10e2777 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Try.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Try.kt @@ -397,9 +397,7 @@ fun Try.filterNotNull(): Try = is Failure -> this } -data class Success( - val value: T -) : Try() { +data class Success(val value: T) : Try() { override val isSuccess: Boolean get() = true @@ -418,9 +416,7 @@ data class Success( override fun toOption(): Option = Some(value) } -data class Failure( - val exception: Throwable -) : Try() { +data class Failure(val exception: Throwable) : Try() { override val isSuccess: Boolean get() = false diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/StoveContainer.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/StoveContainer.kt index a36009f7..db85af3e 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/StoveContainer.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/StoveContainer.kt @@ -13,12 +13,9 @@ interface StoveContainer { val imageNameAccess: DockerImageName val containerIdAccess: String - get() = dockerClientAccess.value - .listContainersCmd() - .exec() - .first { - it.image == imageNameAccess.asCanonicalNameString() - }.id + get() = dockerClientAccess.value.listContainersCmd().exec().first { + it.image == imageNameAccess.asCanonicalNameString() + }.id val dockerClientAccess: Lazy get() = lazy { DockerClientFactory.lazyClient() } @@ -31,29 +28,19 @@ interface StoveContainer { dockerClientAccess.value.unpauseContainerCmd(containerIdAccess).exec() } - fun inspect(): StoveContainerInspectInformation = dockerClientAccess.value - .inspectContainerCmd(containerIdAccess) - .exec() + fun inspect(): StoveContainerInspectInformation = dockerClientAccess.value.inspectContainerCmd(containerIdAccess).exec() .let { StoveContainerInspectInformation( id = it.id, labels = it.config.labels ?: emptyMap(), name = it.name, state = it.state.toString(), - running = it.state.running - .toOption() - .getOrElse { false }, - paused = it.state.paused - .toOption() - .getOrElse { false }, - restarting = it.state.restarting - .toOption() - .getOrElse { false }, + running = it.state.running.toOption().getOrElse { false }, + paused = it.state.paused.toOption().getOrElse { false }, + restarting = it.state.restarting.toOption().getOrElse { false }, startedAt = it.state.startedAt.toString(), finishedAt = it.state.finishedAt.toString(), - exitCode = it.state.exitCodeLong - .toOption() - .getOrElse { 0 }, + exitCode = it.state.exitCodeLong.toOption().getOrElse { 0 }, error = it.state.error.toString() ) } diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration.kt index d38d8226..aeb6a95b 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/DatabaseMigration.kt @@ -20,9 +20,7 @@ interface DatabaseMigration { val order: Int } -enum class MigrationPriority( - val value: Int -) { +enum class MigrationPriority(val value: Int) { LOWEST(Int.MAX_VALUE), HIGHEST(Int.MIN_VALUE) } diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/MigrationCollection.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/MigrationCollection.kt index 1c7b9805..112f77f0 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/MigrationCollection.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/database/migrations/MigrationCollection.kt @@ -91,10 +91,9 @@ class MigrationCollection { * @return Unit */ @StoveDsl - suspend fun run(connection: TConnection): Unit = types - .map { - it.value - }.sortedBy { - it.order - }.forEach { it.execute(connection) } + suspend fun run(connection: TConnection): Unit = types.map { + it.value + }.sortedBy { + it.order + }.forEach { it.execute(connection) } } diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/messaging/Observation.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/messaging/Observation.kt index 0807e5ba..494b9a2a 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/messaging/Observation.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/messaging/Observation.kt @@ -2,6 +2,7 @@ package com.trendyol.stove.testing.e2e.messaging import arrow.core.Option import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import kotlin.reflect.KClass data class MessageMetadata( val topic: String, @@ -43,3 +44,8 @@ data class Failure( val message: ObservedMessage, val reason: Throwable ) + +data class MessagingAssertion( + val clazz: KClass, + val condition: (ParsedMessage) -> Boolean +) 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 new file mode 100644 index 00000000..ab3ff4c0 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/E2eObjectMapperConfig.kt @@ -0,0 +1,35 @@ +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 new file mode 100644 index 00000000..e793dba0 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/IsoInstantDeserializer.kt @@ -0,0 +1,46 @@ +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 new file mode 100644 index 00000000..a6d7cdf2 --- /dev/null +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/StoveObjectMapper.kt @@ -0,0 +1,11 @@ +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 deleted file mode 100644 index fcb34abf..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/gson.kt +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index 913738fa..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/jackson.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -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() - } - - 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 deleted file mode 100644 index cb964d40..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/kotlinx.kt +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 4c5cfa3a..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/serialization/serialization.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -import arrow.core.* - -/** - * 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 - - /** - * Deserializes data from the target format into the specified type. - * Returns an [Either] to indicate success or failure. - * @param value The serialized data to deserialize - * @param clazz The target class to deserialize into - * @return The deserialized object or a [StoveSerdeProblem] - */ - fun deserializeEither(value: TOut, clazz: Class): Either = Either - .catch { deserialize(value, clazz) } - .mapLeft { StoveSerdeProblem.BecauseOfDeserialization(it.message ?: "Deserialization failed", it) } - - /** - * Companion object containing default configurations and utility functions - */ - companion object { - val jackson = StoveJackson - - val gson = StoveGson - - val kotlinx = StoveKotlinx - - /** - * Deserializes data from the target format into the specified type. - */ - inline fun StoveSerde.deserialize( - value: ByteArray - ): T = deserialize(value, T::class.java) - - /** - * Deserializes data from the target format into the specified type. - * Returns a [None] if deserialization fails. - */ - inline fun StoveSerde.deserializeOption( - value: ByteArray - ): Option = deserializeEither(value, T::class.java).getOrNone() - - /** - * Deserializes data from the target format into the specified type. - */ - inline fun StoveSerde.deserialize( - value: String - ): T = deserialize(value, T::class.java) - - /** - * Deserializes data from the target format into the specified type. - * Returns a [None] if deserialization fails. - */ - inline fun StoveSerde.deserializeOption(value: String): Option = - deserializeEither(value, T::class.java).getOrNone() - } - - sealed class StoveSerdeProblem( - message: String, - cause: Throwable? = null - ) : RuntimeException(message, cause) { - class BecauseOfSerialization( - message: String, - cause: Throwable? = null - ) : StoveSerdeProblem(message, cause) - - class BecauseOfDeserialization( - message: String, - cause: Throwable? = null - ) : StoveSerdeProblem(message, cause) - - class BecauseOfDeserializationButExpected( - message: String, - cause: Throwable? = null - ) : StoveSerdeProblem(message, cause) - } -} diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/BridgeSystem.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/BridgeSystem.kt index f08451b1..82908f4a 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/BridgeSystem.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/BridgeSystem.kt @@ -11,10 +11,7 @@ import kotlin.reflect.KClass * @property testSystem the test system to bridge. */ @StoveDsl -abstract class BridgeSystem( - override val testSystem: TestSystem -) : PluggedSystem, - AfterRunAwareWithContext { +abstract class BridgeSystem(override val testSystem: TestSystem) : PluggedSystem, AfterRunAwareWithContext { /** * The application context used to resolve dependencies. */ @@ -124,8 +121,7 @@ inline fun < reified T1 : Any, reified T2 : Any, reified T3 : Any -> ValidationDsl.using(validation: (T1, T2, T3) -> Unit): Unit = this.testSystem - .bridge() +> ValidationDsl.using(validation: (T1, T2, T3) -> Unit): Unit = this.testSystem.bridge() .let { val t1: T1 = it.resolve() val t2: T2 = it.resolve() @@ -139,8 +135,7 @@ inline fun < reified T2 : Any, reified T3 : Any, reified T4 : Any -> ValidationDsl.using(validation: (T1, T2, T3, T4) -> Unit): Unit = this.testSystem - .bridge() +> ValidationDsl.using(validation: (T1, T2, T3, T4) -> Unit): Unit = this.testSystem.bridge() .let { val t1: T1 = it.resolve() val t2: T2 = it.resolve() @@ -156,8 +151,7 @@ inline fun < reified T3 : Any, reified T4 : Any, reified T5 : Any -> ValidationDsl.using(validation: (T1, T2, T3, T4, T5) -> Unit): Unit = this.testSystem - .bridge() +> ValidationDsl.using(validation: (T1, T2, T3, T4, T5) -> Unit): Unit = this.testSystem.bridge() .let { val t1: T1 = it.resolve() val t2: T2 = it.resolve() diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/PropertiesFile.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/PropertiesFile.kt index a787ff6b..6f8cce7b 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/PropertiesFile.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/PropertiesFile.kt @@ -18,8 +18,7 @@ class PropertiesFile { if (propertiesFilePath.exists()) { l.info("'.testcontainers.properties' file exists") when { - propertiesFilePath - .readText() + propertiesFilePath.readText() .contains(REUSE_ENABLED) -> l.info("'.testcontainers.properties' looks good and contains reuse feature!") else -> @@ -51,8 +50,7 @@ class PropertiesFile { !propertiesFilePath.exists() -> propertiesFilePath.writeText(REUSE_ENABLED) else -> when { - propertiesFilePath - .readText() + propertiesFilePath.readText() .contains(REUSE_ENABLED) -> l.info( "'.testcontainers.properties' looks good and contains reuse feature!" diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/TestSystem.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/TestSystem.kt index 8015be2a..bad00619 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/TestSystem.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/TestSystem.kt @@ -74,10 +74,7 @@ import kotlin.reflect.KClass * ``` */ @StoveDsl -class TestSystem( - configure: @StoveDsl TestSystemOptionsDsl.() -> Unit = {} -) : ReadyTestSystem, - AutoCloseable { +class TestSystem(configure: @StoveDsl TestSystemOptionsDsl.() -> Unit = {}) : ReadyTestSystem, AutoCloseable { private val optionsDsl: TestSystemOptionsDsl = TestSystemOptionsDsl() init { diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/ValidationDsl.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/ValidationDsl.kt index ce266f7d..40f22cca 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/ValidationDsl.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/ValidationDsl.kt @@ -6,6 +6,4 @@ import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl /** The wrapper class for DSLs that are used write validations against the [PluggedSystem]*/ @JvmInline @StoveDsl -value class ValidationDsl( - val testSystem: TestSystem -) +value class ValidationDsl(val testSystem: TestSystem) diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/WithDsl.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/WithDsl.kt index a688b20a..67c2bdc6 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/WithDsl.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/WithDsl.kt @@ -6,9 +6,7 @@ import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl /** The wrapper class for constructing the entire system with [PluggedSystem]s*/ @JvmInline @StoveDsl -value class WithDsl( - val testSystem: TestSystem -) { +value class WithDsl(val testSystem: TestSystem) { @StoveDsl fun applicationUnderTest(applicationUnderTest: ApplicationUnderTest<*>) { this.testSystem.applicationUnderTest(applicationUnderTest) diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem.kt index e5f122d2..f532e43a 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem.kt @@ -4,6 +4,4 @@ package com.trendyol.stove.testing.e2e.system.abstractions * Marks the dependency which can be plugged into the [TestSystem] * @author Oguzhan Soykan */ -interface PluggedSystem : - AutoCloseable, - ThenSystemContinuation +interface PluggedSystem : AutoCloseable, ThenSystemContinuation diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext.kt index 6535eca7..cbed7587 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext.kt @@ -39,11 +39,7 @@ interface AfterRunAware { /** * @author Oguzhan Soykan */ -interface RunnableSystemWithContext : - AutoCloseable, - BeforeRunAware, - RunAware, - AfterRunAwareWithContext { +interface RunnableSystemWithContext : AutoCloseable, BeforeRunAware, RunAware, AfterRunAwareWithContext { private val logger: Logger get() = LoggerFactory.getLogger(javaClass) override fun close(): Unit = runBlocking { Try { stop() }.recover { logger.warn("got an error while stopping") } } 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 a8dd321c..4a3474d2 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.* +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper 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 = StoveSerde.jackson.default + private val j = StoveObjectMapper.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 deleted file mode 100644 index 028fef85..00000000 --- a/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/serialization/SerializationTests.kt +++ /dev/null @@ -1,198 +0,0 @@ -package com.trendyol.stove.testing.e2e.serialization - -import arrow.core.None -import com.fasterxml.jackson.core.JsonParseException -import com.fasterxml.jackson.databind.MapperFeature -import com.google.gson.JsonSyntaxException -import com.trendyol.stove.testing.e2e.serialization.StoveSerde.Companion.deserialize -import com.trendyol.stove.testing.e2e.serialization.StoveSerde.Companion.deserializeOption -import com.trendyol.stove.testing.e2e.serialization.StoveSerde.StoveSerdeProblem -import io.kotest.assertions.arrow.core.shouldBeLeft -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.* -import io.kotest.matchers.types.shouldBeInstanceOf -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) - } - } - - test("should return None when invalid JSON is deserialized") { - val a = serializer.deserializeOption("invalid json") - a shouldBe None - } - - test("should return Left when invalid JSON is deserialized") { - val op = serializer.deserializeEither("invalid json", TestData::class.java) - op.shouldBeLeft() - op.value.message shouldContain "Unrecognized token 'invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')" - op.value.shouldBeInstanceOf() - } - } - - 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) - val deserializedTyped = serializer.deserialize(serialized) - - deserialized shouldBe testData - deserializedTyped 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/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/system/TestSystemOptionsDslTest.kt b/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/system/TestSystemOptionsDslTest.kt index bcda407f..a3e8c505 100644 --- a/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/system/TestSystemOptionsDslTest.kt +++ b/lib/stove-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/e2e/system/TestSystemOptionsDslTest.kt @@ -5,67 +5,64 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import kotlin.reflect.KClass -class TestSystemOptionsDslTest : - FunSpec({ - test("should keep dependencies running") { - val testSystemOptionsDsl = TestSystemOptionsDsl() - testSystemOptionsDsl.keepDependenciesRunning() +class TestSystemOptionsDslTest : FunSpec({ + test("should keep dependencies running") { + val testSystemOptionsDsl = TestSystemOptionsDsl() + testSystemOptionsDsl.keepDependenciesRunning() - testSystemOptionsDsl.options.keepDependenciesRunning shouldBe true - } + testSystemOptionsDsl.options.keepDependenciesRunning shouldBe true + } - test("should check if running locally") { - val testSystemOptionsDsl = TestSystemOptionsDsl() - testSystemOptionsDsl.isRunningLocally() shouldBe ( - System.getenv("CI") != "true" && - System.getenv("GITLAB_CI") != "true" && - System.getenv("GITHUB_ACTIONS") != "true" - ) - } + test("should check if running locally") { + val testSystemOptionsDsl = TestSystemOptionsDsl() + testSystemOptionsDsl.isRunningLocally() shouldBe ( + System.getenv("CI") != "true" && + System.getenv("GITLAB_CI") != "true" && + System.getenv("GITHUB_ACTIONS") != "true" + ) + } - test("should enable reuse for test containers") { - val testSystemOptionsDsl = TestSystemOptionsDsl() - testSystemOptionsDsl.enableReuseForTestContainers() - } + test("should enable reuse for test containers") { + val testSystemOptionsDsl = TestSystemOptionsDsl() + testSystemOptionsDsl.enableReuseForTestContainers() + } - test("should set state storage factory") { - val testSystemOptionsDsl = TestSystemOptionsDsl() + test("should set state storage factory") { + val testSystemOptionsDsl = TestSystemOptionsDsl() - class AnotherStateStorageFactory : StateStorageFactory { - override fun invoke( - options: TestSystemOptions, - system: KClass<*>, - state: KClass - ): StateStorage = object : StateStorage { - override suspend fun capture(start: suspend () -> T): T = start() + class AnotherStateStorageFactory : StateStorageFactory { + override fun invoke(options: TestSystemOptions, system: KClass<*>, state: KClass): StateStorage { + return object : StateStorage { + override suspend fun capture(start: suspend () -> T): T { + return start() + } - override fun isSubsequentRun(): Boolean = false + override fun isSubsequentRun(): Boolean { + return false + } } } + } - data class Example1ExposedState( - val id: Int = 1 - ) : ExposedConfiguration + data class Example1ExposedState(val id: Int = 1) : ExposedConfiguration - class Example1System( - override val testSystem: TestSystem - ) : PluggedSystem { - override fun close() = Unit - } + class Example1System(override val testSystem: TestSystem) : PluggedSystem { + override fun close() = Unit + } - val anotherStateStorageFactory = AnotherStateStorageFactory() - testSystemOptionsDsl.stateStorage(anotherStateStorageFactory) + val anotherStateStorageFactory = AnotherStateStorageFactory() + testSystemOptionsDsl.stateStorage(anotherStateStorageFactory) - testSystemOptionsDsl.options.stateStorageFactory shouldBe anotherStateStorageFactory - val storage = testSystemOptionsDsl.options.createStateStorage() - storage.isSubsequentRun() shouldBe false - storage.capture { Example1ExposedState() } shouldBe Example1ExposedState() - } + testSystemOptionsDsl.options.stateStorageFactory shouldBe anotherStateStorageFactory + val storage = testSystemOptionsDsl.options.createStateStorage() + storage.isSubsequentRun() shouldBe false + storage.capture { Example1ExposedState() } shouldBe Example1ExposedState() + } - test("should run migrations always") { - val testSystemOptionsDsl = TestSystemOptionsDsl() - testSystemOptionsDsl.runMigrationsAlways() + test("should run migrations always") { + val testSystemOptionsDsl = TestSystemOptionsDsl() + testSystemOptionsDsl.runMigrationsAlways() - testSystemOptionsDsl.options.runMigrationsAlways shouldBe true - } - }) + testSystemOptionsDsl.options.runMigrationsAlways shouldBe true + } +}) diff --git a/lib/stove-testing-e2e/src/testFixtures/kotlin/com/trendyol/stove/CapturedOutput.kt b/lib/stove-testing-e2e/src/testFixtures/kotlin/com/trendyol/stove/CapturedOutput.kt index d1c2c866..67415959 100644 --- a/lib/stove-testing-e2e/src/testFixtures/kotlin/com/trendyol/stove/CapturedOutput.kt +++ b/lib/stove-testing-e2e/src/testFixtures/kotlin/com/trendyol/stove/CapturedOutput.kt @@ -12,29 +12,27 @@ class CapturedOutput( val err: String get() = errBuffer.toString() } -abstract class ConsoleSpec( - body: ConsoleSpec.(CapturedOutput) -> Unit = {} -) : FunSpec({ - val originalOut = System.out - val originalErr = System.err - val outBuffer = ByteArrayOutputStream() - val errBuffer = ByteArrayOutputStream() - val capturedOutput = CapturedOutput(outBuffer, errBuffer) +abstract class ConsoleSpec(body: ConsoleSpec.(CapturedOutput) -> Unit = {}) : FunSpec({ + val originalOut = System.out + val originalErr = System.err + val outBuffer = ByteArrayOutputStream() + val errBuffer = ByteArrayOutputStream() + val capturedOutput = CapturedOutput(outBuffer, errBuffer) - beforeSpec { - System.setOut(PrintStream(outBuffer)) - System.setErr(PrintStream(outBuffer)) - } + beforeSpec { + System.setOut(PrintStream(outBuffer)) + System.setErr(PrintStream(outBuffer)) + } - afterSpec { - System.setOut(originalOut) - System.setOut(originalErr) - } + afterSpec { + System.setOut(originalOut) + System.setOut(originalErr) + } - beforeEach { - outBuffer.reset() - errBuffer.reset() - } + beforeEach { + outBuffer.reset() + errBuffer.reset() + } - body(this as ConsoleSpec, capturedOutput) - }) + body(this as ConsoleSpec, capturedOutput) +}) diff --git a/recipes/build.gradle.kts b/recipes/build.gradle.kts index 85187898..568301e5 100644 --- a/recipes/build.gradle.kts +++ b/recipes/build.gradle.kts @@ -25,25 +25,20 @@ subprojects { parallel = true config.from(rootProject.file("detekt.yml")) } - dependencies { detektPlugins(rootProject.libs.detekt.formatting) } spotless { java { - googleJavaFormat("1.25.0") + removeUnusedImports() + googleJavaFormat() targetExclude("build", "generated", "out") targetExcludeIfContentContains("generated") targetExcludeIfContentContainsRegex(".*generated.*") } - - scala { - scalafmt() - } - kotlin { - ktlint(libs.versions.ktlint.get()).setEditorConfigPath(rootProject.layout.projectDirectory.file(".editorconfig")) + ktlint().setEditorConfigPath(rootProject.layout.projectDirectory.file(".editorconfig")) targetExcludeIfContentContains("generated") } } @@ -56,6 +51,7 @@ subprojects { } tasks { + test { dependsOn(spotlessApply) useJUnitPlatform() @@ -71,6 +67,7 @@ subprojects { jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED") } + kotlin { jvmToolchain(21) } diff --git a/recipes/gradle/libs.versions.toml b/recipes/gradle/libs.versions.toml index c2a601d7..b5232103 100644 --- a/recipes/gradle/libs.versions.toml +++ b/recipes/gradle/libs.versions.toml @@ -1,10 +1,9 @@ [versions] -kotlin = "2.1.0" +kotlin = "2.0.21" kotlinx = "1.9.0" scala2x = "2.13.15" scala3x = "3.4.1" -quarkus = "3.17.3" -ktlint = "1.5.0" +quarkus = "3.17.0" # Spring-Boot spring-boot = "3.4.0" @@ -13,22 +12,22 @@ spring-dependency-management = "1.1.6" spring-kafka = "3.3.0" # arrow -arrow = "2.0.0" +arrow = "1.2.4" arrow-jackson = "0.14.1" arrowSuspendApp = "0.4.0" # Jackson -jackson = "2.18.2" +jackson = "2.18.1" # Couchbase -couchbase-kotlin = "1.4.6" -couchbase-client = "3.7.6" -couchbase-client-metrics = "0.7.6" +couchbase-kotlin = "1.4.5" +couchbase-client = "3.7.5" +couchbase-client-metrics = "0.7.5" # Kafka kafka = "3.9.0" kafka-kotlin = "0.4.0" -kafka-streams-registry = "7.8.0" +kafka-streams-registry = "7.7.1" # Reactor io-reactor = "3.7.0" @@ -36,7 +35,7 @@ io-reactor-extensions = "1.2.3" # Logging slf4j = "2.0.16" -kotlinLogging = "7.0.3" +kotlinLogging = "7.0.0" # Ktor ktor = "2.3.13" @@ -62,18 +61,18 @@ io-grpc-kotlin = "1.4.1" # Tooling kover = "0.8.3" -spotless = "7.0.0.BETA4" +spotless = "6.25.0" detekt = "1.23.7" lombok = "1.18.36" # Misc -hoplite = "2.9.0" +hoplite = "2.8.2" kediatr = "3.1.2" # Testing -stove = "1.0.0.454-SNAPSHOT" +stove = "1.0.0-SNAPSHOT" junit = "5.11.3" -kotest = "6.0.0.M1" +kotest = "5.9.1" #protobuf protobuf-plugin = "0.9.4" @@ -169,11 +168,10 @@ mongodb-kotlin-coroutine = { module = "org.mongodb:mongodb-driver-kotlin-corouti kotlin-logging-jvm = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlinLogging" } r2dbc-mssql = { module = "io.r2dbc:r2dbc-mssql", version.ref = "r2dbc-mssql" } -lettuce-core = { module = "io.lettuce:lettuce-core", version = "6.5.1.RELEASE" } +lettuce-core = { module = "io.lettuce:lettuce-core", version = "6.5.0.RELEASE" } logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.5.12" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } -ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } hoplite = { module = "com.sksamuel.hoplite:hoplite-core", version.ref = "hoplite" } hoplite-yaml = { module = "com.sksamuel.hoplite:hoplite-yaml", version.ref = "hoplite" } @@ -186,6 +184,7 @@ kediatr-koin = { module = "com.trendyol:kediatr-koin-starter", version.ref = "ke lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } # Testing + junit = { module = "org.junit:junit-bom", version.ref = "junit" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } @@ -218,6 +217,6 @@ kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } testLogger = { id = "com.adarshr.test-logger", version = "4.0.0" } -ksp = { id = "com.google.devtools.ksp", version = "2.1.0-1.0.29" } +ksp = { id = "com.google.devtools.ksp", version = "2.0.21-1.0.28" } quarkus = { id = "io.quarkus", version.ref = "quarkus" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin"} diff --git a/recipes/java-recipes/quarkus-recipe/build.gradle.kts b/recipes/java-recipes/quarkus-recipe/build.gradle.kts index 3fee566f..60c3a36f 100644 --- a/recipes/java-recipes/quarkus-recipe/build.gradle.kts +++ b/recipes/java-recipes/quarkus-recipe/build.gradle.kts @@ -24,8 +24,3 @@ dependencies { testImplementation(libs.stove.spring.testing) testImplementation(libs.jackson.kotlin) } - -tasks.e2eTest.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.recipes.quarkus.e2e.setup.Stove") -} - diff --git a/recipes/java-recipes/quarkus-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/quarkus/e2e/tests/IndexTests.kt b/recipes/java-recipes/quarkus-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/quarkus/e2e/tests/IndexTests.kt index f6f8641e..26225251 100644 --- a/recipes/java-recipes/quarkus-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/quarkus/e2e/tests/IndexTests.kt +++ b/recipes/java-recipes/quarkus-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/quarkus/e2e/tests/IndexTests.kt @@ -5,21 +5,20 @@ import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class IndexTests : - FunSpec({ - test("Index page should return 200") { - validate { - http { - get( - "/hello", - headers = mapOf( - "Content-Type" to "text/plain", - "Accept" to "text/plain" - ) - ) { actual -> - actual shouldBe "Hello from Quarkus REST" - } +class IndexTests : FunSpec({ + test("Index page should return 200") { + validate { + http { + get( + "/hello", + headers = mapOf( + "Content-Type" to "text/plain", + "Accept" to "text/plain" + ) + ) { actual -> + actual shouldBe "Hello from Quarkus REST" } } } - }) + } +}) diff --git a/recipes/java-recipes/spring-boot-recipe/build.gradle.kts b/recipes/java-recipes/spring-boot-recipe/build.gradle.kts index 4d178617..f313df5c 100644 --- a/recipes/java-recipes/spring-boot-recipe/build.gradle.kts +++ b/recipes/java-recipes/spring-boot-recipe/build.gradle.kts @@ -28,7 +28,3 @@ dependencies { testImplementation(libs.stove.spring.testing) testImplementation(libs.jackson.kotlin) } - -tasks.e2eTest.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.example.java.spring.e2e.setup.Stove") -} diff --git a/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaBeanConfiguration.java b/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaBeanConfiguration.java index fecf759d..b12cc70e 100644 --- a/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaBeanConfiguration.java +++ b/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaBeanConfiguration.java @@ -15,9 +15,6 @@ import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.listener.DefaultErrorHandler; -import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer; -import org.springframework.kafka.support.serializer.JsonDeserializer; -import org.springframework.kafka.support.serializer.JsonSerializer; import org.springframework.util.backoff.FixedBackOff; @Configuration @@ -54,16 +51,10 @@ public Properties consumerProperties(KafkaConfiguration kafkaConfiguration) { ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG, kafkaConfiguration.isAutoCreateTopics()); properties.put( ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, kafkaConfiguration.getAutoOffsetReset()); - properties.put( ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); properties.put( - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class.getName()); - properties.put( - ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class.getName()); - properties.put(JsonDeserializer.TRUSTED_PACKAGES, "*"); - properties.put(JsonDeserializer.VALUE_DEFAULT_TYPE, Object.class.getName()); - + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); properties.put( ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, kafkaConfiguration.flattenInterceptorClasses()); logger.info("Kafka consumer properties: {}", properties); @@ -76,7 +67,7 @@ public Properties producerProperties(KafkaConfiguration kafkaConfiguration) { properties.put( ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfiguration.getBootstrapServers()); properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class.getName()); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); properties.put( ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, kafkaConfiguration.flattenInterceptorClasses()); return properties; diff --git a/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaDomainEventPublisher.java b/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaDomainEventPublisher.java index 838bad2e..c1737c5f 100644 --- a/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaDomainEventPublisher.java +++ b/recipes/java-recipes/spring-boot-recipe/src/main/java/com/trendyol/stove/examples/java/spring/infra/boilerplate/kafka/KafkaDomainEventPublisher.java @@ -1,10 +1,13 @@ package com.trendyol.stove.examples.java.spring.infra.boilerplate.kafka; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.trendyol.stove.examples.domain.ddd.AggregateRoot; import com.trendyol.stove.examples.domain.ddd.EventPublisher; import java.util.stream.Stream; import org.apache.kafka.clients.producer.ProducerRecord; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; @@ -12,12 +15,16 @@ public class KafkaDomainEventPublisher implements EventPublisher { private final KafkaTemplate template; private final TopicResolver topicResolver; + private final ObjectMapper objectMapper; private final Logger logger = org.slf4j.LoggerFactory.getLogger(KafkaDomainEventPublisher.class); public KafkaDomainEventPublisher( - KafkaTemplate template, TopicResolver topicResolver) { + KafkaTemplate template, + TopicResolver topicResolver, + @Qualifier("objectMapper") ObjectMapper objectMapper) { this.template = template; this.topicResolver = topicResolver; + this.objectMapper = objectMapper; } @Override @@ -31,8 +38,15 @@ private Stream> mapEventsToProducerRecords( .map( event -> { var topic = topicResolver.resolve(aggregateRoot.getAggregateName()); - logger.info("Publishing event {} to topic {}", event, topic.getName()); - return new ProducerRecord<>(topic.getName(), aggregateRoot.getIdAsString(), event); + try { + logger.info("Publishing event {} to topic {}", event, topic.getName()); + return new ProducerRecord<>( + topic.getName(), + aggregateRoot.getIdAsString(), + objectMapper.writeValueAsString(event)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } }); } } diff --git a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/setup/Stove.kt b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/setup/Stove.kt index eb242fd4..4a7f4b45 100644 --- a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/setup/Stove.kt +++ b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/setup/Stove.kt @@ -1,6 +1,5 @@ package com.trendyol.stove.example.java.spring.e2e.setup -import com.couchbase.client.kotlin.codec.JacksonJsonSerializer import com.trendyol.stove.example.java.spring.e2e.setup.migrations.CouchbaseMigration import com.trendyol.stove.examples.java.spring.ExampleSpringBootApp import com.trendyol.stove.examples.java.spring.infra.boilerplate.serialization.JacksonConfiguration @@ -8,77 +7,72 @@ import com.trendyol.stove.examples.java.spring.infra.components.product.persiste import com.trendyol.stove.testing.e2e.* import com.trendyol.stove.testing.e2e.couchbase.* import com.trendyol.stove.testing.e2e.http.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.standalone.kafka.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.wiremock.* import io.kotest.core.config.AbstractProjectConfig -import io.ktor.serialization.jackson.* -import org.springframework.kafka.support.serializer.JsonSerializer class Stove : AbstractProjectConfig() { override suspend fun beforeProject() { - TestSystem() - .with { - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8080", - contentConverter = JacksonConverter(JacksonConfiguration.defaultObjectMapper()) - ) - } + TestSystem().with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8080", + objectMapper = JacksonConfiguration.defaultObjectMapper() + ) + } - bridge() - wiremock { - WireMockSystemOptions( - port = 9090, - serde = StoveSerde.jackson.anyByteArraySerde(JacksonConfiguration.defaultObjectMapper()) - ) - } - couchbase { - CouchbaseSystemOptions( - defaultBucket = "stove-java-spring-boot", - CouchbaseContainerOptions { - withStartupAttempts(3) - dockerImageName = "couchbase/server:7.2.5" - }, - clusterSerDe = JacksonJsonSerializer(JacksonConfiguration.defaultObjectMapper()), - configureExposedConfiguration = { cfg -> - listOf( - "couchbase.bucket=${CollectionConstants.BUCKET_NAME}", - "couchbase.username=${cfg.username}", - "couchbase.password=${cfg.password}", - "couchbase.hosts=${cfg.hostsWithPort}", - "couchbase.timeout=600" - ) - } - ).migrations { - register() + bridge() + wiremock { + WireMockSystemOptions( + port = 9090, + objectMapper = JacksonConfiguration.defaultObjectMapper() + ) + } + couchbase { + CouchbaseSystemOptions( + defaultBucket = "stove-java-spring-boot", + CouchbaseContainerOptions { + withStartupAttempts(3) + dockerImageName = "couchbase/server:7.2.5" + }, + objectMapper = JacksonConfiguration.defaultObjectMapper(), + configureExposedConfiguration = { cfg -> + listOf( + "couchbase.bucket=${CollectionConstants.BUCKET_NAME}", + "couchbase.username=${cfg.username}", + "couchbase.password=${cfg.password}", + "couchbase.hosts=${cfg.hostsWithPort}", + "couchbase.timeout=600" + ) } + ).migrations { + register() } + } - kafka { - KafkaSystemOptions( - serde = StoveSerde.jackson.anyByteArraySerde(JacksonConfiguration.defaultObjectMapper()), - valueSerializer = JsonSerializer(JacksonConfiguration.defaultObjectMapper()), - containerOptions = KafkaContainerOptions { - withStartupAttempts(3) - }, - configureExposedConfiguration = { - listOf( - "kafka.bootstrap-servers=${it.bootstrapServers}", - "kafka.interceptor-classes=${it.interceptorClass}" - ) - } - ) - } - springBoot( - runner = { parameters -> - ExampleSpringBootApp.run(parameters) { - } + kafka { + KafkaSystemOptions( + objectMapper = JacksonConfiguration.defaultObjectMapper(), + containerOptions = KafkaContainerOptions { + withStartupAttempts(3) }, - withParameters = listOf() + configureExposedConfiguration = { + listOf( + "kafka.bootstrap-servers=${it.bootstrapServers}", + "kafka.interceptor-classes=${it.interceptorClass}" + ) + } ) - }.run() + } + springBoot( + runner = { parameters -> + ExampleSpringBootApp.run(parameters) { + } + }, + withParameters = listOf() + ) + }.run() } override suspend fun afterProject() { diff --git a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/IndexTests.kt b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/IndexTests.kt index 480cf818..5121575a 100644 --- a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/IndexTests.kt +++ b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/IndexTests.kt @@ -5,15 +5,14 @@ import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class IndexTests : - FunSpec({ - test("Index page should be accessible") { - validate { - http { - get("/") { actual -> - actual shouldBe "Hello, World!" - } +class IndexTests : FunSpec({ + test("Index page should be accessible") { + validate { + http { + get("/") { actual -> + actual shouldBe "Hello, World!" } } } - }) + } +}) diff --git a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/product/CreateTests.kt b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/product/CreateTests.kt index a07490e8..7385e276 100644 --- a/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/product/CreateTests.kt +++ b/recipes/java-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/example/java/spring/e2e/tests/product/CreateTests.kt @@ -20,88 +20,88 @@ import org.springframework.http.HttpStatus import java.util.* import kotlin.time.Duration.Companion.seconds -class CreateTests : - FunSpec({ - test("product can be created with valid category") { - validate { - val productName = TestData.Random.positiveInt().toString() - val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) +class CreateTests : FunSpec({ + test("product can be created with valid category") { + validate { + val productName = TestData.Random.positiveInt().toString() + val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) - val categoryApiResponse = CategoryApiResponse( - TestData.Random.positiveInt(), - "category-name", - true + val categoryApiResponse = CategoryApiResponse( + TestData.Random.positiveInt(), + "category-name", + true + ) + + wiremock { + mockGet( + url = "/categories/${categoryApiResponse.id}", + statusCode = 200, + responseBody = categoryApiResponse.some() ) + } - wiremock { - mockGet( - url = "/categories/${categoryApiResponse.id}", - statusCode = 200, - responseBody = categoryApiResponse.some() - ) + http { + val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) + postAndExpectBody("/products", body = req.some()) { actual -> + actual.status shouldBe 200 } + } - http { - val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) - postAndExpectBody("/products", body = req.some()) { actual -> - actual.status shouldBe 200 - } + couchbase { + shouldGet(CollectionConstants.PRODUCT_COLLECTION, productId.toString()) { actual -> + actual.id shouldBe productId.toString() + actual.name shouldBe productName + actual.price shouldBe 100.0 } + } - couchbase { - shouldGet(CollectionConstants.PRODUCT_COLLECTION, productId.toString()) { actual -> - actual.id shouldBe productId.toString() - actual.name shouldBe productName - actual.price shouldBe 100.0 - } - } + using { + val product = findById(productId.toString()).toFuture().get() + product.name shouldBe productName + product.price shouldBe 100.0 + product.categoryId shouldBe categoryApiResponse.id + } - using { - val product = findById(productId.toString()).toFuture().get() - product.name shouldBe productName - product.price shouldBe 100.0 - product.categoryId shouldBe categoryApiResponse.id + kafka { + shouldBePublished(10.seconds) { + actual.price == 100.0 && actual.name == productName } - kafka { - shouldBePublished(10.seconds) { - actual.price == 100.0 && actual.name == productName - } - shouldBeConsumed { - actual.price == 100.0 && actual.name == productName - } + shouldBeConsumed { + actual.price == 100.0 && actual.name == productName } } } + } - test("when category is not active, product creation should fail") { - validate { - val productName = TestData.Random.positiveInt().toString() - val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) - val categoryApiResponse = CategoryApiResponse( - TestData.Random.positiveInt(), - "category-name", - false - ) + test("when category is not active, product creation should fail") { + validate { + val productName = TestData.Random.positiveInt().toString() + val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) + val categoryApiResponse = CategoryApiResponse( + TestData.Random.positiveInt(), + "category-name", + false + ) - wiremock { - mockGet( - url = "/categories/${categoryApiResponse.id}", - statusCode = 200 - ) - } + wiremock { + mockGet( + url = "/categories/${categoryApiResponse.id}", + statusCode = 200 + ) + } - http { - val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) - postAndExpectBody("/products", body = req.some()) { actual -> - actual.status shouldBe HttpStatus.CONFLICT.value() - } + http { + val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) + postAndExpectBody("/products", body = req.some()) { actual -> + actual.status shouldBe HttpStatus.CONFLICT.value() } + } - couchbase { - shouldNotExist(CollectionConstants.PRODUCT_COLLECTION, productId.toString()) - } + couchbase { + shouldNotExist(CollectionConstants.PRODUCT_COLLECTION, productId.toString()) } } - }) + } +}) diff --git a/recipes/kotlin-recipes/ktor-recipe/build.gradle.kts b/recipes/kotlin-recipes/ktor-recipe/build.gradle.kts index ad147de8..8c4e647f 100644 --- a/recipes/kotlin-recipes/ktor-recipe/build.gradle.kts +++ b/recipes/kotlin-recipes/ktor-recipe/build.gradle.kts @@ -47,7 +47,4 @@ dependencies { testImplementation(libs.jackson.kotlin) } -tasks.e2eTest.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.examples.kotlin.ktor.e2e.setup.Stove") -} diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/application/external/CategoryHttpApiImpl.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/application/external/CategoryHttpApiImpl.kt index 9fc89919..928120b7 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/application/external/CategoryHttpApiImpl.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/application/external/CategoryHttpApiImpl.kt @@ -12,13 +12,16 @@ class CategoryHttpApiImpl( private val httpClient: HttpClient, private val categoryApiConfiguration: CategoryApiConfiguration ) : CategoryHttpApi { - override suspend fun getCategory(id: Int): CategoryApiResponse = httpClient - .get("${categoryApiConfiguration.url}/categories/$id") { - accept(ContentType.Application.Json) - timeout { - requestTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds - connectTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds - socketTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds + override suspend fun getCategory(id: Int): CategoryApiResponse { + return httpClient + .get("${categoryApiConfiguration.url}/categories/$id") { + accept(ContentType.Application.Json) + timeout { + requestTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds + connectTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds + socketTimeoutMillis = categoryApiConfiguration.timeout.seconds.inWholeMilliseconds + } } - }.body() + .body() + } } diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/ConsumerSupervisor.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/ConsumerSupervisor.kt index ee477f84..05097fa9 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/ConsumerSupervisor.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/ConsumerSupervisor.kt @@ -23,8 +23,7 @@ abstract class ConsumerSupervisor( @OptIn(ExperimentalCoroutinesApi::class) @Suppress("TooGenericExceptionCaught") private suspend fun subscribe() { - kafkaReceiver - .receiveAutoAck(topics) + kafkaReceiver.receiveAutoAck(topics) .flattenMerge(maxConcurrency) .collect { try { diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/KafkaDomainEventPublisher.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/KafkaDomainEventPublisher.kt index 7aa35f23..d8c8d554 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/KafkaDomainEventPublisher.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/KafkaDomainEventPublisher.kt @@ -1,5 +1,6 @@ package com.trendyol.stove.examples.kotlin.ktor.infra.boilerplate.kafka +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.examples.domain.ddd.* import io.github.nomisRev.kafka.publisher.KafkaPublisher import kotlinx.coroutines.runBlocking @@ -8,7 +9,8 @@ import org.slf4j.* class KafkaDomainEventPublisher( private val publisher: KafkaPublisher, - private val topicResolver: TopicResolver + private val topicResolver: TopicResolver, + private val objectMapper: ObjectMapper ) : EventPublisher { private val logger: Logger = LoggerFactory.getLogger(KafkaDomainEventPublisher::class.java) @@ -19,15 +21,14 @@ class KafkaDomainEventPublisher( private fun mapEventsToProducerRecords( aggregateRoot: AggregateRoot - ): List> = aggregateRoot - .domainEvents() + ): List> = aggregateRoot.domainEvents() .map { event -> val topic: Topic = topicResolver(aggregateRoot.aggregateName) logger.info("Publishing event {} to topic {}", event, topic.name) ProducerRecord( topic.name, aggregateRoot.idAsString, - event + objectMapper.writeValueAsString(event) ) } } diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/TopicResolver.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/TopicResolver.kt index 6952c242..7e4ce043 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/TopicResolver.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/TopicResolver.kt @@ -2,8 +2,6 @@ package com.trendyol.stove.examples.kotlin.ktor.infra.boilerplate.kafka import com.trendyol.stove.examples.kotlin.ktor.application.* -class TopicResolver( - private val kafkaConfiguration: KafkaConfiguration -) { +class TopicResolver(private val kafkaConfiguration: KafkaConfiguration) { operator fun invoke(aggregateName: String): Topic = kafkaConfiguration.topics.getValue(aggregateName) } diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/kafka.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/kafka.kt index fdf1e81b..3ccd5142 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/kafka.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/kafka/kafka.kt @@ -22,7 +22,7 @@ fun KoinApplication.registerKafka(kafkaConfiguration: KafkaConfiguration) { single { kafkaPublisher(get()) } single { kafkaReceiver(get()) } single { ConsumerEngine(getAll()) } - single { KafkaDomainEventPublisher(get(), get()) }.bind() + single { KafkaDomainEventPublisher(get(), get(), get()) }.bind() single { TopicResolver(get()) } } ) @@ -74,8 +74,7 @@ private fun kafkaReceiver( putAll( mapOf( ConsumerConfig.ALLOW_AUTO_CREATE_TOPICS_CONFIG to kafkaConfiguration.autoCreateTopics.toString(), - ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to kafkaConfiguration.heartbeatIntervalSeconds.seconds.inWholeMilliseconds - .toInt(), + ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to kafkaConfiguration.heartbeatIntervalSeconds.seconds.inWholeMilliseconds.toInt(), ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG to kafkaConfiguration.flattenInterceptorClasses() ) ) diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/mongo/mongo.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/mongo/mongo.kt index c82c34a5..ef369bd4 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/mongo/mongo.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/mongo/mongo.kt @@ -16,15 +16,16 @@ private fun createMongoModule() = module { single { createMongoDatabase(get(), get()) } } -private fun createMongoClient(recipeAppConfig: RecipeAppConfig): MongoClient = MongoClient.create( - MongoClientSettings - .builder() - .uuidRepresentation(UuidRepresentation.STANDARD) - .applyConnectionString(ConnectionString(recipeAppConfig.mongo.uri)) - .readConcern(ReadConcern.MAJORITY) - .build() -) +private fun createMongoClient(recipeAppConfig: RecipeAppConfig): MongoClient { + return MongoClient.create( + MongoClientSettings.builder() + .uuidRepresentation(UuidRepresentation.STANDARD) + .applyConnectionString(ConnectionString(recipeAppConfig.mongo.uri)) + .readConcern(ReadConcern.MAJORITY) + .build() + ) +} -private fun createMongoDatabase(mongoClient: MongoClient, recipeAppConfig: RecipeAppConfig): MongoDatabase = mongoClient.getDatabase( - recipeAppConfig.mongo.database -) +private fun createMongoDatabase(mongoClient: MongoClient, recipeAppConfig: RecipeAppConfig): MongoDatabase { + return mongoClient.getDatabase(recipeAppConfig.mongo.database) +} diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/serialization/JacksonConfiguration.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/serialization/JacksonConfiguration.kt index f340cad3..497d2e29 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/serialization/JacksonConfiguration.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/serialization/JacksonConfiguration.kt @@ -11,8 +11,7 @@ import org.koin.dsl.module import org.koin.ktor.ext.inject object JacksonConfiguration { - val default: ObjectMapper = JsonMapper - .builder() + val default: ObjectMapper = JsonMapper.builder() .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/util.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/util.kt index fb2a1484..89414cf8 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/util.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/boilerplate/util.kt @@ -15,8 +15,7 @@ import io.ktor.server.response.* import org.slf4j.LoggerFactory @OptIn(ExperimentalHoplite::class) -inline fun loadConfiguration(args: Array = arrayOf()): T = ConfigLoaderBuilder - .default() +inline fun loadConfiguration(args: Array = arrayOf()): T = ConfigLoaderBuilder.default() .addEnvironmentSource() .addCommandLineSource(args) .withExplicitSealedTypes() @@ -36,12 +35,11 @@ inline fun loadConfiguration(args: Array = arrayOf()): addResourceSource("/application.yaml", optional = true) } } - }.build() + } + .build() .loadConfigOrThrow() -enum class AppEnv( - val env: String -) { +enum class AppEnv(val env: String) { Unspecified(""), Local(Environment.local.name), Prod(Environment.prod.name) @@ -62,15 +60,17 @@ enum class AppEnv( } } - fun isLocal(): Boolean = this === Local + fun isLocal(): Boolean { + return this === Local + } - fun isProd(): Boolean = this === Prod + fun isProd(): Boolean { + return this === Prod + } } fun startKtorApplication(config: RecipeAppConfig, wait: Boolean = true, configure: Application.() -> Unit): Application { - val loggerName = configure.javaClass.name - .split('$') - .first() + val loggerName = configure.javaClass.name.split('$').first() val server = embeddedServer( Netty, diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/api/routing.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/api/routing.kt index 18e3e736..ab661a82 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/api/routing.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/api/routing.kt @@ -17,8 +17,4 @@ fun Routing.productApi() { } } -data class ProductCreateRequest( - var name: String, - var price: Double, - var categoryId: Int -) +data class ProductCreateRequest(var name: String, var price: Double, var categoryId: Int) diff --git a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/persistency/MongoProductRepository.kt b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/persistency/MongoProductRepository.kt index 14169fe7..c05c1979 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/persistency/MongoProductRepository.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/ktor/infra/components/product/persistency/MongoProductRepository.kt @@ -27,11 +27,11 @@ class MongoProductRepository( eventPublisher.publishFor(product) } - override suspend fun findById(id: String): Option = collection - .find(Filters.eq("id", id)) - .firstOrNull() - ?.let { objectMapper.convertValue(it, Product::class.java) } - .toOption() + override suspend fun findById(id: String): Option { + return collection.find(Filters.eq("id", id)) + .firstOrNull()?.let { objectMapper.convertValue(it, Product::class.java) } + .toOption() + } companion object { private const val RESERVED_ID = "_id" @@ -40,8 +40,7 @@ class MongoProductRepository( } object MongoJsonWriterSettings { - val default: JsonWriterSettings = JsonWriterSettings - .builder() + val default: JsonWriterSettings = JsonWriterSettings.builder() .objectIdConverter { value, writer -> writer.writeString(value.toHexString()) } .build() } diff --git a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/setup/Stove.kt b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/setup/Stove.kt index 31c00f48..458a3a6a 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/setup/Stove.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/setup/Stove.kt @@ -6,69 +6,66 @@ import com.trendyol.stove.examples.kotlin.ktor.infra.components.product.persiste import com.trendyol.stove.testing.e2e.* import com.trendyol.stove.testing.e2e.http.* import com.trendyol.stove.testing.e2e.mongodb.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import com.trendyol.stove.testing.e2e.standalone.kafka.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.wiremock.* import io.kotest.core.config.AbstractProjectConfig -import io.ktor.serialization.jackson.* import org.koin.dsl.module -private const val DATABASE = "stove-kotlin-ktor" +private val database = "stove-kotlin-ktor" class Stove : AbstractProjectConfig() { override suspend fun beforeProject() { - TestSystem() - .with { - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8081", - contentConverter = JacksonConverter(JacksonConfiguration.default) - ) - } - bridge() - wiremock { - WireMockSystemOptions( - port = 9090 - ) - } - kafka { - KafkaSystemOptions( - serde = StoveSerde.jackson.anyByteArraySerde(JacksonConfiguration.default), - configureExposedConfiguration = { cfg -> - listOf( - "kafka.bootstrapServers=${cfg.bootstrapServers}", - "kafka.interceptor-classes=${cfg.interceptorClass}" - ) - } - ) - } - mongodb { - MongodbSystemOptions( - databaseOptions = DatabaseOptions(DatabaseOptions.DefaultDatabase(DATABASE, collection = PRODUCT_COLLECTION)), - container = MongoContainerOptions(), - configureExposedConfiguration = { cfg -> - listOf( - "mongo.database=$DATABASE", - "mongo.uri=${cfg.connectionString}/?retryWrites=true&w=majority" - ) - } - ) - } - ktor( - runner = { parameters -> - ExampleStoveKtorApp.run( - parameters, - wait = false, - module { - } + TestSystem().with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8081", + objectMapper = JacksonConfiguration.default + ) + } + bridge() + wiremock { + WireMockSystemOptions( + port = 9090 + ) + } + kafka { + KafkaSystemOptions( + configureExposedConfiguration = { cfg -> + listOf( + "kafka.bootstrapServers=${cfg.bootstrapServers}", + "kafka.interceptor-classes=${cfg.interceptorClass}" + ) + } + ) + } + mongodb { + MongodbSystemOptions( + databaseOptions = DatabaseOptions(DatabaseOptions.DefaultDatabase(database, collection = PRODUCT_COLLECTION)), + container = MongoContainerOptions(), + objectMapper = JacksonConfiguration.default, + configureExposedConfiguration = { cfg -> + listOf( + "mongo.database=$database", + "mongo.uri=${cfg.connectionString}/?retryWrites=true&w=majority" ) - }, - withParameters = listOf( - "server.name=${Thread.currentThread().name}" + } + ) + } + ktor( + runner = { parameters -> + ExampleStoveKtorApp.run( + parameters, + wait = false, + module { + } ) + }, + withParameters = listOf( + "server.name=${Thread.currentThread().name}" ) - }.run() + ) + }.run() } override suspend fun afterProject() { diff --git a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/IndexTests.kt b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/IndexTests.kt index 8c7c464b..3cb73962 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/IndexTests.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/IndexTests.kt @@ -5,16 +5,15 @@ import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class IndexTests : - FunSpec({ - test("Index page should return 200") { - validate { - http { - getResponse("/") { actual -> - actual.status shouldBe 200 - actual.body() shouldBe "Hello, World!" - } +class IndexTests : FunSpec({ + test("Index page should return 200") { + validate { + http { + getResponse("/") { actual -> + actual.status shouldBe 200 + actual.body() shouldBe "Hello, World!" } } } - }) + } +}) diff --git a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/configuration/ConfigurationTests.kt b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/configuration/ConfigurationTests.kt index baa72965..456d9b22 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/configuration/ConfigurationTests.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/configuration/ConfigurationTests.kt @@ -6,13 +6,12 @@ import com.trendyol.stove.testing.e2e.system.using import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldNotBe -class ConfigurationTests : - FunSpec({ - test("configuration can be changed from app") { - validate { - using { - this.server.name shouldNotBe "test" - } +class ConfigurationTests : FunSpec({ + test("configuration can be changed from app") { + validate { + using { + this.server.name shouldNotBe "test" } } - }) + } +}) diff --git a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/product/CreateTests.kt b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/product/CreateTests.kt index 028d5328..b51a1c5e 100644 --- a/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/product/CreateTests.kt +++ b/recipes/kotlin-recipes/ktor-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/ktor/e2e/tests/product/CreateTests.kt @@ -20,95 +20,94 @@ import io.kotest.matchers.shouldBe import java.util.* import kotlin.time.Duration.Companion.seconds -class CreateTests : - FunSpec({ - test("product can be created with valid category") { - validate { - val productName = TestData.Random.positiveInt().toString() - val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) +class CreateTests : FunSpec({ + test("product can be created with valid category") { + validate { + val productName = TestData.Random.positiveInt().toString() + val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) - val categoryApiResponse = CategoryApiResponse( - TestData.Random.positiveInt(), - "category-name", - true + val categoryApiResponse = CategoryApiResponse( + TestData.Random.positiveInt(), + "category-name", + true + ) + + wiremock { + mockGet( + url = "/categories/${categoryApiResponse.id}", + statusCode = 200, + responseBody = categoryApiResponse.some() ) + } - wiremock { - mockGet( - url = "/categories/${categoryApiResponse.id}", - statusCode = 200, - responseBody = categoryApiResponse.some() - ) + http { + val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) + postAndExpectBody("/products", body = req.some()) { actual -> + actual.status shouldBe 200 } + } - http { - val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) - postAndExpectBody("/products", body = req.some()) { actual -> - actual.status shouldBe 200 - } + mongodb { + shouldQuery(Filters.eq("id", productId.toString()).toBsonDocument().toJson()) { actual -> + actual.size shouldBe 1 + actual[0].name shouldBe productName + actual[0].price shouldBe 100.0 } + } - mongodb { - shouldQuery(Filters.eq("id", productId.toString()).toBsonDocument().toJson()) { actual -> - actual.size shouldBe 1 - actual[0].name shouldBe productName - actual[0].price shouldBe 100.0 - } - } + using { + val product = findById(productId.toString()).get() + product.name shouldBe productName + product.price shouldBe 100.0 + product.categoryId shouldBe categoryApiResponse.id + } - using { - val product = findById(productId.toString()).get() - product.name shouldBe productName - product.price shouldBe 100.0 - product.categoryId shouldBe categoryApiResponse.id + kafka { + shouldBePublished(10.seconds) { + actual.price == 100.0 && actual.name == productName } - kafka { - shouldBePublished(10.seconds) { - actual.price == 100.0 && actual.name == productName - } - - shouldBeConsumed(10.seconds) { - actual.price == 100.0 && actual.name == productName - } + shouldBeConsumed(10.seconds) { + actual.price == 100.0 && actual.name == productName } } } + } - test("when category is not active, product creation should fail") { - validate { - val productName = TestData.Random.positiveInt().toString() - val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) - val categoryApiResponse = CategoryApiResponse( - TestData.Random.positiveInt(), - "category-name", - false - ) + test("when category is not active, product creation should fail") { + validate { + val productName = TestData.Random.positiveInt().toString() + val productId = UUID.nameUUIDFromBytes(productName.toByteArray()) + val categoryApiResponse = CategoryApiResponse( + TestData.Random.positiveInt(), + "category-name", + false + ) - wiremock { - mockGet( - url = "/categories/${categoryApiResponse.id}", - statusCode = 200, - responseBody = categoryApiResponse.some() - ) - } + wiremock { + mockGet( + url = "/categories/${categoryApiResponse.id}", + statusCode = 200, + responseBody = categoryApiResponse.some() + ) + } - http { - val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) - postAndExpectBody("/products", body = req.some()) { actual -> - actual.status shouldBe 409 - } + http { + val req = ProductCreateRequest(productName, 100.0, categoryApiResponse.id) + postAndExpectBody("/products", body = req.some()) { actual -> + actual.status shouldBe 409 } + } - mongodb { - shouldQuery(Filters.eq("id", productId.toString()).toBsonDocument().toJson()) { actual -> - actual.size shouldBe 0 - } + mongodb { + shouldQuery(Filters.eq("id", productId.toString()).toBsonDocument().toJson()) { actual -> + actual.size shouldBe 0 } + } - using { - findById(productId.toString()) shouldBe None - } + using { + findById(productId.toString()) shouldBe None } } - }) + } +}) diff --git a/recipes/kotlin-recipes/spring-boot-recipe/build.gradle.kts b/recipes/kotlin-recipes/spring-boot-recipe/build.gradle.kts index bf909608..bc774f60 100644 --- a/recipes/kotlin-recipes/spring-boot-recipe/build.gradle.kts +++ b/recipes/kotlin-recipes/spring-boot-recipe/build.gradle.kts @@ -28,7 +28,3 @@ dependencies { testImplementation(libs.ktor.client.content.negotiation) testImplementation(libs.ktor.serialization.jackson.json) } - -tasks.e2eTest.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.examples.kotlin.spring.e2e.setup.Stove") -} diff --git a/recipes/kotlin-recipes/spring-boot-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/spring/ExampleStoveSpringBootApp.kt b/recipes/kotlin-recipes/spring-boot-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/spring/ExampleStoveSpringBootApp.kt index fdd7946c..c52edfc1 100644 --- a/recipes/kotlin-recipes/spring-boot-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/spring/ExampleStoveSpringBootApp.kt +++ b/recipes/kotlin-recipes/spring-boot-recipe/src/main/kotlin/com/trendyol/stove/examples/kotlin/spring/ExampleStoveSpringBootApp.kt @@ -20,10 +20,7 @@ fun run(args: Array, init: SpringApplication.() -> Unit = {}): Configura init() } -data class ExampleData( - val id: Int, - val name: String -) +data class ExampleData(val id: Int, val name: String) @RestController @RequestMapping("/api/streaming") diff --git a/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/setup/Stove.kt b/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/setup/Stove.kt index 55558d3c..f655ac10 100644 --- a/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/setup/Stove.kt +++ b/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/setup/Stove.kt @@ -7,23 +7,21 @@ import io.kotest.core.config.AbstractProjectConfig class Stove : AbstractProjectConfig() { override suspend fun beforeProject() { - TestSystem() - .with { - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8024" - ) - } - springBoot( - runner = { params -> - com.trendyol.stove.examples.kotlin.spring - .run(params) - }, - withParameters = listOf( - "server.port=8024" - ) + TestSystem().with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8024" ) - }.run() + } + springBoot( + runner = { params -> + com.trendyol.stove.examples.kotlin.spring.run(params) + }, + withParameters = listOf( + "server.port=8024" + ) + ) + }.run() } override suspend fun afterProject() { diff --git a/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/tests/StreamingTests.kt b/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/tests/StreamingTests.kt index ab5120bd..cac930ef 100644 --- a/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/tests/StreamingTests.kt +++ b/recipes/kotlin-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/examples/kotlin/spring/e2e/tests/StreamingTests.kt @@ -1,9 +1,9 @@ package com.trendyol.stove.examples.kotlin.spring.e2e.tests +import com.fasterxml.jackson.module.kotlin.readValue import com.trendyol.stove.examples.kotlin.spring.ExampleData import com.trendyol.stove.testing.e2e.http.http -import com.trendyol.stove.testing.e2e.serialization.StoveSerde -import com.trendyol.stove.testing.e2e.serialization.StoveSerde.Companion.deserialize +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.spec.style.FunSpec import io.ktor.client.* @@ -20,29 +20,27 @@ import java.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration -class StreamingTests : - FunSpec({ - test("streaming") { - validate { - http { - streamClient() - .prepareGet { - url("http://localhost:8024/api/streaming/json") - parameter("load", 100) - parameter("delay", 1) - contentType(ContentType.parse("application/x-ndjson")) - }.also { response -> - response - .readJsonStream { line -> - StoveSerde.jackson.anyJsonStringSerde().deserialize(line) - }.collect { data -> - println(data) - } +class StreamingTests : FunSpec({ + test("streaming") { + validate { + http { + streamClient() + .prepareGet { + url("http://localhost:8024/api/streaming/json") + parameter("load", 100) + parameter("delay", 1) + contentType(ContentType.parse("application/x-ndjson")) + }.also { response -> + response.readJsonStream { line -> + StoveObjectMapper.Default.readValue(line) + }.collect { data -> + println(data) } - } + } } } - }) + } +}) @OptIn(InternalAPI::class) suspend fun HttpStatement.readJsonStream(transform: (String) -> T): Flow = flow { diff --git a/recipes/scala-recipes/build.gradle.kts b/recipes/scala-recipes/build.gradle.kts index 7dccf078..0f299111 100644 --- a/recipes/scala-recipes/build.gradle.kts +++ b/recipes/scala-recipes/build.gradle.kts @@ -31,11 +31,19 @@ subprojects { isDownloadSources = true } } - dependencies { implementation(rootProject.projects.shared.domain) } + dependencies { + } + + spotless { + scala { + scalafmt() + } + } + task("e2eTest") { description = "Runs e2e tests." group = "verification" diff --git a/recipes/scala-recipes/spring-boot-recipe/build.gradle.kts b/recipes/scala-recipes/spring-boot-recipe/build.gradle.kts index 5541de7a..867b3d4e 100644 --- a/recipes/scala-recipes/spring-boot-recipe/build.gradle.kts +++ b/recipes/scala-recipes/spring-boot-recipe/build.gradle.kts @@ -28,7 +28,3 @@ dependencies { testImplementation(libs.stove.spring.testing) testImplementation(libs.jackson.kotlin) } - -tasks.e2eTest.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.recipes.scala.spring.e2e.setup.Stove") -} diff --git a/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/setup/Stove.kt b/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/setup/Stove.kt index fd2b85f5..503c97fe 100644 --- a/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/setup/Stove.kt +++ b/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/setup/Stove.kt @@ -8,22 +8,21 @@ import io.kotest.core.config.AbstractProjectConfig class Stove : AbstractProjectConfig() { override suspend fun beforeProject() { - TestSystem() - .with { - httpClient { - HttpClientSystemOptions( - baseUrl = "http://localhost:8080" - ) - } - bridge() - springBoot( - runner = { parameters -> - SpringBootRecipeApp.run(parameters) { - } - }, - withParameters = listOf() + TestSystem().with { + httpClient { + HttpClientSystemOptions( + baseUrl = "http://localhost:8080" ) - }.run() + } + bridge() + springBoot( + runner = { parameters -> + SpringBootRecipeApp.run(parameters) { + } + }, + withParameters = listOf() + ) + }.run() } override suspend fun afterProject() { diff --git a/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/tests/IndexTests.kt b/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/tests/IndexTests.kt index 3dc5ec17..8c4e400f 100644 --- a/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/tests/IndexTests.kt +++ b/recipes/scala-recipes/spring-boot-recipe/src/test-e2e/kotlin/com/trendyol/stove/recipes/scala/spring/e2e/tests/IndexTests.kt @@ -8,24 +8,23 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain -class IndexTests : - FunSpec({ - test("Index page should be accessible") { - validate { - http { - get("/hello") { actual -> - actual shouldContain "Hello, World! from reactor" - } +class IndexTests : FunSpec({ + test("Index page should be accessible") { + validate { + http { + get("/hello") { actual -> + actual shouldContain "Hello, World! from reactor" } } } + } - test("bridge should work") { - validate { - using { - this.currentThreadName shouldNotBe "" - println(this.currentThreadName) - } + test("bridge should work") { + validate { + using { + this.currentThreadName shouldNotBe "" + println(this.currentThreadName) } } - }) + } +}) diff --git a/recipes/settings.gradle.kts b/recipes/settings.gradle.kts index 80a52f28..0eb50c1d 100644 --- a/recipes/settings.gradle.kts +++ b/recipes/settings.gradle.kts @@ -14,6 +14,7 @@ include( "shared", "shared:domain", "shared:application", + "shared:infra", ) enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/ProductTests.kt b/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/ProductTests.kt index ed314978..b781f27f 100644 --- a/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/ProductTests.kt +++ b/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/ProductTests.kt @@ -6,58 +6,57 @@ import com.trendyol.stove.examples.domain.testing.aggregateroot.AggregateRootAss import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class ProductTests : - FunSpec({ - test("should create product") { - // Given - val product = Product.create("Product 1", 100.0, 1) - - // Then - product.name shouldBe "Product 1" - product.price shouldBe 100.0 - } - - test("when price change") { - // Given - val product = Product.create("Product 1", 100.0, 1) - - // When - product.changePrice(200.0) - - // Then - product.version shouldBe 2 - assertEvents(product) { - shouldContain { - newPrice shouldBe 200.0 - } - shouldContain { - name shouldBe "Product 1" - price shouldBe 100.0 - } - - shouldNotContain() +class ProductTests : FunSpec({ + test("should create product") { + // Given + val product = Product.create("Product 1", 100.0, 1) + + // Then + product.name shouldBe "Product 1" + product.price shouldBe 100.0 + } + + test("when price change") { + // Given + val product = Product.create("Product 1", 100.0, 1) + + // When + product.changePrice(200.0) + + // Then + product.version shouldBe 2 + assertEvents(product) { + shouldContain { + newPrice shouldBe 200.0 } + shouldContain { + name shouldBe "Product 1" + price shouldBe 100.0 + } + + shouldNotContain() } + } - test("change name") { - // Given - val product = Product.create("Product 1", 100.0, 1) - - // When - product.changeName("Product 2") - - // Then - product.version shouldBe 2 - assertEvents(product) { - shouldContain { - newName shouldBe "Product 2" - } - shouldContain { - name shouldBe "Product 1" - price shouldBe 100.0 - } - - shouldNotContain() + test("change name") { + // Given + val product = Product.create("Product 1", 100.0, 1) + + // When + product.changeName("Product 2") + + // Then + product.version shouldBe 2 + assertEvents(product) { + shouldContain { + newName shouldBe "Product 2" } + shouldContain { + name shouldBe "Product 1" + price shouldBe 100.0 + } + + shouldNotContain() } - }) + } +}) diff --git a/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/testing/aggregateroot/AggregateRootAssertion.kt b/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/testing/aggregateroot/AggregateRootAssertion.kt index d6f6eaa5..595c70d0 100644 --- a/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/testing/aggregateroot/AggregateRootAssertion.kt +++ b/recipes/shared/domain/src/test/kotlin/com/trendyol/stove/examples/domain/testing/aggregateroot/AggregateRootAssertion.kt @@ -4,7 +4,7 @@ import arrow.core.firstOrNone import com.trendyol.stove.examples.domain.ddd.* import io.kotest.assertions.* import io.kotest.assertions.print.Printed -import io.kotest.engine.mapError +import io.kotest.common.mapError import io.kotest.matchers.shouldBe class AggregateRootAssertion>( @@ -21,16 +21,10 @@ class AggregateRootAssertion>( inline fun shouldContain(act: T.() -> Unit) { shouldContain() - root - .domainEvents() - .filter { it::class == T::class } - .map { it as T } - .forEach { act(it) } + root.domainEvents().filter { it::class == T::class }.map { it as T }.forEach { act(it) } } - inline fun shouldContain() = root - .domainEvents() - .map { it.javaClass } + inline fun shouldContain() = root.domainEvents().map { it.javaClass } .firstOrNone { it == T::class.java } .onNone { throw failure( @@ -41,9 +35,7 @@ class AggregateRootAssertion>( } inline fun shouldNotContain() = - root - .domainEvents() - .map { it.javaClass } + root.domainEvents().map { it.javaClass } .firstOrNone { it == T::class.java } .onSome { throw failure( diff --git a/settings.gradle.kts b/settings.gradle.kts index fd6e19bd..cf6eb5a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,5 @@ @file:Suppress("UnstableApiUsage") - enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") - rootProject.name = "stove" include( "lib:stove-testing-e2e", @@ -16,18 +14,18 @@ include( "lib:stove-testing-e2e-redis", "lib:stove-testing-e2e-mongodb" ) - include( "starters:ktor:stove-ktor-testing-e2e", "starters:spring:stove-spring-testing-e2e", - "starters:spring:stove-spring-testing-e2e-kafka" + "starters:spring:stove-spring-testing-e2e-kafka", + "starters:micronaut-starter:stove-micronaut-testing-e2e" ) - include( "examples:spring-example", "examples:spring-standalone-example", "examples:ktor-example", - "examples:spring-streams-example" + "examples:spring-streams-example", + "examples:micronaut-example" ) dependencyResolutionManagement { repositories { diff --git a/starters/ktor/stove-ktor-testing-e2e/api/stove-ktor-testing-e2e.api b/starters/ktor/stove-ktor-testing-e2e/api/stove-ktor-testing-e2e.api deleted file mode 100644 index 2e0d03e6..00000000 --- a/starters/ktor/stove-ktor-testing-e2e/api/stove-ktor-testing-e2e.api +++ /dev/null @@ -1,21 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/KtorApplicationUnderTest : com/trendyol/stove/testing/e2e/system/abstractions/ApplicationUnderTest { - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;)V - public fun start (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/KtorApplicationUnderTestKt { - public static final fun ktor-FMzRXaI (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;)Lcom/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem; - public static synthetic fun ktor-FMzRXaI$default (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem; -} - -public final class com/trendyol/stove/testing/e2e/KtorBridgeSystem : com/trendyol/stove/testing/e2e/system/BridgeSystem, com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAwareWithContext, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem { - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)V - public fun get (Lkotlin/reflect/KClass;)Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/KtorBridgeSystemKt { - public static final fun bridge-hQma78k (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - diff --git a/starters/ktor/stove-ktor-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/KtorBridgeSystem.kt b/starters/ktor/stove-ktor-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/KtorBridgeSystem.kt index 9b351ad1..06163b79 100644 --- a/starters/ktor/stove-ktor-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/KtorBridgeSystem.kt +++ b/starters/ktor/stove-ktor-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/KtorBridgeSystem.kt @@ -15,9 +15,7 @@ import kotlin.reflect.KClass @StoveDsl class KtorBridgeSystem( override val testSystem: TestSystem -) : BridgeSystem(testSystem), - PluggedSystem, - AfterRunAwareWithContext { +) : PluggedSystem, AfterRunAwareWithContext, BridgeSystem(testSystem) { override fun get(klass: KClass): D = ctx.getKoin().get(klass) } diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/.gitignore b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/gradle.xml b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/gradle.xml new file mode 100644 index 00000000..39ec4f56 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/misc.xml b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/misc.xml new file mode 100644 index 00000000..6ed36dd3 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/vcs.xml b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/vcs.xml new file mode 100644 index 00000000..c2365ab1 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/build.gradle.kts b/starters/micronaut-starter/stove-micronaut-testing-e2e/build.gradle.kts new file mode 100644 index 00000000..1288da28 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/build.gradle.kts @@ -0,0 +1,13 @@ +dependencies { + api(projects.lib.stoveTestingE2e) + implementation("io.micronaut:micronaut-core") // Temel Micronaut bağımlılığı + implementation("io.micronaut.kotlin:micronaut-kotlin-runtime:4.5.0") + testImplementation("io.micronaut.test:micronaut-test-kotest5:4.0.0") + testImplementation("io.micronaut.test:micronaut-test-kotest5:4.0.0") + testImplementation("io.kotest:kotest-runner-junit5:5.7.2") +} + +repositories { + mavenCentral() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } +} diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.jar b/starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.properties b/starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..df97d72b --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/BaseApplicationContextInitializer.kt b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/BaseApplicationContextInitializer.kt new file mode 100644 index 00000000..3a7012c2 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/BaseApplicationContextInitializer.kt @@ -0,0 +1,38 @@ +package com.trendyol.stove.testing + +import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import io.micronaut.context.ApplicationContext +import io.micronaut.context.event.ApplicationEventListener +import io.micronaut.context.event.StartupEvent +import io.micronaut.runtime.Micronaut + +@StoveDsl +abstract class BaseApplicationContextInitializer : ApplicationEventListener { + + private val registrations = mutableListOf<(ApplicationContext) -> Unit>() + + @StoveDsl + fun register(registration: (ApplicationContext) -> Unit): BaseApplicationContextInitializer { + registrations.add(registration) + return this + } + + override fun onApplicationEvent(event: StartupEvent) { + val applicationContext = event.source as ApplicationContext + onStartup(applicationContext) + registrations.forEach { it(applicationContext) } + } + + protected open fun onStartup(applicationContext: ApplicationContext) { + // Custom initialization code can be added here + } + + companion object { + @JvmStatic fun main(args: Array) { + Micronaut.build() + .args(*args) + .packages("your.base.package.here") + .start() + } + } +} diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautApplicationUnderTest.kt b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautApplicationUnderTest.kt new file mode 100644 index 00000000..6a213a45 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautApplicationUnderTest.kt @@ -0,0 +1,56 @@ +package com.trendyol.stove.testing + +import com.trendyol.stove.testing.e2e.system.* +import com.trendyol.stove.testing.e2e.system.abstractions.* +import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import io.micronaut.context.* +import kotlinx.coroutines.* + +@StoveDsl +internal fun TestSystem.systemUnderTest( + runner: Runner, + withParameters: List = listOf() +): ReadyTestSystem { + this.applicationUnderTest(MicronautApplicationUnderTest(this, runner, withParameters)) + return this +} + +@StoveDsl +fun WithDsl.micronaut( + runner: Runner, + withParameters: List = listOf() +): ReadyTestSystem = this.testSystem.systemUnderTest(runner, withParameters) + +@StoveDsl +class MicronautApplicationUnderTest( + private val testSystem: TestSystem, + private val runner: Runner, + private val parameters: List +) : ApplicationUnderTest { + private lateinit var application: ApplicationContext + + companion object { + private const val DELAY = 500L + } + + override suspend fun start(configurations: List): ApplicationContext = coroutineScope { + val allConfigurations = (configurations + defaultConfigurations() + parameters).map { "--$it" }.toTypedArray() + application = runner(allConfigurations) + while (!application.isRunning) { + delay(DELAY) + continue + } + testSystem.activeSystems + .map { it.value } + .filterIsInstance>() + .map { async(context = Dispatchers.IO) { it.afterRun(application) } } + .awaitAll() + application + } + + override suspend fun stop() { + application.stop() + } + + private fun defaultConfigurations(): Array = arrayOf("test-system=true") +} diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautBridgeSystem.kt b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautBridgeSystem.kt new file mode 100644 index 00000000..0b70ebd2 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/MicronautBridgeSystem.kt @@ -0,0 +1,16 @@ +package com.trendyol.stove.testing + +import com.trendyol.stove.testing.e2e.system.* +import com.trendyol.stove.testing.e2e.system.abstractions.* +import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl +import io.micronaut.context.ApplicationContext +import kotlin.reflect.KClass + +@StoveDsl +class MicronautBridgeSystem( + override val testSystem: TestSystem +) : PluggedSystem, AfterRunAwareWithContext, BridgeSystem(testSystem) { + override fun get(klass: KClass): D = ctx.getBean(klass.java) +} +@StoveDsl +fun WithDsl.bridge(): TestSystem = this.testSystem.withBridgeSystem(MicronautBridgeSystem(this.testSystem)) diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/application.properties b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/application.properties new file mode 100644 index 00000000..de971bae --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#Mon Nov 18 19:19:36 UTC 2024 +micronaut.application.name=stove-micronaut-testing-e2e diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/logback.xml b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/logback.xml new file mode 100644 index 00000000..2d77bdab --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/BridgeSystemKtTest.kt b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/BridgeSystemKtTest.kt new file mode 100644 index 00000000..36a72b6f --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/com/trendyol/stove/testing/BridgeSystemKtTest.kt @@ -0,0 +1,100 @@ +package com.trendyol.stove.testing + +import com.fasterxml.jackson.databind.ObjectMapper +import io.kotest.core.config.AbstractProjectConfig +import io.kotest.core.spec.style.* +import io.kotest.matchers.shouldBe +import io.micronaut.context.annotation.* +import io.micronaut.runtime.EmbeddedApplication +import io.micronaut.runtime.Micronaut +import jakarta.inject.Singleton +import kotlinx.coroutines.delay +import java.time.Instant +import kotlin.time.Duration.Companion.seconds + +class BridgeSystemTests( + private val exampleService: ExampleService, + private val getUtcNow: GetUtcNow, + private val parameterCollector: ApplicationParameterCollector, + private val embeddedApplication: EmbeddedApplication<*> +) : FunSpec({ + + test("bridge to application") { + embeddedApplication.isRunning shouldBe true + + exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime + getUtcNow() shouldBe GetUtcNow.frozenTime + + parameterCollector.parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + + delay(5.seconds) + } + + test("resolve multiple") { + getUtcNow() shouldBe GetUtcNow.frozenTime + exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime + + parameterCollector.parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + } + }) + +@Factory +class TestAppConfig { + @Singleton + fun objectMapper(): ObjectMapper = ObjectMapper() + + @Singleton + fun getUtcNow(): GetUtcNow = SystemTimeGetUtcNow() + + @Singleton + fun exampleService(getUtcNow: GetUtcNow): ExampleService = ExampleService(getUtcNow) + + @Singleton + fun applicationParameterCollector(environment: io.micronaut.context.env.Environment): ApplicationParameterCollector = ApplicationParameterCollector(environment) +} + +fun interface GetUtcNow { + companion object { + val frozenTime: Instant = Instant.parse("2021-01-01T00:00:00Z") + } + + operator fun invoke(): Instant +} + +class SystemTimeGetUtcNow : GetUtcNow { + override fun invoke(): Instant = GetUtcNow.frozenTime +} + +@Singleton +class ExampleService( + private val getUtcNow: GetUtcNow +) { + fun whatIsTheTime(): Instant = getUtcNow() +} + +@Singleton +class ApplicationParameterCollector( + private val environment: io.micronaut.context.env.Environment +) { + val parameters: List + get() = environment.activeNames.toList() +} + +class Setup : AbstractProjectConfig() { + override suspend fun beforeProject() { + Micronaut + .build() + .args("--test-system=true", "--context=SetupOfBridgeSystemTests") + .start() + } + + override suspend fun afterProject() { + // Uygulama durdurma işlemi yapılabilir + } +} diff --git a/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/io/kotest/provided/ProjectConfig.kt b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/io/kotest/provided/ProjectConfig.kt new file mode 100644 index 00000000..a328bf46 --- /dev/null +++ b/starters/micronaut-starter/stove-micronaut-testing-e2e/src/test/kotlin/io/kotest/provided/ProjectConfig.kt @@ -0,0 +1,8 @@ +package io.kotest.provided + +import io.kotest.core.config.AbstractProjectConfig +import io.micronaut.test.extensions.kotest5.MicronautKotest5Extension + +object ProjectConfig : AbstractProjectConfig() { + override fun extensions() = listOf(MicronautKotest5Extension) +} diff --git a/starters/spring/stove-spring-testing-e2e-kafka/api/stove-spring-testing-e2e-kafka.api b/starters/spring/stove-spring-testing-e2e-kafka/api/stove-spring-testing-e2e-kafka.api deleted file mode 100644 index 7dbb9ee9..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/api/stove-spring-testing-e2e-kafka.api +++ /dev/null @@ -1,176 +0,0 @@ -public final class com/trendyol/stove/testing/e2e/kafka/Caching { - public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/kafka/Caching; - public final fun of ()Lcom/github/benmanes/caffeine/cache/Cache; -} - -public final class com/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde { - public fun ()V - public fun (Lorg/apache/kafka/common/serialization/Serializer;Lorg/apache/kafka/common/serialization/Serializer;)V - public synthetic fun (Lorg/apache/kafka/common/serialization/Serializer;Lorg/apache/kafka/common/serialization/Serializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lorg/apache/kafka/common/serialization/Serializer; - public final fun component2 ()Lorg/apache/kafka/common/serialization/Serializer; - public final fun copy (Lorg/apache/kafka/common/serialization/Serializer;Lorg/apache/kafka/common/serialization/Serializer;)Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde;Lorg/apache/kafka/common/serialization/Serializer;Lorg/apache/kafka/common/serialization/Serializer;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde; - public fun equals (Ljava/lang/Object;)Z - public final fun getKeySerializer ()Lorg/apache/kafka/common/serialization/Serializer; - public final fun getValueSerializer ()Lorg/apache/kafka/common/serialization/Serializer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions : com/trendyol/stove/testing/e2e/containers/ContainerOptions { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlin/jvm/functions/Function1; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getCompatibleSubstitute ()Ljava/lang/String; - public fun getContainerFn ()Lkotlin/jvm/functions/Function1; - public fun getImage ()Ljava/lang/String; - public fun getImageWithTag ()Ljava/lang/String; - public fun getRegistry ()Ljava/lang/String; - public fun getTag ()Ljava/lang/String; - public fun getUseContainerFn ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaContext { - public fun (Lcom/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions;)V - public final fun component1 ()Lcom/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer; - public final fun component2 ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions; - public final fun copy (Lcom/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaContext; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaContext;Lcom/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer;Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaContext; - public fun equals (Ljava/lang/Object;)Z - public final fun getContainer ()Lcom/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer; - public final fun getOptions ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface annotation class com/trendyol/stove/testing/e2e/kafka/KafkaDsl : java/lang/annotation/Annotation { -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaExposedConfiguration : com/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaExposedConfiguration; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaExposedConfiguration;Ljava/lang/String;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaExposedConfiguration; - public fun equals (Ljava/lang/Object;)Z - public final fun getBootstrapServers ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaOps { - public fun ()V - public fun (Lkotlin/jvm/functions/Function3;)V - public synthetic fun (Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlin/jvm/functions/Function3; - public final fun copy (Lkotlin/jvm/functions/Function3;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps; - public fun equals (Ljava/lang/Object;)Z - public final fun getSend ()Lkotlin/jvm/functions/Function3; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaSystem : com/trendyol/stove/testing/e2e/system/abstractions/ExposesConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem, com/trendyol/stove/testing/e2e/system/abstractions/RunnableSystemWithContext { - public static final field Companion Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystem$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lcom/trendyol/stove/testing/e2e/kafka/KafkaContext;)V - public final fun adminOperations (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public synthetic fun afterRun (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun afterRun (Lorg/springframework/context/ApplicationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun beforeRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun close ()V - public fun configuration ()Ljava/util/List; - public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getGetInterceptor ()Lkotlin/jvm/functions/Function0; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun pause ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystem; - public final fun publish (Ljava/lang/String;Ljava/lang/Object;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Larrow/core/Option;Larrow/core/Option;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun publish$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystem;Ljava/lang/String;Ljava/lang/Object;Larrow/core/Option;Larrow/core/Option;Ljava/util/Map;Larrow/core/Option;Larrow/core/Option;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBeConsumedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBeFailedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun shouldBePublishedInternal-dWUq8MI (Lkotlin/reflect/KClass;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun then ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; - public final fun unpause ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystem; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaSystem$Companion { - public final fun kafkaTemplate (Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystem;)Lorg/springframework/kafka/core/KafkaTemplate; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions : com/trendyol/stove/testing/e2e/system/abstractions/ConfiguresExposedConfiguration, com/trendyol/stove/testing/e2e/system/abstractions/SystemOptions { - public static final field Companion Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions$Companion; - public fun (Ljava/lang/String;Ljava/util/List;Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde;Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions;Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde;Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions;Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde; - public final fun component4 ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions; - public final fun component5 ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps; - public final fun component6 ()Lkotlin/jvm/functions/Function1; - public final fun copy (Ljava/lang/String;Ljava/util/List;Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde;Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions;Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions; - public static synthetic fun copy$default (Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions;Ljava/lang/String;Ljava/util/List;Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde;Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions;Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions; - public fun equals (Ljava/lang/Object;)Z - public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; - public final fun getContainerOptions ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaContainerOptions; - public final fun getFallbackSerde ()Lcom/trendyol/stove/testing/e2e/kafka/FallbackTemplateSerde; - public final fun getOps ()Lcom/trendyol/stove/testing/e2e/kafka/KafkaOps; - public final fun getPorts ()Ljava/util/List; - public final fun getRegistry ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaSystemOptions$Companion { - public final fun getDEFAULT_KAFKA_PORTS ()Ljava/util/List; -} - -public final class com/trendyol/stove/testing/e2e/kafka/KafkaTemplateCompatibilityKt { - public static final fun sendCompatible (Lorg/springframework/kafka/core/KafkaTemplate;Lorg/apache/kafka/clients/producer/ProducerRecord;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class com/trendyol/stove/testing/e2e/kafka/MessageProperties { - public abstract fun getKey ()Ljava/lang/String; - public abstract fun getMetadata ()Lcom/trendyol/stove/testing/e2e/messaging/MessageMetadata; - public abstract fun getPartition ()Ljava/lang/Integer; - public abstract fun getTimestamp ()Ljava/lang/Long; - public abstract fun getTopic ()Ljava/lang/String; - public abstract fun getValue ()[B - public abstract fun getValueAsString ()Ljava/lang/String; -} - -public final class com/trendyol/stove/testing/e2e/kafka/OptionsKt { - public static final fun kafka-E6EcY7A (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun kafka-PmNtuJU (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function0;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public class com/trendyol/stove/testing/e2e/kafka/StoveKafkaContainer : org/testcontainers/kafka/ConfluentKafkaContainer, com/trendyol/stove/testing/e2e/containers/StoveContainer { - public fun (Lorg/testcontainers/utility/DockerImageName;)V - public fun getContainerIdAccess ()Ljava/lang/String; - public fun getDockerClientAccess ()Lkotlin/Lazy; - public fun getImageNameAccess ()Lorg/testcontainers/utility/DockerImageName; - public fun inspect ()Lcom/trendyol/stove/testing/e2e/containers/StoveContainerInspectInformation; - public fun pause ()V - public fun unpause ()V -} - -public final class com/trendyol/stove/testing/e2e/kafka/TestSystemKafkaInterceptor : org/springframework/kafka/listener/CompositeRecordInterceptor, org/springframework/kafka/support/ProducerListener { - public fun (Lcom/trendyol/stove/testing/e2e/serialization/StoveSerde;)V - public fun failure (Lorg/apache/kafka/clients/consumer/ConsumerRecord;Ljava/lang/Exception;Lorg/apache/kafka/clients/consumer/Consumer;)V - public fun onError (Lorg/apache/kafka/clients/producer/ProducerRecord;Lorg/apache/kafka/clients/producer/RecordMetadata;Ljava/lang/Exception;)V - public fun onSuccess (Lorg/apache/kafka/clients/producer/ProducerRecord;Lorg/apache/kafka/clients/producer/RecordMetadata;)V - public fun success (Lorg/apache/kafka/clients/consumer/ConsumerRecord;Lorg/apache/kafka/clients/consumer/Consumer;)V -} - diff --git a/starters/spring/stove-spring-testing-e2e-kafka/build.gradle.kts b/starters/spring/stove-spring-testing-e2e-kafka/build.gradle.kts index c0f5a92d..49df7953 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/build.gradle.kts +++ b/starters/spring/stove-spring-testing-e2e-kafka/build.gradle.kts @@ -1,9 +1,3 @@ -import com.google.protobuf.gradle.id - -plugins { - alias(libs.plugins.protobuf) -} - dependencies { api(projects.lib.stoveTestingE2e) api(libs.testcontainers.kafka) @@ -16,27 +10,5 @@ dependencies { testAnnotationProcessor(libs.spring.boot.annotationProcessor) testImplementation(libs.spring.boot.autoconfigure) testImplementation(projects.starters.spring.stoveSpringTestingE2e) - testImplementation(libs.logback.classic) - testImplementation(libs.google.protobuf.kotlin) - testImplementation(libs.kafka.streams.protobuf.serde) -} - -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.kafka.Setup") + testImplementation(libs.slf4j.simple) } - -protobuf { - protoc { - artifact = libs.protoc.get().toString() - } - - generateProtoTasks { - all().forEach { - it.descriptorSetOptions.includeSourceInfo = true - it.descriptorSetOptions.includeImports = true - it.builtins { id("kotlin") } - } - } -} - - diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Extensions.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Extensions.kt index e5d73399..a6eb07a9 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Extensions.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/Extensions.kt @@ -1,8 +1,8 @@ package com.trendyol.stove.testing.e2e.kafka import arrow.core.Option +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.messaging.MessageMetadata -import com.trendyol.stove.testing.e2e.serialization.StoveSerde import org.apache.kafka.clients.consumer.ConsumerRecord import org.apache.kafka.clients.producer.ProducerRecord @@ -19,61 +19,33 @@ internal fun ConsumerRecord.toMetadata(): MessageMetadata = Message ) internal fun ConsumerRecord.toStoveMessage( - serde: StoveSerde -): StoveMessage.Consumed = StoveMessage.consumed( - this.topic(), - serializeIfNotYet(this.value(), serde), - this.toMetadata(), - this.partition(), - this.key()?.toString() ?: "", - this.timestamp(), - this.offset() -) - -internal fun ConsumerRecord.toFailedStoveMessage( - serde: StoveSerde, - exception: Exception -): StoveMessage.Failed = StoveMessage.failed( - this.topic(), - serializeIfNotYet(this.value(), serde), - this.toMetadata(), - exception, - this.partition(), - this.key()?.toString() ?: "", - this.timestamp() -) + objectMapper: ObjectMapper +): StoveMessage.StoveConsumedMessage = + StoveMessage.StoveConsumedMessage( + this.topic(), + serializeIfNotString(this.value(), objectMapper), + this.toMetadata(), + this.offset(), + this.partition(), + this.key().toString(), + this.timestamp() + ) internal fun ProducerRecord.toStoveMessage( - serde: StoveSerde -): StoveMessage.Published = StoveMessage.published( - this.topic(), - serializeIfNotYet(this.value(), serde), - this.toMetadata(), - this.partition(), - this.key()?.toString() ?: "", - this.timestamp() -) - -internal fun ProducerRecord.toFailedStoveMessage( - serde: StoveSerde, - exception: Exception -): StoveMessage.Failed = StoveMessage.failed( + objectMapper: ObjectMapper +): StoveMessage.StovePublishedMessage = StoveMessage.StovePublishedMessage( this.topic(), - serializeIfNotYet(this.value(), serde), + serializeIfNotString(this.value(), objectMapper), this.toMetadata(), - exception, this.partition(), - this.key()?.toString() ?: "", + this.key().toString(), this.timestamp() ) -private fun serializeIfNotYet( +private fun serializeIfNotString( value: V, - serde: StoveSerde -): ByteArray = when (value) { - is ByteArray -> value - else -> serde.serialize(value as Any) -} + objectMapper: ObjectMapper +): String = if (value is String) value else objectMapper.writeValueAsString(value) internal fun (MutableMap).addTestCase(testCase: Option): MutableMap = if (this.containsKey("testCase")) this else testCase.map { this["testCase"] = it }.let { this } diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystem.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystem.kt index 8bf283c9..8d2cc7e2 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystem.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystem.kt @@ -3,11 +3,9 @@ package com.trendyol.stove.testing.e2e.kafka import arrow.core.* import com.trendyol.stove.functional.* import com.trendyol.stove.testing.e2e.messaging.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde -import com.trendyol.stove.testing.e2e.system.* +import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* import kotlinx.coroutines.* -import org.apache.kafka.clients.admin.* import org.apache.kafka.clients.producer.* import org.apache.kafka.common.header.internals.RecordHeader import org.slf4j.* @@ -20,45 +18,99 @@ import kotlin.time.* import kotlin.time.Duration.Companion.seconds @KafkaDsl -@Suppress("TooManyFunctions", "unused") class KafkaSystem( override val testSystem: TestSystem, private val context: KafkaContext -) : PluggedSystem, - RunnableSystemWithContext, - ExposesConfiguration { +) : PluggedSystem, RunnableSystemWithContext, ExposesConfiguration { private val logger: Logger = LoggerFactory.getLogger(javaClass) private lateinit var applicationContext: ApplicationContext private lateinit var kafkaTemplate: KafkaTemplate private lateinit var exposedConfiguration: KafkaExposedConfiguration - private lateinit var admin: Admin val getInterceptor: () -> TestSystemKafkaInterceptor = { applicationContext.getBean() } private val state: StateStorage = testSystem.options.createStateStorage() - /** - * Publishes a message to the given topic. - * The message will be serialized using the provided serde. - * - * If the KafkaTemplate of the application is desired to be used, then [BridgeSystem] functionality can be used. - * For example: - * ```kotlin - * validate { - * using> { - * this.send(ProducerRecord("topic", "message")) - * } - * } - * ``` - * [BridgeSystem] should be enabled while configuring the [TestSystem]. - * @param topic The topic to publish the message to. - * @param message The message to publish. - * @param key The key of the message. - * @param partition The partition to publish the message to. - * @param headers The headers of the message. - * @param serde The serde to serialize the message. - * @param testCase The test case of the message. - * @return KafkaSystem - */ + override suspend fun beforeRun() = Unit + + override suspend fun run() { + exposedConfiguration = state.capture { + context.container.start() + KafkaExposedConfiguration(context.container.bootstrapServers) + } + } + + override suspend fun afterRun(context: ApplicationContext) { + applicationContext = context + checkIfInterceptorConfiguredProperly(context) + kafkaTemplate = createKafkaTemplate(context, exposedConfiguration) + } + + private fun createKafkaTemplate(context: ApplicationContext, exposedConfiguration: KafkaExposedConfiguration): KafkaTemplate { + val kafkaTemplates: Map> = context.getBeansOfType() + return kafkaTemplates + .values + .onEach { + it.setProducerListener(getInterceptor()) + it.setCloseTimeout(1.seconds.toJavaDuration()) + } + .firstOrNone { safeContains(it, exposedConfiguration) } + .getOrElse { + logger.warn("No KafkaTemplate found for the configured bootstrap servers, using a fallback KafkaTemplate") + createFallbackTemplate(exposedConfiguration) + } + } + + @Suppress("UNCHECKED_CAST") + private fun safeContains( + it: KafkaTemplate, + exposedConfiguration: KafkaExposedConfiguration + ): Boolean = it.producerFactory.configurationProperties[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] + .toOption() + .map { + when (it) { + is String -> it + is Iterable<*> -> (it as Iterable).joinToString(",") + else -> it.toString() + } + }.isSome { it.contains(exposedConfiguration.bootstrapServers) } + + private fun createFallbackTemplate(exposedConfiguration: KafkaExposedConfiguration): KafkaTemplate { + val producerFactory = DefaultKafkaProducerFactory( + mapOf( + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to exposedConfiguration.bootstrapServers, + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to "org.apache.kafka.common.serialization.StringSerializer", + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to "org.apache.kafka.common.serialization.StringSerializer" + ) + ) + val fallbackTemplate = KafkaTemplate(producerFactory).also { + it.setProducerListener(getInterceptor()) + it.setCloseTimeout(1.seconds.toJavaDuration()) + } + return fallbackTemplate + } + + private fun checkIfInterceptorConfiguredProperly(context: ApplicationContext) { + val interceptors: Map> = context.getBeansOfType() + + fun stoveInterceptionPresent(): Boolean = interceptors.values.any { it is TestSystemKafkaInterceptor<*, *> } + if (!stoveInterceptionPresent()) { + throw AssertionError( + "Kafka interceptor is not an instance of TestSystemKafkaInterceptor, " + + "please make sure that you have configured the Stove Kafka interceptor in your Spring Application properly." + + "You can use a TestSystemInitializer to add the interceptor to your Spring Application: " + + """ + fun SpringApplication.addTestSystemDependencies() { + this.addInitializers(TestSystemInitializer()) + } + + class TestSystemInitializer : BaseApplicationContextInitializer({ + bean>(isPrimary = true) + }) + """.trimIndent() + ) + } + } + @KafkaDsl suspend fun publish( topic: String, @@ -66,31 +118,19 @@ class KafkaSystem( key: Option = None, partition: Option = None, headers: Map = mapOf(), - serde: Option> = None, testCase: Option = None ): KafkaSystem { val record = ProducerRecord( topic, partition.getOrNull(), key.getOrNull(), - message, + context.objectMapper.writeValueAsString(message), headers.toMutableMap().addTestCase(testCase).map { RecordHeader(it.key, it.value.toByteArray()) } ) - return context.options.ops - .send(kafkaTemplate, record) - .let { this } + return context.options.ops.send(kafkaTemplate, record).let { this } } - /** - * Admin operations for Kafka. - */ - @KafkaDsl - suspend fun adminOperations(block: suspend Admin.() -> Unit) = block(admin) - - /** - * Asserts that a message is consumed. - */ @KafkaDsl suspend inline fun shouldBeConsumed( atLeastIn: Duration = 5.seconds, @@ -101,9 +141,6 @@ class KafkaSystem( } }.let { this } - /** - * Asserts that a message is failed. - */ @KafkaDsl suspend inline fun shouldBeFailed( atLeastIn: Duration = 5.seconds, @@ -111,13 +148,18 @@ class KafkaSystem( ): KafkaSystem = coroutineScope { shouldBeFailedInternal(T::class, atLeastIn) { parsed -> parsed as FailedParsedMessage - parsed.message.isSome { o -> condition(FailedObservedMessage(o, parsed.metadata, parsed.reason)) } + parsed.message.isSome { o -> + condition( + FailedObservedMessage( + o, + parsed.metadata, + parsed.reason + ) + ) + } } }.let { this } - /** - * Asserts that a message is published. - */ @KafkaDsl suspend inline fun shouldBePublished( atLeastIn: Duration = 5.seconds, @@ -175,96 +217,4 @@ class KafkaSystem( logger.warn("got an error while closing KafkaSystem", it) } } - - override suspend fun beforeRun() = Unit - - override suspend fun run() { - exposedConfiguration = state.capture { - context.container.start() - KafkaExposedConfiguration(context.container.bootstrapServers) - } - } - - override suspend fun afterRun(context: ApplicationContext) { - applicationContext = context - checkIfInterceptorConfiguredProperly(context) - kafkaTemplate = createKafkaTemplate(context, exposedConfiguration) - admin = createAdminClient(exposedConfiguration) - } - - private fun createAdminClient( - exposedConfiguration: KafkaExposedConfiguration - ): Admin = mapOf( - AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to exposedConfiguration.bootstrapServers, - AdminClientConfig.CLIENT_ID_CONFIG to "stove-kafka-admin-client" - ).let { Admin.create(it) } - - private fun createKafkaTemplate(context: ApplicationContext, exposedConfiguration: KafkaExposedConfiguration): KafkaTemplate { - val kafkaTemplates: Map> = context.getBeansOfType() - return kafkaTemplates - .values - .onEach { - it.setProducerListener(getInterceptor()) - it.setCloseTimeout(1.seconds.toJavaDuration()) - }.firstOrNone { safeContains(it, exposedConfiguration) } - .getOrElse { - logger.warn("No KafkaTemplate found for the configured bootstrap servers, using a fallback KafkaTemplate") - createFallbackTemplate(exposedConfiguration) - } - } - - @Suppress("UNCHECKED_CAST") - private fun safeContains( - kafkaTemplate: KafkaTemplate, - exposedConfiguration: KafkaExposedConfiguration - ): Boolean = kafkaTemplate.producerFactory.configurationProperties[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] - .toOption() - .map { - when (it) { - is String -> it - is Iterable<*> -> (it as Iterable).joinToString(",") - else -> it.toString() - } - }.isSome { it.contains(exposedConfiguration.bootstrapServers) } - - private fun createFallbackTemplate(exposedConfiguration: KafkaExposedConfiguration): KafkaTemplate { - val producerFactory = DefaultKafkaProducerFactory( - mapOf( - ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to exposedConfiguration.bootstrapServers, - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to context.options.fallbackSerde.keySerializer::class.java, - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to context.options.fallbackSerde.valueSerializer::class.java - ) - ) - val fallbackTemplate = KafkaTemplate(producerFactory).also { - it.setProducerListener(getInterceptor()) - it.setCloseTimeout(1.seconds.toJavaDuration()) - } - return fallbackTemplate - } - - private fun checkIfInterceptorConfiguredProperly(context: ApplicationContext) { - val interceptors: Map> = context.getBeansOfType() - - fun stoveInterceptionPresent(): Boolean = interceptors.values.any { it is TestSystemKafkaInterceptor<*, *> } - if (!stoveInterceptionPresent()) { - throw AssertionError( - "Kafka interceptor is not an instance of TestSystemKafkaInterceptor, " + - "please make sure that you have configured the Stove Kafka interceptor in your Spring Application properly." + - "You can use a TestSystemInitializer to add the interceptor to your Spring Application: " + - """ - fun SpringApplication.addTestSystemDependencies() { - this.addInitializers(TestSystemInitializer()) - } - - class TestSystemInitializer : BaseApplicationContextInitializer({ - bean>(isPrimary = true) - }) - """.trimIndent() - ) - } - } - - companion object { - fun KafkaSystem.kafkaTemplate(): KafkaTemplate = kafkaTemplate - } } diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/MessageStore.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/MessageStore.kt index 3f135a20..6697de49 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/MessageStore.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/MessageStore.kt @@ -5,35 +5,31 @@ import io.exoquery.pprint import java.util.* internal class MessageStore { - private val consumed = Caching.of() - private val produced = Caching.of() - private val failures = Caching.of>() + private val consumed = Caching.of() + private val produced = Caching.of() + private val failures = Caching.of>() - fun record(record: StoveMessage.Consumed) { + fun record(record: StoveMessage.StoveConsumedMessage) { consumed.put(UUID.randomUUID(), record) } - fun record(record: StoveMessage.Published) { + fun record(record: StoveMessage.StovePublishedMessage) { produced.put(UUID.randomUUID(), record) } - fun record(failure: Failure) { + fun record(failure: Failure) { failures.put(UUID.randomUUID(), failure) } - fun consumedRecords(): List = consumed.asMap().values.toList() + fun consumedRecords(): List = consumed.asMap().values.toList() - fun producedRecords(): List = produced.asMap().values.toList() + fun producedRecords(): List = produced.asMap().values.toList() - fun failedRecords(): List = failures - .asMap() - .values - .map { failure -> failure.message.actual } - .toList() + fun failedRecords(): List> = failures.asMap().values.toList() override fun toString(): String = """ - |Consumed: ${pprint(consumedRecords().map { it.copy(value = ByteArray(0)) })} - |Published: ${pprint(producedRecords().map { it.copy(value = ByteArray(0)) })} - |Failed: ${pprint(failedRecords().map { it.copy(value = ByteArray(0)) })} + |Consumed: ${pprint(consumedRecords())} + |Published: ${pprint(producedRecords())} + |Failed: ${pprint(failedRecords())} """.trimIndent().trimMargin() } 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 4eb97fed..4f4979f7 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 @@ -1,20 +1,20 @@ 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.system.* import com.trendyol.stove.testing.e2e.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl import org.apache.kafka.clients.producer.ProducerRecord -import org.apache.kafka.common.serialization.* import org.springframework.kafka.core.KafkaTemplate -import org.testcontainers.kafka.ConfluentKafkaContainer +import org.testcontainers.kafka.* import org.testcontainers.utility.DockerImageName open class StoveKafkaContainer( override val imageNameAccess: DockerImageName -) : ConfluentKafkaContainer(imageNameAccess), - StoveContainer +) : ConfluentKafkaContainer(imageNameAccess), StoveContainer @StoveDsl data class KafkaExposedConfiguration( @@ -38,43 +38,15 @@ data class KafkaOps( ) -> Unit = { kafkaTemplate, record -> kafkaTemplate.sendCompatible(record) } ) -data class FallbackTemplateSerde( - val keySerializer: Serializer<*> = StringSerializer(), - val valueSerializer: Serializer<*> = StringSerializer() -) - @StoveDsl data class KafkaSystemOptions( - /** - * The registry of the Kafka image. The default value is `DEFAULT_REGISTRY`. - */ val registry: String = DEFAULT_REGISTRY, - /** - * The ports of the Kafka container. The default value is `DEFAULT_KAFKA_PORTS`. - */ val ports: List = DEFAULT_KAFKA_PORTS, - /** - * The fallback serde for Kafka. It is used to serialize and deserialize the messages before sending them to Kafka. - * If no [KafkaTemplate] is provided, it will be used to create a new [KafkaTemplate]. - * Most of the time you won't need this. - */ - val fallbackSerde: FallbackTemplateSerde = FallbackTemplateSerde(), - /** - * Container options for Kafka. - */ + val objectMapper: ObjectMapper = StoveObjectMapper.Default, val containerOptions: KafkaContainerOptions = KafkaContainerOptions(), - /** - * Operations for Kafka. It is used to customize the operations of Kafka. - * The reason why this exists is to provide a way to interact with lower versions of Spring-Kafka dependencies. - * @see KafkaOps - */ val ops: KafkaOps = KafkaOps(), - /** - * The configuration of the Kafka settings that is exposed to the Application Under Test(AUT). - */ override val configureExposedConfiguration: (KafkaExposedConfiguration) -> List -) : SystemOptions, - ConfiguresExposedConfiguration { +) : SystemOptions, ConfiguresExposedConfiguration { companion object { val DEFAULT_KAFKA_PORTS = listOf(9092, 9093) } @@ -83,6 +55,7 @@ data class KafkaSystemOptions( @StoveDsl data class KafkaContext( val container: StoveKafkaContainer, + val objectMapper: ObjectMapper, val options: KafkaSystemOptions ) @@ -92,13 +65,12 @@ internal fun TestSystem.withKafka(options: KafkaSystemOptions): TestSystem = registry = options.registry, compatibleSubstitute = options.containerOptions.compatibleSubstitute ) { - options.containerOptions - .useContainerFn(it) + options.containerOptions.useContainerFn(it) .withExposedPorts(*options.ports.toTypedArray()) .withReuse(this.options.keepDependenciesRunning) .let { c -> c as StoveKafkaContainer } .apply(options.containerOptions.containerFn) - }.let { getOrRegister(KafkaSystem(this, KafkaContext(it, options))) } + }.let { getOrRegister(KafkaSystem(this, KafkaContext(it, options.objectMapper, options))) } .let { this } internal fun TestSystem.kafka(): KafkaSystem = getOrNone().getOrElse { diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/StoveMessage.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/StoveMessage.kt index 26a61e3a..e169a43c 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/StoveMessage.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/StoveMessage.kt @@ -1,123 +1,31 @@ package com.trendyol.stove.testing.e2e.kafka import com.trendyol.stove.testing.e2e.messaging.MessageMetadata -import io.exoquery.pprint -sealed interface MessageProperties { - val topic: String - val value: ByteArray - val valueAsString: String - val metadata: MessageMetadata - val partition: Int? - val key: String? - val timestamp: Long? -} - -internal sealed class StoveMessage : MessageProperties { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Consumed - - if (topic != other.topic) return false - if (!value.contentEquals(other.value)) return false - if (metadata != other.metadata) return false - if (partition != other.partition) return false - if (key != other.key) return false - if (timestamp != other.timestamp) return false - - return true - } +internal sealed class StoveMessage { + abstract val topic: String + abstract val value: String + abstract val metadata: MessageMetadata + abstract val partition: Int? + abstract val key: String? + abstract val timeStamp: Long? - override fun hashCode(): Int { - var result = topic.hashCode() - result = 31 * result + value.contentHashCode() - result = 31 * result + metadata.hashCode() - result = 31 * result + (partition ?: 0) - result = 31 * result + (key?.hashCode() ?: 0) - result = 31 * result + (timestamp?.hashCode() ?: 0) - return result - } - - data class Consumed( + internal data class StoveConsumedMessage( override val topic: String, - override val value: ByteArray, + override val value: String, override val metadata: MessageMetadata, - override val partition: Int?, - override val key: String?, - override val timestamp: Long?, val offset: Long?, - override val valueAsString: String = String(value) - ) : StoveMessage() { - override fun hashCode(): Int = super.hashCode() + offset.hashCode() - - override fun equals(other: Any?): Boolean = super.equals(other) && other is Consumed && offset == other.offset - - override fun toString(): String = pprint(this.copy(value = ByteArray(0))).toString() - } - - data class Published( - override val topic: String, - override val value: ByteArray, - override val metadata: MessageMetadata, override val partition: Int?, override val key: String?, - override val timestamp: Long?, - override val valueAsString: String = String(value) - ) : StoveMessage() { - override fun hashCode(): Int = super.hashCode() - - override fun equals(other: Any?): Boolean = super.equals(other) - - override fun toString(): String = pprint(this.copy(value = ByteArray(0))).toString() - } + override val timeStamp: Long? + ) : StoveMessage() - data class Failed( + internal data class StovePublishedMessage( override val topic: String, - override val value: ByteArray, + override val value: String, override val metadata: MessageMetadata, override val partition: Int?, override val key: String?, - override val timestamp: Long?, - val reason: Throwable, - override val valueAsString: String = String(value) - ) : StoveMessage() { - override fun hashCode(): Int = super.hashCode() + reason.hashCode() - - override fun equals(other: Any?): Boolean = super.equals(other) && other is Failed && reason == other.reason - - override fun toString(): String = pprint(this.copy(value = ByteArray(0))).toString() - } - - companion object { - fun consumed( - topic: String, - value: ByteArray, - metadata: MessageMetadata, - partition: Int? = null, - key: String? = null, - timestamp: Long? = null, - offset: Long? = null - ): Consumed = Consumed(topic, value, metadata, partition, key, timestamp, offset) - - fun published( - topic: String, - value: ByteArray, - metadata: MessageMetadata, - partition: Int? = null, - key: String? = null, - timestamp: Long? = null - ): Published = Published(topic, value, metadata, partition, key, timestamp) - - fun failed( - topic: String, - value: ByteArray, - metadata: MessageMetadata, - reason: Throwable, - partition: Int? = null, - key: String? = null, - timestamp: Long? = null - ): Failed = Failed(topic, value, metadata, partition, key, timestamp, reason) - } + override val timeStamp: Long? + ) : StoveMessage() } diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/TestSystemInterceptor.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/TestSystemInterceptor.kt index 01eb382e..7a10968d 100644 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/TestSystemInterceptor.kt +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/main/kotlin/com/trendyol/stove/testing/e2e/kafka/TestSystemInterceptor.kt @@ -1,8 +1,9 @@ package com.trendyol.stove.testing.e2e.kafka import arrow.core.toOption +import com.fasterxml.jackson.databind.ObjectMapper import com.trendyol.stove.testing.e2e.messaging.* -import com.trendyol.stove.testing.e2e.serialization.StoveSerde +import io.exoquery.pprint import kotlinx.coroutines.* import org.apache.kafka.clients.consumer.* import org.apache.kafka.clients.producer.* @@ -12,20 +13,9 @@ import org.springframework.kafka.support.ProducerListener import kotlin.reflect.KClass import kotlin.time.Duration -/** - * This is the main actor between your Kafka Spring Boot application and the test system. - * It is responsible for intercepting the messages that are produced and consumed by the application. - * It also provides a way to wait until a message is consumed or produced. - * - * @param serde The serializer/deserializer that will be used to serialize/deserialize the messages. - * It is important to use the same serde that is used in the application. - * For example, if the application uses Avro, then you should use Avro serde here. - * Target of the serialization is ByteArray, so the serde should be able to serialize the message to ByteArray. - */ class TestSystemKafkaInterceptor( - private val serde: StoveSerde -) : CompositeRecordInterceptor(), - ProducerListener { + private val objectMapper: ObjectMapper +) : CompositeRecordInterceptor(), ProducerListener { private val logger: Logger = LoggerFactory.getLogger(javaClass) private val store = MessageStore() @@ -33,9 +23,8 @@ class TestSystemKafkaInterceptor( record: ProducerRecord, recordMetadata: RecordMetadata ) { - val message = record.toStoveMessage(serde) - store.record(message) - logger.info("Successfully produced:\n{}", message) + store.record(record.toStoveMessage(objectMapper)) + logger.info("Successfully produced:\n{}", pprint(record.toStoveMessage(objectMapper))) } override fun onError( @@ -43,16 +32,13 @@ class TestSystemKafkaInterceptor( recordMetadata: RecordMetadata?, exception: Exception ) { - val underlyingReason = extractCause(exception) - val message = record.toFailedStoveMessage(serde, underlyingReason) - store.record(Failure(ObservedMessage(message, record.toMetadata()), underlyingReason)) - logger.error("Error while producing:\n{}", message, exception) + store.record(Failure(ObservedMessage(record.toStoveMessage(objectMapper), record.toMetadata()), extractCause(exception))) + logger.error("Error while producing:\n{}", pprint(record.toStoveMessage(objectMapper)), exception) } override fun success(record: ConsumerRecord, consumer: Consumer) { - val message = record.toStoveMessage(serde) - store.record(message) - logger.info("Successfully consumed:\n{}", message) + store.record(record.toStoveMessage(objectMapper)) + logger.info("Successfully consumed:\n{}", pprint(record.toStoveMessage(objectMapper))) } override fun failure( @@ -60,10 +46,8 @@ class TestSystemKafkaInterceptor( exception: Exception, consumer: Consumer ) { - val underlyingReason = extractCause(exception) - val message = record.toFailedStoveMessage(serde, underlyingReason) - store.record(Failure(ObservedMessage(message, record.toMetadata()), underlyingReason)) - logger.error("Error while consuming:\n{}", message, exception) + store.record(Failure(ObservedMessage(record.toStoveMessage(objectMapper), record.toMetadata()), extractCause(exception))) + logger.error("Error while consuming:\n{}", pprint(record.toStoveMessage(objectMapper)), exception) } internal suspend fun waitUntilConsumed( @@ -73,7 +57,7 @@ class TestSystemKafkaInterceptor( ) { val getRecords = { store.consumedRecords() } getRecords.waitUntilConditionMet(atLeastIn, "While expecting the consume of '${clazz.java.simpleName}'") { - val outcome = deserializeCatching(it.value, clazz) + val outcome = readCatching(it.value, clazz) outcome.isSuccess && condition(SuccessfulParsedMessage(outcome.getOrNull().toOption(), it.metadata)) } @@ -87,8 +71,8 @@ class TestSystemKafkaInterceptor( ) { val getRecords = { store.failedRecords() } getRecords.waitUntilConditionMet(atLeastIn, "While expecting the failure of '${clazz.java.simpleName}'") { - val outcome = deserializeCatching(it.value, clazz) - outcome.isSuccess && condition(FailedParsedMessage(outcome.getOrNull().toOption(), it.metadata, it.reason)) + val outcome = readCatching(it.message.actual.value, clazz) + outcome.isSuccess && condition(FailedParsedMessage(outcome.getOrNull().toOption(), it.message.metadata, it.reason)) } throwIfSucceeded(clazz, condition) @@ -101,7 +85,7 @@ class TestSystemKafkaInterceptor( ) { val getRecords = { store.producedRecords() } getRecords.waitUntilConditionMet(atLeastIn, "While expecting the publish of '${clazz.java.simpleName}'") { - val outcome = deserializeCatching(it.value, clazz) + val outcome = readCatching(it.value, clazz) outcome.isSuccess && condition(SuccessfulParsedMessage(outcome.getOrNull().toOption(), it.metadata)) } @@ -109,59 +93,65 @@ class TestSystemKafkaInterceptor( } private fun extractCause( - listenerException: Exception - ): Exception = when (listenerException) { + listenerException: Throwable + ): Throwable = when (listenerException) { is ListenerExecutionFailedException -> - listenerException.cause - ?: AssertionError("No cause found: Listener was not able to capture the cause") + listenerException.cause ?: AssertionError("No cause found: Listener was not able to capture the cause") else -> listenerException - } as Exception + } - private fun deserializeCatching( - value: ByteArray, + private fun readCatching( + json: String, clazz: KClass - ): Result = runCatching { serde.deserialize(value, clazz.java) } - .onFailure { logger.error("[Stove#deserializeCatching] Error while deserializing: '{}'", String(value), it) } + ): Result = runCatching { + objectMapper.readValue(json, clazz.java) + } private fun throwIfFailed( clazz: KClass, selector: (message: ParsedMessage) -> Boolean - ) = store - .failedRecords() + ) = store.failedRecords() .filter { selector( FailedParsedMessage( - deserializeCatching(it.value, clazz).getOrNull().toOption(), - MessageMetadata(it.metadata.topic, it.metadata.key, it.metadata.headers), + readCatching(it.message.actual.value, clazz).getOrNull().toOption(), + MessageMetadata(it.message.metadata.topic, it.message.metadata.key, it.message.metadata.headers), it.reason ) ) - }.forEach { throw it.reason } + } + .forEach { throw it.reason } private fun throwIfSucceeded( clazz: KClass, selector: (ParsedMessage) -> Boolean - ): Unit = store - .consumedRecords() + ): Unit = store.consumedRecords() .filter { record -> selector( FailedParsedMessage( - deserializeCatching(record.value, clazz).getOrNull().toOption(), + readCatching(record.value, clazz).getOrNull().toOption(), record.metadata, getExceptionFor(clazz, selector) ) ) - }.forEach { throw AssertionError("Expected to fail but succeeded: $it") } + } + .forEach { throw AssertionError("Expected to fail but succeeded: $it") } private fun getExceptionFor( clazz: KClass, selector: (message: FailedParsedMessage) -> Boolean - ): Throwable = store - .failedRecords() + ): Throwable = store.failedRecords() .first { - selector(FailedParsedMessage(deserializeCatching(it.value, clazz).getOrNull().toOption(), it.metadata, it.reason)) - }.reason + selector( + FailedParsedMessage( + readCatching(it.message.actual.value, clazz).getOrNull().toOption(), + it.message.metadata, + it.reason + ) + ) + } + .reason private suspend fun (() -> Collection).waitUntilConditionMet( duration: Duration, 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 new file mode 100644 index 00000000..2fa29077 --- /dev/null +++ b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/KafkaSystemTests.kt @@ -0,0 +1,190 @@ +package com.trendyol.stove.testing.e2e.kafka + +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +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 +import io.kotest.core.config.AbstractProjectConfig +import io.kotest.core.spec.style.ShouldSpec +import org.apache.kafka.clients.consumer.ConsumerConfig +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.* +import org.slf4j.Logger +import org.springframework.boot.* +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.* +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.support.beans +import org.springframework.kafka.annotation.* +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory +import org.springframework.kafka.core.* +import org.springframework.kafka.listener.* +import org.springframework.util.backoff.FixedBackOff + +object KafkaSystemTestAppRunner { + fun run( + args: Array, + init: SpringApplication.() -> Unit = {} + ): ConfigurableApplicationContext = runApplication(args = args) { + init() + } +} + +@SpringBootApplication +@EnableKafka +@EnableConfigurationProperties(KafkaTestSpringBotApplication.KafkaTestSpringBotApplicationConfiguration::class) +open class KafkaTestSpringBotApplication { + private val logger: Logger = org.slf4j.LoggerFactory.getLogger(javaClass) + + @ConfigurationProperties(prefix = "kafka") + @ConstructorBinding + data class KafkaTestSpringBotApplicationConfiguration( + val bootstrapServers: String, + val groupId: String, + val offset: String + ) + + @Bean + open fun kafkaListenerContainerFactory( + consumerFactory: ConsumerFactory, + interceptor: RecordInterceptor, + recoverer: DeadLetterPublishingRecoverer + ): ConcurrentKafkaListenerContainerFactory { + val factory = ConcurrentKafkaListenerContainerFactory() + factory.consumerFactory = consumerFactory + factory.setCommonErrorHandler( + DefaultErrorHandler( + recoverer, + FixedBackOff(20, 1) + ).also { it.addNotRetryableExceptions(StoveBusinessException::class.java) } + ) + factory.setRecordInterceptor(interceptor) + return factory + } + + @Bean + open fun recoverer( + kafkaTemplate: KafkaTemplate<*, *> + ): DeadLetterPublishingRecoverer = DeadLetterPublishingRecoverer(kafkaTemplate) + + @Bean + open fun consumerFactory( + config: KafkaTestSpringBotApplicationConfiguration + ): ConsumerFactory = DefaultKafkaConsumerFactory( + mapOf( + ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, + ConsumerConfig.GROUP_ID_CONFIG to config.groupId, + ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to config.offset, + ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, + ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to 2000, + ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG to 6000, + ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG to 6000 + ) + ) + + @Bean + open fun kafkaTemplate( + config: KafkaTestSpringBotApplicationConfiguration + ): KafkaTemplate = KafkaTemplate( + DefaultKafkaProducerFactory( + mapOf( + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java, + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java, + ProducerConfig.ACKS_CONFIG to "1" + ) + ) + ) + + @KafkaListener(topics = ["topic"], groupId = "group_id") + fun listen(message: String) { + logger.info("Received Message in consumer: $message") + } + + @KafkaListener(topics = ["topic-failed"], groupId = "group_id") + fun listen_failed(message: String) { + logger.info("Received Message in failed consumer: $message") + throw StoveBusinessException("This exception is thrown intentionally for testing purposes.") + } + + @KafkaListener(topics = ["topic-failed.DLT"], groupId = "group_id") + fun listen_dead_letter(message: String) { + logger.info("Received Message in the lead letter, and allowing the fail by just logging: $message") + } +} + +class StoveBusinessException(message: String) : Exception(message) + +class Setup : AbstractProjectConfig() { + override suspend fun beforeProject(): Unit = + TestSystem() + .with { + kafka { + KafkaSystemOptions( + configureExposedConfiguration = { + listOf( + "kafka.bootstrapServers=${it.bootstrapServers}", + "kafka.groupId=test-group", + "kafka.offset=earliest" + ) + }, + containerOptions = KafkaContainerOptions { + } + ) + } + springBoot({ params -> + KafkaSystemTestAppRunner.run(params) { + addInitializers( + beans { + bean>() + bean { StoveObjectMapper.Default } + } + ) + } + }) + }.run() + + override suspend fun afterProject(): Unit = TestSystem.stop() +} + +class KafkaSystemTests : ShouldSpec({ + should("publish and consume") { + validate { + kafka { + val message = "this message is coming from ${testCase.descriptor.id.value} and testName is ${testCase.name.testName}" + val headers = mapOf("x-user-id" to "1") + publish("topic", message, headers = headers) + shouldBePublished { + actual == message && this.metadata.headers["x-user-id"] == "1" && this.metadata.topic == "topic" + } + shouldBeConsumed { + actual == message && this.metadata.headers["x-user-id"] == "1" && this.metadata.topic == "topic" + } + } + } + } + + should("publish and consume with failed consumer") { + shouldThrowMaybe { + validate { + kafka { + val message = "this message is coming from ${testCase.descriptor.id.value} and testName is ${testCase.name.testName}" + val headers = mapOf("x-user-id" to "1") + publish("topic-failed", message, headers = headers) + shouldBePublished { + actual == message && this.metadata.headers["x-user-id"] == "1" && this.metadata.topic == "topic-failed" + } + shouldBeFailed { + actual == message && this.metadata.headers["x-user-id"] == "1" && this.metadata.topic == "topic-failed" && reason is StoveBusinessException + } + + shouldBePublished { + actual == message && this.metadata.headers["x-user-id"] == "1" && this.metadata.topic == "topic-failed.DLT" + } + } + } + } + } +}) diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/ProtobufSerdeKafkaSystemTest.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/ProtobufSerdeKafkaSystemTest.kt deleted file mode 100644 index fd47eb29..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/ProtobufSerdeKafkaSystemTest.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.trendyol.stove.testing.e2e.kafka.protobufserde - -import com.google.protobuf.Message -import com.trendyol.stove.spring.testing.e2e.kafka.v1.* -import com.trendyol.stove.spring.testing.e2e.kafka.v1.Example.* -import com.trendyol.stove.testing.e2e.kafka.* -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 -import io.confluent.kafka.streams.serdes.protobuf.KafkaProtobufSerde -import io.kotest.core.spec.style.ShouldSpec -import kotlinx.serialization.ExperimentalSerializationApi -import org.springframework.context.support.beans -import kotlin.random.Random - -@Suppress("UNCHECKED_CAST") -@OptIn(ExperimentalSerializationApi::class) -class StoveProtobufSerde : StoveSerde { - private val parseFromMethod = "parseFrom" - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde() - - override fun serialize(value: Any): ByteArray = protobufSerde.serializer().serialize("any", value as Message) - - override fun deserialize(value: ByteArray, clazz: Class): T { - val incoming: Message = protobufSerde.deserializer().deserialize("any", value) - incoming.isAssignableFrom(clazz).also { isAssignableFrom -> - require(isAssignableFrom) { - "Expected '${clazz.simpleName}' but got '${incoming.descriptorForType.name}'. " + - "This could be transient ser/de problem since the message stream is constantly checked if the expected message is arrived, " + - "so you can ignore this error if you are sure that the message is the expected one." - } - } - - val parseFromMethod = clazz.getDeclaredMethod(parseFromMethod, ByteArray::class.java) - val parsed = parseFromMethod(incoming, incoming.toByteArray()) as T - return parsed - } -} - -private fun Message.isAssignableFrom(clazz: Class<*>): Boolean = this.descriptorForType.name == clazz.simpleName - -class ProtobufSerdeKafkaSystemTest : - ShouldSpec({ - beforeSpec { - TestSystem() - .with { - kafka { - KafkaSystemOptions( - configureExposedConfiguration = { - listOf( - "kafka.bootstrapServers=${it.bootstrapServers}", - "kafka.groupId=test-group", - "kafka.offset=earliest", - "kafka.schemaRegistryUrl=mock://mock-registry" - ) - }, - containerOptions = KafkaContainerOptions { - } - ) - } - springBoot( - runner = { params -> - KafkaTestSpringBotApplicationForProtobufSerde.run(params) { - addInitializers( - beans { - bean>() - bean { StoveProtobufSerde() } - } - ) - } - }, - withParameters = listOf( - "spring.lifecycle.timeout-per-shutdown-phase=0s" - ) - ) - }.run() - } - - afterSpec { - TestSystem.stop() - } - - should("publish and consume") { - validate { - kafka { - val userId = Random.nextInt().toString() - val productId = Random.nextInt().toString() - val product = product { - id = productId - name = "product-${Random.nextInt()}" - price = Random.nextDouble() - currency = "eur" - description = "description-${Random.nextInt()}" - } - val headers = mapOf("x-user-id" to userId) - publish("topic-protobuf", product, headers = headers) - shouldBePublished { - actual == product && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-protobuf" - } - shouldBeConsumed { - actual == product && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-protobuf" - } - - val orderId = Random.nextInt().toString() - val order = order { - id = orderId - customerId = userId - products += product - } - publish("topic-protobuf", order, headers = headers) - shouldBePublished { - actual == order && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-protobuf" - } - - shouldBeConsumed { - actual == order && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-protobuf" - } - } - } - } - }) diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/app.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/app.kt deleted file mode 100644 index 60e6478e..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/protobufserde/app.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.trendyol.stove.testing.e2e.kafka.protobufserde - -import com.google.protobuf.Message -import com.trendyol.stove.testing.e2e.kafka.StoveBusinessException -import io.confluent.kafka.schemaregistry.testutil.MockSchemaRegistry -import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG -import io.confluent.kafka.streams.serdes.protobuf.KafkaProtobufSerde -import org.apache.kafka.clients.consumer.ConsumerConfig -import org.apache.kafka.clients.producer.ProducerConfig -import org.apache.kafka.common.serialization.* -import org.slf4j.* -import org.springframework.boot.* -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.context.properties.* -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Bean -import org.springframework.kafka.annotation.* -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory -import org.springframework.kafka.core.* -import org.springframework.kafka.listener.* -import org.springframework.util.backoff.FixedBackOff - -sealed class KafkaRegistry( - open val url: String -) { - object Mock : KafkaRegistry("mock://mock-registry") - - data class Defined( - override val url: String - ) : KafkaRegistry(url) - - companion object { - fun createSerde(registry: KafkaRegistry = Mock): KafkaProtobufSerde { - val schemaRegistryClient = when (registry) { - is Mock -> MockSchemaRegistry.getClientForScope("mock-registry") - is Defined -> MockSchemaRegistry.getClientForScope(registry.url) - } - val serde: KafkaProtobufSerde = KafkaProtobufSerde(schemaRegistryClient) - val serdeConfig: MutableMap = HashMap() - serdeConfig[SCHEMA_REGISTRY_URL_CONFIG] = registry.url - serde.configure(serdeConfig, false) - return serde - } - } -} - -class ProtobufValueSerializer : Serializer { - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde() - - override fun serialize( - topic: String, - data: T - ): ByteArray = when (data) { - is ByteArray -> data - else -> protobufSerde.serializer().serialize(topic, data as Message) - } -} - -class ProtobufValueDeserializer : Deserializer { - private val protobufSerde: KafkaProtobufSerde = KafkaRegistry.createSerde() - - override fun deserialize( - topic: String, - data: ByteArray - ): Message = protobufSerde.deserializer().deserialize(topic, data) -} - -@SpringBootApplication(scanBasePackages = ["com.trendyol.stove.testing.e2e.kafka.protobufserde"]) -@EnableKafka -@EnableConfigurationProperties(KafkaTestSpringBotApplicationForProtobufSerde.ProtobufSerdeKafkaConf::class) -open class KafkaTestSpringBotApplicationForProtobufSerde { - companion object { - fun run( - args: Array, - init: SpringApplication.() -> Unit = {} - ): ConfigurableApplicationContext { - System.setProperty("org.springframework.boot.logging.LoggingSystem", "none") - return runApplication(args = args) { - webApplicationType = WebApplicationType.NONE - init() - } - } - } - - private val logger: Logger = LoggerFactory.getLogger(javaClass) - - @ConfigurationProperties(prefix = "kafka") - @ConstructorBinding - data class ProtobufSerdeKafkaConf( - val bootstrapServers: String, - val groupId: String, - val offset: String, - val schemaRegistryUrl: String - ) - - @Bean - open fun createConfiguredSerdeForRecordValues(config: ProtobufSerdeKafkaConf): KafkaProtobufSerde { - val registry = when { - config.schemaRegistryUrl.contains("mock://") -> KafkaRegistry.Mock - else -> KafkaRegistry.Defined(config.schemaRegistryUrl) - } - return KafkaRegistry.createSerde(registry) - } - - @Bean - open fun kafkaListenerContainerFactory( - consumerFactory: ConsumerFactory, - interceptor: RecordInterceptor, - recoverer: DeadLetterPublishingRecoverer - ): ConcurrentKafkaListenerContainerFactory { - val factory = ConcurrentKafkaListenerContainerFactory() - factory.consumerFactory = consumerFactory - factory.setCommonErrorHandler( - DefaultErrorHandler( - recoverer, - FixedBackOff(20, 1) - ).also { it.addNotRetryableExceptions(StoveBusinessException::class.java) } - ) - factory.setRecordInterceptor(interceptor) - return factory - } - - @Bean - open fun recoverer( - kafkaTemplate: KafkaTemplate<*, *> - ): DeadLetterPublishingRecoverer = DeadLetterPublishingRecoverer(kafkaTemplate) - - @Bean - open fun consumerFactory( - config: ProtobufSerdeKafkaConf - ): ConsumerFactory = DefaultKafkaConsumerFactory( - mapOf( - ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, - ConsumerConfig.GROUP_ID_CONFIG to config.groupId, - ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to config.offset, - ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to Serdes.String().deserializer().javaClass, - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to ProtobufValueDeserializer().javaClass, - ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to 2000, - ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG to 6000, - ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG to 6000 - ) - ) - - @Bean - open fun kafkaTemplate( - config: ProtobufSerdeKafkaConf - ): KafkaTemplate = KafkaTemplate( - DefaultKafkaProducerFactory( - mapOf( - ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to Serdes.String().serializer().javaClass, - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to ProtobufValueSerializer().javaClass, - ProducerConfig.ACKS_CONFIG to "1" - ) - ) - ) - - @KafkaListener(topics = ["topic-protobuf"], groupId = "group_id") - fun listen(message: Message) { - logger.info("Received Message in consumer: $message") - } -} diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/shared.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/shared.kt deleted file mode 100644 index 84c06a09..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/shared.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.trendyol.stove.testing.e2e.kafka - -import io.kotest.common.ExperimentalKotest -import io.kotest.core.config.AbstractProjectConfig - -class Setup : AbstractProjectConfig() { - @ExperimentalKotest - override val concurrentSpecs: Int = 1 -} - -class StoveBusinessException( - message: String -) : Exception(message) diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/StringSerdeKafkaSystemTest.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/StringSerdeKafkaSystemTest.kt deleted file mode 100644 index 404f8fed..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/StringSerdeKafkaSystemTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.trendyol.stove.testing.e2e.kafka.stringserde - -import arrow.core.some -import com.trendyol.stove.testing.e2e.kafka.* -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 -import io.kotest.core.spec.style.ShouldSpec -import io.kotest.matchers.shouldBe -import org.apache.kafka.clients.admin.NewTopic -import org.springframework.context.support.beans -import kotlin.random.Random - -class StringSerdeKafkaSystemTests : - ShouldSpec({ - beforeSpec { - TestSystem() - .with { - kafka { - KafkaSystemOptions( - configureExposedConfiguration = { - listOf( - "kafka.bootstrapServers=${it.bootstrapServers}", - "kafka.groupId=test-group", - "kafka.offset=earliest" - ) - }, - containerOptions = KafkaContainerOptions { - } - ) - } - springBoot( - runner = { params -> - KafkaTestSpringBotApplicationForStringSerde.run(params) { - addInitializers( - beans { - bean>() - bean { StoveSerde.jackson.anyByteArraySerde() } - } - ) - } - }, - withParameters = listOf( - "spring.lifecycle.timeout-per-shutdown-phase=0s" - ) - ) - }.run() - } - - afterSpec { - TestSystem.stop() - } - - should("publish and consume") { - validate { - kafka { - val userId = Random.nextInt().toString() - val message = - "this message is coming from ${testCase.descriptor.id.value} and testName is ${testCase.name.testName}" - val headers = mapOf("x-user-id" to userId) - publish("topic", message, headers = headers) - shouldBePublished { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic" - } - shouldBeConsumed { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic" - } - } - } - } - - should("publish and consume with failed consumer") { - shouldThrowMaybe { - validate { - kafka { - val userId = Random.nextInt().toString() - val message = - "this message is coming from ${testCase.descriptor.id.value} and testName is ${testCase.name.testName}" - val headers = mapOf("x-user-id" to userId) - publish("topic-failed", message, headers = headers) - shouldBePublished { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-failed" - } - shouldBeFailed { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-failed" && reason is StoveBusinessException - } - - shouldBePublished { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic-failed.DLT" - } - } - } - } - } - - should("admin operations") { - validate { - kafka { - adminOperations { - val topic = "topic" - createTopics(listOf(NewTopic(topic, 1, 1))) - listTopics().names().get().contains(topic) shouldBe true - deleteTopics(listOf(topic)) - listTopics().names().get().contains(topic) shouldBe false - } - } - } - } - - should("publish with ser/de") { - validate { - kafka { - val userId = Random.nextInt().toString() - val message = - "this message is coming from ${testCase.descriptor.id.value} and testName is ${testCase.name.testName}" - val headers = mapOf("x-user-id" to userId) - publish("topic", message, serde = StoveSerde.jackson.anyJsonStringSerde().some(), headers = headers) - shouldBePublished { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic" - } - shouldBeConsumed { - actual == message && this.metadata.headers["x-user-id"] == userId && this.metadata.topic == "topic" - } - } - } - } - }) diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/app.kt b/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/app.kt deleted file mode 100644 index b5f804f4..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/kotlin/com/trendyol/stove/testing/e2e/kafka/stringserde/app.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.trendyol.stove.testing.e2e.kafka.stringserde - -import com.trendyol.stove.testing.e2e.kafka.StoveBusinessException -import org.apache.kafka.clients.consumer.ConsumerConfig -import org.apache.kafka.clients.producer.ProducerConfig -import org.apache.kafka.common.serialization.Serdes -import org.slf4j.* -import org.springframework.boot.* -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.context.properties.* -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Bean -import org.springframework.kafka.annotation.* -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory -import org.springframework.kafka.core.* -import org.springframework.kafka.listener.* -import org.springframework.util.backoff.FixedBackOff - -@SpringBootApplication(scanBasePackages = ["com.trendyol.stove.testing.e2e.kafka.stringserde"]) -@EnableKafka -@EnableConfigurationProperties(KafkaTestSpringBotApplicationForStringSerde.StringSerdeKafkaConf::class) -open class KafkaTestSpringBotApplicationForStringSerde { - companion object { - fun run( - args: Array, - init: SpringApplication.() -> Unit = {} - ): ConfigurableApplicationContext { - System.setProperty("org.springframework.boot.logging.LoggingSystem", "none") - return runApplication(args = args) { - webApplicationType = WebApplicationType.NONE - init() - } - } - } - - private val logger: Logger = LoggerFactory.getLogger(javaClass) - - @ConfigurationProperties(prefix = "kafka") - @ConstructorBinding - data class StringSerdeKafkaConf( - val bootstrapServers: String, - val groupId: String, - val offset: String - ) - - @Bean - open fun kafkaListenerContainerFactory( - consumerFactory: ConsumerFactory, - interceptor: RecordInterceptor, - recoverer: DeadLetterPublishingRecoverer - ): ConcurrentKafkaListenerContainerFactory { - val factory = ConcurrentKafkaListenerContainerFactory() - factory.consumerFactory = consumerFactory - factory.setCommonErrorHandler( - DefaultErrorHandler( - recoverer, - FixedBackOff(20, 1) - ).also { it.addNotRetryableExceptions(StoveBusinessException::class.java) } - ) - factory.setRecordInterceptor(interceptor) - return factory - } - - @Bean - open fun recoverer( - kafkaTemplate: KafkaTemplate<*, *> - ): DeadLetterPublishingRecoverer = DeadLetterPublishingRecoverer(kafkaTemplate) - - @Bean - open fun consumerFactory( - config: StringSerdeKafkaConf - ): ConsumerFactory = DefaultKafkaConsumerFactory( - mapOf( - ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, - ConsumerConfig.GROUP_ID_CONFIG to config.groupId, - ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to config.offset, - ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to Serdes.String().deserializer().javaClass, - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to Serdes.String().deserializer().javaClass, - ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG to 2000, - ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG to 6000, - ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG to 6000 - ) - ) - - @Bean - open fun kafkaTemplate( - config: StringSerdeKafkaConf - ): KafkaTemplate = KafkaTemplate( - DefaultKafkaProducerFactory( - mapOf( - ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to config.bootstrapServers, - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to Serdes.String().serializer().javaClass, - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to Serdes.String().serializer().javaClass, - ProducerConfig.ACKS_CONFIG to "1" - ) - ) - ) - - @KafkaListener(topics = ["topic"], groupId = "group_id") - fun listen(message: String) { - logger.info("Received Message in consumer: \n$message") - } - - @KafkaListener(topics = ["topic-failed"], groupId = "group_id") - fun listenFailed(message: String) { - logger.info("Received Message in failed consumer: \n$message") - throw StoveBusinessException("This exception is thrown intentionally for testing purposes.") - } - - @KafkaListener(topics = ["topic-failed.DLT"], groupId = "group_id") - fun listenDeadLetter(message: String) { - logger.info("Received Message in the lead letter, and allowing the fail by just logging: \n$message") - } -} diff --git a/starters/spring/stove-spring-testing-e2e-kafka/src/test/proto/example.proto b/starters/spring/stove-spring-testing-e2e-kafka/src/test/proto/example.proto deleted file mode 100644 index 9ce09ee5..00000000 --- a/starters/spring/stove-spring-testing-e2e-kafka/src/test/proto/example.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -// buf:lint:ignore PACKAGE_DIRECTORY_MATCH -package com.trendyol.stove.spring.testing.e2e.kafka.v1; - -message Product { - string id = 1; - string name = 2; - string description = 3; - double price = 4; - string currency = 5; -} - -message Order { - string id = 1; - string customerId = 2; - repeated Product products = 3; -} diff --git a/starters/spring/stove-spring-testing-e2e/api/stove-spring-testing-e2e.api b/starters/spring/stove-spring-testing-e2e/api/stove-spring-testing-e2e.api deleted file mode 100644 index cc080aa3..00000000 --- a/starters/spring/stove-spring-testing-e2e/api/stove-spring-testing-e2e.api +++ /dev/null @@ -1,36 +0,0 @@ -public abstract class com/trendyol/stove/testing/e2e/BaseApplicationContextInitializer : org/springframework/context/ApplicationContextInitializer { - public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - protected fun applicationReady (Lorg/springframework/context/support/GenericApplicationContext;)V - public synthetic fun initialize (Lorg/springframework/context/ConfigurableApplicationContext;)V - public fun initialize (Lorg/springframework/context/support/GenericApplicationContext;)V - protected fun onEvent (Lorg/springframework/context/ApplicationEvent;)V - protected final fun register (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/BaseApplicationContextInitializer; -} - -public final class com/trendyol/stove/testing/e2e/BridgeSystemKt { - public static final fun bridge-hQma78k (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - -public final class com/trendyol/stove/testing/e2e/SpringApplicationUnderTest : com/trendyol/stove/testing/e2e/system/abstractions/ApplicationUnderTest { - public static final field Companion Lcom/trendyol/stove/testing/e2e/SpringApplicationUnderTest$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;)V - public fun start (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun stop (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class com/trendyol/stove/testing/e2e/SpringApplicationUnderTest$Companion { -} - -public final class com/trendyol/stove/testing/e2e/SpringApplicationUnderTestKt { - public static final fun springBoot-FMzRXaI (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;)Lcom/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem; - public static synthetic fun springBoot-FMzRXaI$default (Lcom/trendyol/stove/testing/e2e/system/TestSystem;Lkotlin/jvm/functions/Function1;Ljava/util/List;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/system/abstractions/ReadyTestSystem; -} - -public final class com/trendyol/stove/testing/e2e/SpringBridgeSystem : com/trendyol/stove/testing/e2e/system/BridgeSystem, com/trendyol/stove/testing/e2e/system/abstractions/AfterRunAwareWithContext, com/trendyol/stove/testing/e2e/system/abstractions/PluggedSystem { - public fun (Lcom/trendyol/stove/testing/e2e/system/TestSystem;)V - public fun get (Lkotlin/reflect/KClass;)Ljava/lang/Object; - public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; -} - diff --git a/starters/spring/stove-spring-testing-e2e/build.gradle.kts b/starters/spring/stove-spring-testing-e2e/build.gradle.kts index 35658939..5e2fb5a9 100644 --- a/starters/spring/stove-spring-testing-e2e/build.gradle.kts +++ b/starters/spring/stove-spring-testing-e2e/build.gradle.kts @@ -9,6 +9,3 @@ dependencies { testImplementation(libs.slf4j.simple) } -tasks.test.configure { - systemProperty("kotest.framework.config.fqn", "com.trendyol.stove.testing.e2e.Stove") -} diff --git a/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BaseApplicationContextInitializer.kt b/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BaseApplicationContextInitializer.kt index 19b45669..5ab499a1 100644 --- a/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BaseApplicationContextInitializer.kt +++ b/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BaseApplicationContextInitializer.kt @@ -6,9 +6,8 @@ import org.springframework.context.* import org.springframework.context.support.* @StoveDsl -abstract class BaseApplicationContextInitializer( - registration: BeanDefinitionDsl.() -> Unit = {} -) : ApplicationContextInitializer { +abstract class BaseApplicationContextInitializer(registration: BeanDefinitionDsl.() -> Unit = {}) : + ApplicationContextInitializer { private var registrations = mutableListOf<(BeanDefinitionDsl) -> Unit>() private val beans = beans {} diff --git a/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BridgeSystem.kt b/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BridgeSystem.kt index 5d9111af..c7ec03ef 100644 --- a/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BridgeSystem.kt +++ b/starters/spring/stove-spring-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/BridgeSystem.kt @@ -14,9 +14,7 @@ import kotlin.reflect.KClass @StoveDsl class SpringBridgeSystem( override val testSystem: TestSystem -) : BridgeSystem(testSystem), - PluggedSystem, - AfterRunAwareWithContext { +) : PluggedSystem, AfterRunAwareWithContext, BridgeSystem(testSystem) { override fun get(klass: KClass): D = ctx.getBean(klass.java) } 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 583780f2..9b7bccc3 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.StoveSerde +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper import com.trendyol.stove.testing.e2e.system.* import com.trendyol.stove.testing.e2e.system.TestSystem.Companion.validate import io.kotest.core.config.AbstractProjectConfig @@ -40,11 +40,10 @@ class SystemTimeGetUtcNow : GetUtcNow { override fun invoke(): Instant = GetUtcNow.frozenTime } -class TestAppInitializers : - BaseApplicationContextInitializer({ - bean { StoveSerde.jackson.default } - bean { SystemTimeGetUtcNow() } - }) { +class TestAppInitializers : BaseApplicationContextInitializer({ + bean { StoveObjectMapper.Default } + bean { SystemTimeGetUtcNow() } +}) { var onEvent: Boolean = false var appReady: Boolean = false @@ -80,7 +79,7 @@ class ParameterCollectorOfSpringBoot( get() = applicationArguments.sourceArgs.toList() } -class Stove : AbstractProjectConfig() { +class Setup : AbstractProjectConfig() { override suspend fun beforeProject(): Unit = TestSystem() .with { @@ -102,69 +101,68 @@ class Stove : AbstractProjectConfig() { override suspend fun afterProject(): Unit = TestSystem.stop() } -class BridgeSystemTests : - ShouldSpec({ - should("bridge to application") { - validate { - using { - whatIsTheTime() shouldBe GetUtcNow.frozenTime - } - - using { - parameters shouldBe listOf( - "--test-system=true", - "--context=SetupOfBridgeSystemTests" - ) - } +class BridgeSystemTests : ShouldSpec({ + should("bridge to application") { + validate { + using { + whatIsTheTime() shouldBe GetUtcNow.frozenTime + } + + using { + parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + } - delay(5.seconds) - using { - appReady shouldBe true - onEvent shouldBe true - } + delay(5.seconds) + using { + appReady shouldBe true + onEvent shouldBe true } } + } - should("resolve multiple") { - validate { - using { getUtcNow: GetUtcNow, testAppInitializers: TestAppInitializers -> - getUtcNow() shouldBe GetUtcNow.frozenTime - testAppInitializers.appReady shouldBe true - testAppInitializers.onEvent shouldBe true - } - - using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot -> - getUtcNow() shouldBe GetUtcNow.frozenTime - testAppInitializers.appReady shouldBe true - testAppInitializers.onEvent shouldBe true - parameterCollectorOfSpringBoot.parameters shouldBe listOf( - "--test-system=true", - "--context=SetupOfBridgeSystemTests" - ) - } - - using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot, exampleService -> - getUtcNow() shouldBe GetUtcNow.frozenTime - testAppInitializers.appReady shouldBe true - testAppInitializers.onEvent shouldBe true - parameterCollectorOfSpringBoot.parameters shouldBe listOf( - "--test-system=true", - "--context=SetupOfBridgeSystemTests" - ) - exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime - } - - using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot, exampleService, objectMapper -> - getUtcNow() shouldBe GetUtcNow.frozenTime - testAppInitializers.appReady shouldBe true - testAppInitializers.onEvent shouldBe true - parameterCollectorOfSpringBoot.parameters shouldBe listOf( - "--test-system=true", - "--context=SetupOfBridgeSystemTests" - ) - exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime - objectMapper.writeValueAsString(mapOf("a" to "b")) shouldBe """{"a":"b"}""" - } + should("resolve multiple") { + validate { + using { getUtcNow: GetUtcNow, testAppInitializers: TestAppInitializers -> + getUtcNow() shouldBe GetUtcNow.frozenTime + testAppInitializers.appReady shouldBe true + testAppInitializers.onEvent shouldBe true + } + + using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot -> + getUtcNow() shouldBe GetUtcNow.frozenTime + testAppInitializers.appReady shouldBe true + testAppInitializers.onEvent shouldBe true + parameterCollectorOfSpringBoot.parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + } + + using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot, exampleService -> + getUtcNow() shouldBe GetUtcNow.frozenTime + testAppInitializers.appReady shouldBe true + testAppInitializers.onEvent shouldBe true + parameterCollectorOfSpringBoot.parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime + } + + using { getUtcNow, testAppInitializers, parameterCollectorOfSpringBoot, exampleService, objectMapper -> + getUtcNow() shouldBe GetUtcNow.frozenTime + testAppInitializers.appReady shouldBe true + testAppInitializers.onEvent shouldBe true + parameterCollectorOfSpringBoot.parameters shouldBe listOf( + "--test-system=true", + "--context=SetupOfBridgeSystemTests" + ) + exampleService.whatIsTheTime() shouldBe GetUtcNow.frozenTime + objectMapper.writeValueAsString(mapOf("a" to "b")) shouldBe """{"a":"b"}""" } } - }) + } +})