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 index 3b417400..15e3dca0 100644 --- a/lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api +++ b/lib/stove-testing-e2e-elasticsearch/api/stove-testing-e2e-elasticsearch.api @@ -1,18 +1,3 @@ -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 @@ -52,19 +37,20 @@ public abstract interface annotation class com/trendyol/stove/testing/e2e/elasti } 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 fun (Ljava/lang/String;ILjava/lang/String;Z)V + public synthetic fun (Ljava/lang/String;ILjava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)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 final fun component4 ()Z + public final fun copy (Ljava/lang/String;ILjava/lang/String;Z)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;ZILjava/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 final fun isSecure ()Z public fun toString ()Ljava/lang/String; } @@ -101,45 +87,80 @@ public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExp 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 field baseUrl Ljava/lang/String; + public field httpClient Lio/ktor/client/HttpClient; public fun afterRun (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun close ()V public fun configuration ()Ljava/util/List; + public final fun createIndex (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createIndex$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun deleteIndex (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun ensureSuccess (Lio/ktor/client/statement/HttpResponse;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun ensureSuccessOrNotFound (Lio/ktor/client/statement/HttpResponse;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun executeWithReuseCheck (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getEsClient ()Lco/elastic/clients/elasticsearch/ElasticsearchClient; + public final fun extractSearchHits (Lcom/fasterxml/jackson/databind/JsonNode;Ljava/lang/Class;)Ljava/util/List; + public final fun extractSource (Lcom/fasterxml/jackson/databind/JsonNode;Ljava/lang/Class;)Ljava/lang/Object; + public final fun getBaseUrl ()Ljava/lang/String; + public final fun getHttpClient ()Lio/ktor/client/HttpClient; + public final fun getObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; public fun getTestSystem ()Lcom/trendyol/stove/testing/e2e/system/TestSystem; + public final fun indexExists (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun pause ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem; + public final fun refreshIndex (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun requireValidIndex (Ljava/lang/String;)V + public final fun requireValidKey (Ljava/lang/String;)V 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 final fun setBaseUrl (Ljava/lang/String;)V + public final fun setHttpClient (Lio/ktor/client/HttpClient;)V + public final fun shouldDelete (Ljava/lang/String;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 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 fun baseUrl (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem;)Ljava/lang/String; + public final fun client (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem;)Lio/ktor/client/HttpClient; +} + +public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$Endpoint { + public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$Endpoint; + public final fun document (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public final fun index (Ljava/lang/String;)Ljava/lang/String; + public final fun refresh (Ljava/lang/String;)Ljava/lang/String; + public final fun search (Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$QueryParam { + public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$QueryParam; + public static final field REFRESH Ljava/lang/String; + public static final field WAIT_FOR Ljava/lang/String; +} + +public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$ResponseField { + public static final field HITS Ljava/lang/String; + public static final field INSTANCE Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem$ResponseField; + public static final field SOURCE Ljava/lang/String; } public class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions : com/trendyol/stove/testing/e2e/database/migrations/SupportsMigrations, 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/elasticsearch/ElasticsearchSystemOptions$Companion; - public fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function2;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/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Larrow/core/Option;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lcom/fasterxml/jackson/databind/ObjectMapper;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Larrow/core/Option;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions;Lcom/fasterxml/jackson/databind/ObjectMapper;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCleanup ()Lkotlin/jvm/functions/Function2; - public fun getClientConfigurer ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer; public fun getConfigureExposedConfiguration ()Lkotlin/jvm/functions/Function1; public fun getContainer ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticContainerOptions; - public fun getJsonpMapper ()Lco/elastic/clients/json/JsonpMapper; + public fun getHttpClientConfigurer ()Larrow/core/Option; public fun getMigrationCollection ()Lcom/trendyol/stove/testing/e2e/database/migrations/MigrationCollection; + public fun getObjectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; public synthetic fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/database/migrations/SupportsMigrations; public fun migrations (Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions; } public final class com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions$Companion { - public final fun provided (Ljava/lang/String;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lco/elastic/clients/json/JsonpMapper;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ProvidedElasticsearchSystemOptions; - public static synthetic fun provided$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions$Companion;Ljava/lang/String;ILjava/lang/String;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificate;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lco/elastic/clients/json/JsonpMapper;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ProvidedElasticsearchSystemOptions; + public final fun provided (Ljava/lang/String;ILjava/lang/String;ZLarrow/core/Option;Lcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ProvidedElasticsearchSystemOptions; + public static synthetic fun provided$default (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions$Companion;Ljava/lang/String;ILjava/lang/String;ZLarrow/core/Option;Lcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/trendyol/stove/testing/e2e/elasticsearch/ProvidedElasticsearchSystemOptions; } public final class com/trendyol/stove/testing/e2e/elasticsearch/ExtensionsKt { @@ -148,8 +169,8 @@ public final class com/trendyol/stove/testing/e2e/elasticsearch/ExtensionsKt { } public final class com/trendyol/stove/testing/e2e/elasticsearch/ProvidedElasticsearchSystemOptions : com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystemOptions, com/trendyol/stove/testing/e2e/system/abstractions/ProvidedSystemOptions { - public fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function1;)V - public synthetic fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration;Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticClientConfigurer;Lco/elastic/clients/json/JsonpMapper;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration;Larrow/core/Option;Lcom/fasterxml/jackson/databind/ObjectMapper;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function1;)V + public synthetic fun (Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration;Larrow/core/Option;Lcom/fasterxml/jackson/databind/ObjectMapper;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getConfig ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration; public fun getProvidedConfig ()Lcom/trendyol/stove/testing/e2e/elasticsearch/ElasticSearchExposedConfiguration; public synthetic fun getProvidedConfig ()Lcom/trendyol/stove/testing/e2e/system/abstractions/ExposedConfiguration; diff --git a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts index e0bc7fe2..a2d7729a 100644 --- a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts +++ b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts @@ -2,8 +2,11 @@ plugins {} dependencies { api(projects.lib.stoveTestingE2e) - api(libs.elastic) api(libs.testcontainers.elasticsearch) + api(libs.ktor.client.core) + api(libs.ktor.client.okhttp) + api(libs.ktor.client.content.negotiation) + api(libs.ktor.serialization.jackson.json) implementation(libs.jackson.databind) } 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 0b93f7f9..8fe6937a 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 @@ -1,25 +1,27 @@ +@file:Suppress("unused") + package com.trendyol.stove.testing.e2e.elasticsearch -import arrow.core.* -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.transport.rest_client.RestClientTransport +import arrow.core.getOrElse +import com.fasterxml.jackson.databind.* import com.trendyol.stove.functional.* import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.jackson.* import kotlinx.coroutines.runBlocking -import org.apache.http.HttpHost -import org.apache.http.auth.* -import org.apache.http.client.CredentialsProvider -import org.apache.http.impl.client.BasicCredentialsProvider -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder -import org.elasticsearch.client.* import org.slf4j.* -import javax.net.ssl.SSLContext -import kotlin.jvm.optionals.getOrElse +import kotlin.time.Duration.Companion.minutes +import kotlin.time.toJavaDuration +@Suppress("TooManyFunctions") @ElasticDsl class ElasticsearchSystem internal constructor( override val testSystem: TestSystem, @@ -29,7 +31,13 @@ class ElasticsearchSystem internal constructor( AfterRunAware, ExposesConfiguration { @PublishedApi - internal lateinit var esClient: ElasticsearchClient + internal lateinit var httpClient: HttpClient + + @PublishedApi + internal lateinit var baseUrl: String + + @PublishedApi + internal val objectMapper: ObjectMapper = context.options.objectMapper private lateinit var exposedConfiguration: ElasticSearchExposedConfiguration private val logger: Logger = LoggerFactory.getLogger(javaClass) @@ -41,7 +49,8 @@ class ElasticsearchSystem internal constructor( } override suspend fun afterRun() { - esClient = createEsClient(exposedConfiguration) + baseUrl = buildBaseUrl(exposedConfiguration) + httpClient = createHttpClient(exposedConfiguration) runMigrationsIfNeeded() } @@ -49,8 +58,8 @@ class ElasticsearchSystem internal constructor( override fun close(): Unit = runBlocking { Try { - context.options.cleanup(esClient) - esClient._transport().close() + context.options.cleanup(this@ElasticsearchSystem) + httpClient.close() executeWithReuseCheck { stop() } }.recover { logger.warn("got an error while stopping elasticsearch: ${it.message}") } } @@ -58,97 +67,169 @@ class ElasticsearchSystem internal constructor( override fun configuration(): List = context.options.configureExposedConfiguration(exposedConfiguration) @ElasticDsl - inline fun shouldQuery( + suspend inline fun shouldQuery( query: String, index: String, assertion: (List) -> Unit ): ElasticsearchSystem { - require(index.isNotBlank()) { "Index cannot be blank" } + requireValidIndex(index) 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() - .mapNotNull { it.source() } - .also(assertion) - .let { this } - } - @ElasticDsl - inline fun shouldQuery( - query: Query, - assertion: (List) -> Unit - ): ElasticsearchSystem = esClient - .search( - SearchRequest.of { q -> q.query(query) }, - T::class.java - ).hits() - .hits() - .mapNotNull { it.source() } - .also(assertion) - .let { this } + val response = httpClient.post(Endpoint.search(index)) { + contentType(ContentType.Application.Json) + setBody(query) + } + + response.ensureSuccess("Search query") + + val responseBody = response.body() + val results = responseBody.extractSearchHits(T::class.java) + assertion(results) + return this + } @ElasticDsl - inline fun shouldGet( + suspend inline fun shouldGet( index: String, key: String, assertion: (T) -> Unit ): ElasticsearchSystem { - require(index.isNotBlank()) { "Index cannot be blank" } - require(key.isNotBlank()) { "Key cannot be blank" } - return esClient - .get({ req -> req.index(index).id(key).refresh(true) }, T::class.java) - .source() - .toOption() - .map(assertion) - .getOrElse { throw AssertionError("Resource with key ($key) is not found") } - .let { this } + requireValidIndex(index) + requireValidKey(key) + + val response = httpClient.get(Endpoint.document(index, key)) { + parameter(QueryParam.REFRESH, true) + } + + if (response.status == HttpStatusCode.NotFound) { + throw AssertionError("Resource with key ($key) is not found") + } + + response.ensureSuccess("Get request") + + val responseBody = response.body() + val result = responseBody.extractSource(T::class.java) + ?: throw AssertionError("Resource with key ($key) is not found") + + assertion(result) + return this } @ElasticDsl - fun shouldNotExist( + suspend fun shouldNotExist( key: String, index: String ): ElasticsearchSystem { - require(index.isNotBlank()) { "Index cannot be blank" } - require(key.isNotBlank()) { "Key cannot be blank" } - val exists = esClient.exists { req -> req.index(index).id(key) } - if (exists.value()) { + requireValidIndex(index) + requireValidKey(key) + + val response = httpClient.head(Endpoint.document(index, key)) + + if (response.status.isSuccess()) { throw AssertionError("The document with the given id($key) was not expected, but found!") } + return this } @ElasticDsl - fun shouldDelete( + suspend fun shouldDelete( key: String, index: String ): ElasticsearchSystem { - require(index.isNotBlank()) { "Index cannot be blank" } - require(key.isNotBlank()) { "Key cannot be blank" } - return esClient - .delete(DeleteRequest.of { req -> req.index(index).id(key).refresh(Refresh.WaitFor) }) - .let { this } + requireValidIndex(index) + requireValidKey(key) + + val response = httpClient.delete(Endpoint.document(index, key)) { + parameter(QueryParam.REFRESH, QueryParam.WAIT_FOR) + } + + response.ensureSuccessOrNotFound("Delete request") + return this } @ElasticDsl - fun save( + suspend inline fun save( id: String, instance: T, index: String ): ElasticsearchSystem { - require(index.isNotBlank()) { "Index cannot be blank" } + requireValidIndex(index) require(id.isNotBlank()) { "Id cannot be blank" } - return esClient - .index { req -> - req - .index(index) - .id(id) - .document(instance) - .refresh(Refresh.WaitFor) - }.let { this } + + val response = httpClient.put(Endpoint.document(index, id)) { + contentType(ContentType.Application.Json) + parameter(QueryParam.REFRESH, QueryParam.WAIT_FOR) + setBody(instance) + } + + response.ensureSuccess("Save request") + return this + } + + /** + * Creates an index with the given name. + * @param index The name of the index to create + * @param settings Optional JSON settings for the index + * @return ElasticsearchSystem + */ + @ElasticDsl + suspend fun createIndex( + index: String, + settings: String? = null + ): ElasticsearchSystem { + requireValidIndex(index) + + val response = httpClient.put(Endpoint.index(index)) { + contentType(ContentType.Application.Json) + settings?.let { setBody(it) } + } + + check(response.status.isSuccess() || response.status.value == HttpStatusCode.BadRequest.value) { + "Create index request failed with status ${response.status}: ${response.bodyAsText()}" + } + + return this + } + + /** + * Deletes an index with the given name. + * @param index The name of the index to delete + * @return ElasticsearchSystem + */ + @ElasticDsl + suspend fun deleteIndex(index: String): ElasticsearchSystem { + requireValidIndex(index) + + val response = httpClient.delete(Endpoint.index(index)) + response.ensureSuccessOrNotFound("Delete index request") + return this + } + + /** + * Checks if an index exists. + * @param index The name of the index to check + * @return true if the index exists, false otherwise + */ + @ElasticDsl + suspend fun indexExists(index: String): Boolean { + requireValidIndex(index) + val response = httpClient.head(Endpoint.index(index)) + return response.status.isSuccess() + } + + /** + * Refreshes an index to make all operations performed since the last refresh available for search. + * @param index The name of the index to refresh + * @return ElasticsearchSystem + */ + @ElasticDsl + suspend fun refreshIndex(index: String): ElasticsearchSystem { + requireValidIndex(index) + + val response = httpClient.post(Endpoint.refresh(index)) + response.ensureSuccess("Refresh index request") + return this } /** @@ -169,6 +250,41 @@ class ElasticsearchSystem internal constructor( @ElasticDsl fun unpause(): ElasticsearchSystem = withContainerOrWarn("unpause") { it.unpause() } + // region Private helpers + + @PublishedApi + internal fun requireValidIndex(index: String) { + require(index.isNotBlank()) { "Index cannot be blank" } + } + + @PublishedApi + internal fun requireValidKey(key: String) { + require(key.isNotBlank()) { "Key cannot be blank" } + } + + @PublishedApi + internal suspend fun HttpResponse.ensureSuccess(operation: String) { + check(status.isSuccess()) { + "$operation failed with status $status: ${bodyAsText()}" + } + } + + @PublishedApi + internal suspend fun HttpResponse.ensureSuccessOrNotFound(operation: String) { + check(status.isSuccess() || status == HttpStatusCode.NotFound) { + "$operation failed with status $status: ${bodyAsText()}" + } + } + + @PublishedApi + internal fun JsonNode.extractSearchHits(clazz: Class): List = + this[ResponseField.HITS][ResponseField.HITS] + .mapNotNull { hit -> hit[ResponseField.SOURCE]?.let { objectMapper.treeToValue(it, clazz) } } + + @PublishedApi + internal fun JsonNode.extractSource(clazz: Class): T? = + this[ResponseField.SOURCE]?.let { objectMapper.treeToValue(it, clazz) } + private suspend fun obtainExposedConfiguration(): ElasticSearchExposedConfiguration = when { context.options is ProvidedElasticsearchSystemOptions -> context.options.config @@ -183,22 +299,13 @@ class ElasticsearchSystem internal constructor( host = container.host, port = container.firstMappedPort, password = context.options.container.password, - certificate = determineCertificate(container).getOrNull() + isSecure = !context.options.container.disableSecurity ) } - private fun determineCertificate(container: StoveElasticSearchContainer): Option = - when (context.options.container.disableSecurity) { - true -> None - - false -> ElasticsearchExposedCertificate( - container.caCertAsBytes().getOrElse { ByteArray(0) } - ).apply { sslContext = container.createSslContextFromCa() }.some() - } - private suspend fun runMigrationsIfNeeded() { if (shouldRunMigrations()) { - context.options.migrationCollection.run(esClient) + context.options.migrationCollection.run(this) } } @@ -208,56 +315,67 @@ class ElasticsearchSystem internal constructor( else -> throw UnsupportedOperationException("Unsupported runtime type: ${context.runtime::class}") } - private fun createEsClient(exposedConfiguration: ElasticSearchExposedConfiguration): ElasticsearchClient = - context.options.clientConfigurer.restClientOverrideFn - .getOrElse { { cfg -> restClient(cfg) } } - .let { RestClientTransport(it(exposedConfiguration), context.options.jsonpMapper) } - .let { ElasticsearchClient(it) } + private fun buildBaseUrl(cfg: ElasticSearchExposedConfiguration): String { + val scheme = if (cfg.isSecure) Scheme.HTTPS else Scheme.HTTP + return "$scheme://${cfg.host}:${cfg.port}" + } - private fun restClient(cfg: ElasticSearchExposedConfiguration): RestClient = - when (isSecurityDisabled(cfg)) { - true -> createInsecureRestClient(cfg) - false -> createSecureRestClient(cfg, obtainSslContext(cfg)) + private fun createHttpClient(cfg: ElasticSearchExposedConfiguration): HttpClient = + context.options.httpClientConfigurer + .getOrElse { { defaultHttpClient(cfg) } } + .invoke(cfg) + + private fun defaultHttpClient(cfg: ElasticSearchExposedConfiguration): HttpClient = + HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + followSslRedirects(true) + connectTimeout(DEFAULT_TIMEOUT.toJavaDuration()) + readTimeout(DEFAULT_TIMEOUT.toJavaDuration()) + callTimeout(DEFAULT_TIMEOUT.toJavaDuration()) + writeTimeout(DEFAULT_TIMEOUT.toJavaDuration()) + + if (cfg.isSecure) { + configureTrustAllCertificates() + } + } + } + + install(ContentNegotiation) { + register(ContentType.Application.Json, JacksonConverter(objectMapper)) + } + + defaultRequest { + url(buildBaseUrl(cfg)) + if (cfg.isSecure && cfg.password.isNotBlank()) { + header(HttpHeaders.Authorization, cfg.basicAuthHeader()) + } + } } - private fun isSecurityDisabled(cfg: ElasticSearchExposedConfiguration): Boolean = when { - context.options is ProvidedElasticsearchSystemOptions -> cfg.certificate == null - context.runtime is StoveElasticSearchContainer -> context.options.container.disableSecurity - else -> throw UnsupportedOperationException("Unsupported runtime type: ${context.runtime::class}") - } - - private fun obtainSslContext(cfg: ElasticSearchExposedConfiguration): SSLContext = when { - context.options is ProvidedElasticsearchSystemOptions -> cfg.certificate?.sslContext ?: throw IllegalStateException( - "SSL context is required for secure connections with provided instances. " + - "Set the certificate.sslContext in ElasticSearchExposedConfiguration." - ) + private fun okhttp3.OkHttpClient.Builder.configureTrustAllCertificates() { + val trustAllCerts = arrayOf( + object : javax.net.ssl.X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) = Unit - context.runtime is StoveElasticSearchContainer -> context.runtime.createSslContextFromCa() + override fun checkServerTrusted(chain: Array, authType: String) = Unit - else -> throw UnsupportedOperationException("Unsupported runtime type: ${context.runtime::class}") + override fun getAcceptedIssuers(): Array = arrayOf() + } + ) + val sslContext = javax.net.ssl.SSLContext + .getInstance(TLS_PROTOCOL) + sslContext.init(null, trustAllCerts, java.security.SecureRandom()) + sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as javax.net.ssl.X509TrustManager) + hostnameVerifier { _, _ -> true } } - private fun createInsecureRestClient(cfg: ElasticSearchExposedConfiguration): RestClient = - RestClient - .builder(HttpHost(cfg.host, cfg.port)) - .apply { setHttpClientConfigCallback { http -> http.also(context.options.clientConfigurer.httpClientBuilder) } } - .build() - - private fun createSecureRestClient( - cfg: ElasticSearchExposedConfiguration, - sslContext: SSLContext - ): RestClient { - val credentialsProvider: CredentialsProvider = BasicCredentialsProvider().apply { - setCredentials(AuthScope.ANY, UsernamePasswordCredentials("elastic", cfg.password)) - } - return RestClient - .builder(HttpHost(cfg.host, cfg.port, "https")) - .setHttpClientConfigCallback { clientBuilder: HttpAsyncClientBuilder -> - clientBuilder.setSSLContext(sslContext) - clientBuilder.setDefaultCredentialsProvider(credentialsProvider) - context.options.clientConfigurer.httpClientBuilder(clientBuilder) - clientBuilder - }.build() + private fun ElasticSearchExposedConfiguration.basicAuthHeader(): String { + val credentials = java.util.Base64 + .getEncoder() + .encodeToString("$DEFAULT_USERNAME:$password".toByteArray()) + return "Basic $credentials" } private inline fun withContainerOrWarn( @@ -285,13 +403,70 @@ class ElasticsearchSystem internal constructor( } } + // endregion + companion object { + private val DEFAULT_TIMEOUT = 5.minutes + private const val DEFAULT_USERNAME = "elastic" + private const val TLS_PROTOCOL = "TLS" + /** - * Exposes the [ElasticsearchClient] for the given [ElasticsearchSystem] - * This is useful for custom queries + * Exposes the [HttpClient] for the given [ElasticsearchSystem]. + * This is useful for custom operations. */ @Suppress("unused") @ElasticDsl - fun ElasticsearchSystem.client(): ElasticsearchClient = this.esClient + fun ElasticsearchSystem.client(): HttpClient = this.httpClient + + /** + * Returns the base URL for the Elasticsearch instance. + */ + @Suppress("unused") + @ElasticDsl + fun ElasticsearchSystem.baseUrl(): String = this.baseUrl + } + + /** + * Elasticsearch API endpoint builders (relative paths). + */ + @PublishedApi + internal object Endpoint { + private const val DOC = "_doc" + private const val SEARCH = "_search" + private const val REFRESH = "_refresh" + + fun index(index: String): String = "/$index" + + fun document(index: String, id: String): String = "/$index/$DOC/$id" + + fun search(index: String): String = "/$index/$SEARCH" + + fun refresh(index: String): String = "/$index/$REFRESH" + } + + /** + * Elasticsearch response field names. + */ + @PublishedApi + internal object ResponseField { + const val HITS = "hits" + const val SOURCE = "_source" + } + + /** + * Query parameter names and values. + */ + @PublishedApi + internal object QueryParam { + const val REFRESH = "refresh" + const val WAIT_FOR = "wait_for" + } + + /** + * URL scheme constants. + */ + private object Scheme { + const val HTTP = "http" + const val HTTPS = "https" } } 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 814d5d5a..ffcf8e80 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 @@ -26,7 +26,7 @@ internal fun TestSystem.elasticsearch(): ElasticsearchSystem = * ```kotlin * elasticsearch { * ElasticsearchSystemOptions( - * cleanup = { client -> client.indices().delete { it.index("*") } }, + * cleanup = { es -> es.deleteIndex("my-index") }, * configureExposedConfiguration = { cfg -> listOf(...) } * ) * } @@ -40,7 +40,7 @@ internal fun TestSystem.elasticsearch(): ElasticsearchSystem = * port = 9200, * password = "password", * runMigrations = true, - * cleanup = { client -> client.indices().delete { it.index("*") } }, + * cleanup = { es -> es.deleteIndex("my-index") }, * configureExposedConfiguration = { cfg -> listOf(...) } * ) * } 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 089653ea..750d6415 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 @@ -1,20 +1,15 @@ 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.system.abstractions.* import com.trendyol.stove.testing.e2e.system.annotations.StoveDsl -import org.apache.http.client.config.RequestConfig -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder -import org.elasticsearch.client.RestClient +import io.ktor.client.* 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) @@ -25,15 +20,25 @@ annotation class ElasticDsl */ @StoveDsl open class ElasticsearchSystemOptions( - open val clientConfigurer: ElasticClientConfigurer = ElasticClientConfigurer(), + /** + * Optional custom HTTP client configurer. + * If not provided, a default Ktor HTTP client will be created. + */ + open val httpClientConfigurer: Option<(cfg: ElasticSearchExposedConfiguration) -> HttpClient> = none(), open val container: ElasticContainerOptions = ElasticContainerOptions(), - open val jsonpMapper: JsonpMapper = JacksonJsonpMapper(StoveSerde.jackson.default), - open val cleanup: suspend (ElasticsearchClient) -> Unit = {}, + /** + * Jackson ObjectMapper for JSON serialization/deserialization. + */ + open val objectMapper: ObjectMapper = StoveSerde.jackson.default, + /** + * Cleanup function called when the system is closed. + */ + open val cleanup: suspend (ElasticsearchSystem) -> Unit = {}, override val configureExposedConfiguration: (ElasticSearchExposedConfiguration) -> List ) : SystemOptions, ConfiguresExposedConfiguration, - SupportsMigrations { - override val migrationCollection: MigrationCollection = MigrationCollection() + SupportsMigrations { + override val migrationCollection: MigrationCollection = MigrationCollection() companion object { /** @@ -43,9 +48,9 @@ open class ElasticsearchSystemOptions( * @param host The Elasticsearch host * @param port The Elasticsearch port * @param password The Elasticsearch password (for authentication) - * @param certificate Optional SSL certificate for secure connections - * @param clientConfigurer Client configuration - * @param jsonpMapper JSON mapper for serialization + * @param isSecure Whether to use HTTPS (default: false for provided instances) + * @param httpClientConfigurer Optional custom HTTP client configurer + * @param objectMapper Jackson ObjectMapper for serialization * @param runMigrations Whether to run migrations on the external instance (default: true) * @param cleanup A suspend function to clean up data after tests complete * @param configureExposedConfiguration Function to map exposed config to application properties @@ -55,21 +60,21 @@ open class ElasticsearchSystemOptions( host: String, port: Int, password: String = "", - certificate: ElasticsearchExposedCertificate? = null, - clientConfigurer: ElasticClientConfigurer = ElasticClientConfigurer(), - jsonpMapper: JsonpMapper = JacksonJsonpMapper(StoveSerde.jackson.default), + isSecure: Boolean = false, + httpClientConfigurer: Option<(cfg: ElasticSearchExposedConfiguration) -> HttpClient> = none(), + objectMapper: ObjectMapper = StoveSerde.jackson.default, runMigrations: Boolean = true, - cleanup: suspend (ElasticsearchClient) -> Unit = {}, + cleanup: suspend (ElasticsearchSystem) -> Unit = {}, configureExposedConfiguration: (ElasticSearchExposedConfiguration) -> List ): ProvidedElasticsearchSystemOptions = ProvidedElasticsearchSystemOptions( config = ElasticSearchExposedConfiguration( host = host, port = port, password = password, - certificate = certificate + isSecure = isSecure ), - clientConfigurer = clientConfigurer, - jsonpMapper = jsonpMapper, + httpClientConfigurer = httpClientConfigurer, + objectMapper = objectMapper, runMigrations = runMigrations, cleanup = cleanup, configureExposedConfiguration = configureExposedConfiguration @@ -87,18 +92,18 @@ class ProvidedElasticsearchSystemOptions( * The configuration for the provided Elasticsearch instance. */ val config: ElasticSearchExposedConfiguration, - clientConfigurer: ElasticClientConfigurer = ElasticClientConfigurer(), - jsonpMapper: JsonpMapper = JacksonJsonpMapper(StoveSerde.jackson.default), - cleanup: suspend (ElasticsearchClient) -> Unit = {}, + httpClientConfigurer: Option<(cfg: ElasticSearchExposedConfiguration) -> HttpClient> = none(), + objectMapper: ObjectMapper = StoveSerde.jackson.default, + cleanup: suspend (ElasticsearchSystem) -> Unit = {}, /** * Whether to run migrations on the external instance. */ val runMigrations: Boolean = true, configureExposedConfiguration: (ElasticSearchExposedConfiguration) -> List ) : ElasticsearchSystemOptions( - clientConfigurer = clientConfigurer, + httpClientConfigurer = httpClientConfigurer, container = ElasticContainerOptions(), - jsonpMapper = jsonpMapper, + objectMapper = objectMapper, cleanup = cleanup, configureExposedConfiguration = configureExposedConfiguration ), @@ -111,7 +116,10 @@ data class ElasticSearchExposedConfiguration( val host: String, val port: Int, val password: String, - val certificate: ElasticsearchExposedCertificate? + /** + * Whether to use HTTPS for the connection. + */ + val isSecure: Boolean = false ) : ExposedConfiguration @StoveDsl @@ -140,17 +148,3 @@ data class ElasticContainerOptions( const val DEFAULT_ELASTIC_PORT = 9200 } } - -data class ElasticClientConfigurer( - val httpClientBuilder: HttpAsyncClientBuilder.() -> Unit = { - setDefaultRequestConfig( - RequestConfig - .custom() - .setSocketTimeout(5.minutes.inWholeMilliseconds.toInt()) - .setConnectTimeout(5.minutes.inWholeMilliseconds.toInt()) - .setConnectionRequestTimeout(5.minutes.inWholeMilliseconds.toInt()) - .build() - ) - }, - val restClientOverrideFn: Option<(cfg: ElasticSearchExposedConfiguration) -> RestClient> = none() -) diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/util.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/util.kt deleted file mode 100644 index 6b9b54a0..00000000 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/util.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.stove.testing.e2e.elasticsearch - -import co.elastic.clients.elasticsearch._types.query_dsl.QueryVariant - -internal fun QueryVariant.asJsonString(): String = this._toQuery().toString().removePrefix("Query:") 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 deleted file mode 100644 index de5e4d92..00000000 --- a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.trendyol.stove.testing.e2e.elasticsearch - -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.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 = - """ - { - "state": { - "host": "localhost", - "port": 50543, - "password": "password", - "certificate": { - "bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SUJBZ0lWQU4wclloSXpaMS90Rmg5NmR3WGI1b1lWWnl4UU1BMEdDU3FHU0liM0RRRUIKQ3dVQU1Ed3hPakE0QmdOVkJBTVRNVVZzWVhOMGFXTnpaV0Z5WTJnZ2MyVmpkWEpwZEhrZ1lYVjBieTFqYjI1bQphV2QxY21GMGFXOXVJRWhVVkZBZ1EwRXdIaGNOTWpNd09ERTFNVGd5TWpJNFdoY05Nall3T0RFME1UZ3lNakk0CldqQThNVG93T0FZRFZRUURFekZGYkdGemRHbGpjMlZoY21Ob0lITmxZM1Z5YVhSNUlHRjFkRzh0WTI5dVptbG4KZFhKaGRHbHZiaUJJVkZSUUlFTkJNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQQppcDVGaWwySUMyRGhYbHkyV3RYTFliTnJUbHNkQklZQ3JvK0N1QU40djJHM3RuMkNQTmVoMnM2V0ovRUNrRitVCndJUVVKWEN0Mm43aEJDWkY4M1BlQ1JabWZyWkE0VXNtdzBYWS9OWWpTcnJRQXVtODYvamFZM0lMVGYzU1Jnei8KaWNFVEJCRVM1eTdmSFZlU0xmTjl1ME9hSC9tTnN5Q3FoMERMRFZrWXR5MHNJZXorb1paNmtxN2UrRE1OeHB5Sgp1cjRvUGJ5ekdmY1dnZDdnMll5T2RxNEd1TmFOck8ySjFVZG5BV3B5TVdnME5TSzd1TGlEZ0w5a25ZZlBnMmhQCisrWVpnVkJNNXJBMXJhY2x3c0U0NWJNVlErKzRyWEhhbDUwdS83VmN6a3M5QTREVDc3ZHVyOC9aM1hONWtpMm0KaWNTemJDTHlLdTZwdUhLQWhCdFMyWnlMMXBYN09RSVA2aWZLaVpaU1ZYUGZnMGk5cjVQL3ZFZTJoVXpTTDRVLwpsSWQvaWJUQWtIcmZGbEErN2FreFNzcFJoalMra1ZTQndyR05KQ3BDbWsraitxSnB5Sis5aTNWb0pkanVvemprClk2bS9EZG9kc21LdUZFYUdZNytBT1RVMjAwN0ZjZWdXUEJWejgzU051WmpwbURCczMzS25oeG5WM0RBb0QzUm4KbkNoV2ZQTGo4TUl1OG9tMll3RUpZMUtLR1hzUzU5TWtyclpuQnNjdzl0S0prMFQyRHlLM2dWckY5UnJpMk1mTwpKY3FCWUhBSHRQTHRQZU5STHJLUmxtYkh4NXJFMkNCWEJWQWJ1bU1EaGRIbE1lTWtwT1p3WnoyQWljWUV1anhlClU0TUl5LzczU1RHakhtcGpVT3dKcjNMdVdqVlBMNDlZeTZZWmNPbENsTThDQXdFQUFhTlRNRkV3SFFZRFZSME8KQkJZRUZNUTlobHVXN3VzdGQwZkZDNU5zSkNyTDhaTStNQjhHQTFVZEl3UVlNQmFBRk1ROWhsdVc3dXN0ZDBmRgpDNU5zSkNyTDhaTStNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBSEdOCjQ1Nm1iYXdKUHNLTVgvQlowanB2LytTbCtMTTB6U2gxeEF1YXlmbDk3WnBlbS80QkhGcU5vTUxGOEVqczhXcHoKUHU1Y3Y5VVFaZXNaWVVsNHE4ODY5TW03QnQ5UHVRcUJBR25VbTU3alhMRkRsdDRvVTFvZmVpalF1YkZ3M0wwMwpGa2NsQ3psZ2JhV21vb2ZKRTdKK2FEMGo5bHNOWllKem9tQlN6QnZGTC9uK0ptS0poQVk4SDNwTkNqdExtbXZjClZPbmluQWFScGxLQndSS1RRYm1ZVE53QXVTcEhvSUk4empqK3pGWm54MzVqSitJY0YwblQ1Q3FQT0tCcllxTmwKN21kTnU0OGs0eUpiY0JtYXNoa3BRdkQra2Q1RFJBWmZXZ2tjZzVZUk1RUnE3RnVpWkhxcmFVdWV2WmZ3dnB2UApqMmV5M0QwMG5aSUVIN3I0alVpVnl0SGNGejVQU29zRmIwZDlmWkRJYmJGanRQblpSTEVxbS8wd3N1V25VSVdRCnlSWTNvclNiMUZIYjdYQUlUdHlnZlZQZnlUV0lnemdtbjFCR3Z2eE5sYjIyVnB4TXcvaEpLWTU0WDRjc2s1RzkKbHZMNUVzT3BvYnZvWVJRNU9taHlJT1ZGSHUwcjRKZWkzcGJ3dTczWmlnLzNFanJLY0lRS0ttYzdhQUFkbGREeQpid0dRWDdvYzRLS1lra2JPNFNNQTRTZzUxQjJFZFEzVGYrSHJlUjFTcHN1TlB1U2p0aGY5MGY2eWYrU1d0NU04CnY2RmpVRy9sR0NGTndJTTd2N1o3SHhMVnIvbVg4MTRKVzBGREdLUmhHRHd3SDUzcTJYSmRaaEl5RlNaeWtuc1UKdmJLeW51Vm43czZrU1pYbnh2NnYyTTNsL09ZMjdpNHdUVnB6bzhXbgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" - } - }, - "processId": 10496 - } - """.trimIndent() - val j = StoveSerde.jackson.default - 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" - } - }) 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 d0adc52a..933ce8f1 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 @@ -1,9 +1,5 @@ package com.trendyol.stove.testing.e2e.elasticsearch -import arrow.core.Some -import co.elastic.clients.elasticsearch.ElasticsearchClient -import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders -import co.elastic.clients.elasticsearch.indices.CreateIndexRequest import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.trendyol.stove.testing.e2e.database.migrations.* import com.trendyol.stove.testing.e2e.system.TestSystem @@ -11,8 +7,6 @@ import com.trendyol.stove.testing.e2e.system.abstractions.ApplicationUnderTest import io.kotest.core.config.AbstractProjectConfig import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -import org.apache.http.HttpHost -import org.elasticsearch.client.RestClient import org.junit.jupiter.api.assertThrows import org.slf4j.* import org.testcontainers.elasticsearch.ElasticsearchContainer @@ -32,32 +26,22 @@ class NoOpApplication : ApplicationUnderTest { override suspend fun stop() = Unit } -class TestIndexMigrator : DatabaseMigration { +class TestIndexMigrator : DatabaseMigration { override val order: Int = MigrationPriority.HIGHEST.value private val logger: Logger = LoggerFactory.getLogger(javaClass) - override suspend fun execute(connection: ElasticsearchClient) { - val createIndexRequest: CreateIndexRequest = - CreateIndexRequest - .Builder() - .index(TEST_INDEX) - .build() - connection.indices().create(createIndexRequest) + override suspend fun execute(connection: ElasticsearchSystem) { + connection.createIndex(TEST_INDEX) logger.info("$TEST_INDEX is created") } } -class AnotherIndexMigrator : DatabaseMigration { +class AnotherIndexMigrator : DatabaseMigration { override val order: Int = MigrationPriority.HIGHEST.value + 1 private val logger: Logger = LoggerFactory.getLogger(javaClass) - override suspend fun execute(connection: ElasticsearchClient) { - val createIndexRequest: CreateIndexRequest = - CreateIndexRequest - .Builder() - .index(ANOTHER_INDEX) - .build() - connection.indices().create(createIndexRequest) + override suspend fun execute(connection: ElasticsearchSystem) { + connection.createIndex(ANOTHER_INDEX) logger.info("$ANOTHER_INDEX is created") } } @@ -95,10 +79,7 @@ class ContainerElasticsearchStrategy : ElasticsearchTestStrategy { logger.info("Starting Elasticsearch tests with container mode") val options = ElasticsearchSystemOptions( - clientConfigurer = ElasticClientConfigurer( - restClientOverrideFn = Some { cfg -> RestClient.builder(HttpHost(cfg.host, cfg.port)).build() } - ), - ElasticContainerOptions(tag = "8.9.0"), + container = ElasticContainerOptions(tag = "8.9.0"), configureExposedConfiguration = { _ -> listOf() } ).migrations { register() @@ -144,10 +125,7 @@ class ProvidedElasticsearchStrategy : ElasticsearchTestStrategy { host = hostPort[0], port = hostPort[1].toInt(), runMigrations = true, - clientConfigurer = ElasticClientConfigurer( - restClientOverrideFn = Some { cfg -> RestClient.builder(HttpHost(cfg.host, cfg.port)).build() } - ), - cleanup = { client -> + cleanup = { _ -> logger.info("Running cleanup on provided instance") // Clean up test data if needed }, @@ -223,23 +201,26 @@ class ElasticsearchTestSystemTests : 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() + val queryByDesc = """ + { + "query": { + "term": { + "description.keyword": "$desc" + } + } + } + """.trimIndent() + TestSystem.validate { elasticsearch { save(exampleInstance1.id, exampleInstance1, TEST_INDEX) save(exampleInstance2.id, exampleInstance2, TEST_INDEX) - shouldQuery(queryByDesc._toQuery()) { + shouldQuery(queryByDesc, TEST_INDEX) { it.size shouldBe 2 } shouldDelete(exampleInstance1.id, TEST_INDEX) shouldGet(key = exampleInstance2.id, index = TEST_INDEX) {} - shouldQuery(queryAsString, TEST_INDEX) { + shouldQuery(queryByDesc, TEST_INDEX) { it.size shouldBe 1 } }