You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2023/08/11 09:30:41 UTC

[camel-quarkus] branch main updated: Add elasticsearch JVM tests Fixes #5162

This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


The following commit(s) were added to refs/heads/main by this push:
     new 7424027587 Add elasticsearch JVM tests Fixes #5162
7424027587 is described below

commit 7424027587823db19ca3a277ad117f6cdafed102
Author: Lukas Lowinger <ll...@redhat.com>
AuthorDate: Wed Aug 9 14:52:39 2023 +0200

    Add elasticsearch JVM tests
    Fixes #5162
---
 integration-tests-jvm/elasticsearch/pom.xml        |  49 +++
 .../elasticsearch/it/ElasticsearchResource.java    | 298 +++++++++++++++++
 .../elasticsearch/it/ElasticsearchRoutes.java      |  59 ++++
 .../elasticsearch/it/ElasticsearchTest.java        | 365 +++++++++++++++++++++
 .../it/ElasticsearchTestResource.java              |  83 +++++
 pom.xml                                            |   1 +
 6 files changed, 855 insertions(+)

diff --git a/integration-tests-jvm/elasticsearch/pom.xml b/integration-tests-jvm/elasticsearch/pom.xml
index 5acd50c435..15bd3c7550 100644
--- a/integration-tests-jvm/elasticsearch/pom.xml
+++ b/integration-tests-jvm/elasticsearch/pom.xml
@@ -33,6 +33,10 @@
     <description>Integration tests for Camel Quarkus Elasticsearch extension</description>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-direct</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-elasticsearch</artifactId>
@@ -53,6 +57,27 @@
             <artifactId>rest-assured</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit4-mock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
@@ -65,6 +90,19 @@
             </activation>
             <dependencies>
                 <!-- The following dependencies guarantee that this module is built after them. You can update them by running `mvn process-resources -Pformat -N` from the source tree root directory -->
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-direct-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.camel.quarkus</groupId>
                     <artifactId>camel-quarkus-elasticsearch-deployment</artifactId>
@@ -80,5 +118,16 @@
                 </dependency>
             </dependencies>
         </profile>
+        <profile>
+            <id>skip-testcontainers-tests</id>
+            <activation>
+                <property>
+                    <name>skip-testcontainers-tests</name>
+                </property>
+            </activation>
+            <properties>
+                <skipTests>true</skipTests>
+            </properties>
+        </profile>
     </profiles>
 </project>
diff --git a/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchResource.java b/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchResource.java
new file mode 100644
index 0000000000..047266cd91
--- /dev/null
+++ b/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchResource.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.elasticsearch.it;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import co.elastic.clients.elasticsearch._types.query_dsl.MatchPhraseQuery;
+import co.elastic.clients.elasticsearch.core.BulkRequest;
+import co.elastic.clients.elasticsearch.core.GetResponse;
+import co.elastic.clients.elasticsearch.core.MsearchRequest;
+import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
+import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
+import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
+import co.elastic.clients.elasticsearch.core.msearch.MultiSearchResponseItem;
+import co.elastic.clients.elasticsearch.core.msearch.MultisearchBody;
+import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
+import co.elastic.clients.elasticsearch.core.msearch.RequestItem;
+import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
+import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PATCH;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.camel.FluentProducerTemplate;
+import org.apache.camel.component.es.ElasticsearchConstants;
+
+@Path("/elasticsearch")
+@ApplicationScoped
+public class ElasticsearchResource {
+
+    private static final String HEADER_COMPONENT = "component";
+
+    @Inject
+    FluentProducerTemplate fluentProducerTemplate;
+
+    @Path("/get")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response getData(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName,
+            @QueryParam("indexId") String indexId,
+            @QueryParam("indexKey") String indexKey) {
+
+        GetResponse<?> response = fluentProducerTemplate.to("direct:get")
+                .withBody(indexId)
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_NAME, indexName)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(GetResponse.class);
+
+        if (response.source() == null || !(response.source() instanceof ObjectNode)) {
+            return Response.status(404).build();
+        }
+
+        return Response.ok().entity(((ObjectNode) response.source()).get(indexKey).asText()).build();
+    }
+
+    @Path("/index")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response indexData(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName,
+            @QueryParam("indexKey") String indexKey,
+            String indexValue) throws Exception {
+
+        String indexId = fluentProducerTemplate.to("direct:index")
+                .withBody(createIndexedData(indexKey, indexValue))
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_NAME, indexName)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(String.class);
+
+        return Response
+                .created(new URI("https://camel.apache.org/"))
+                .entity(indexId)
+                .build();
+    }
+
+    @Path("/update")
+    @PATCH
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response updateData(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName,
+            @QueryParam("indexId") String indexId,
+            @QueryParam("indexKey") String indexKey,
+            String indexValue) {
+
+        fluentProducerTemplate.to("direct:update")
+                .withBody(Map.of("doc", createIndexedData(indexKey, indexValue)))
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_NAME, indexName)
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_ID, indexId)
+                .withHeader(HEADER_COMPONENT, component)
+                .request();
+
+        return Response.ok().build();
+    }
+
+    @Path("/delete")
+    @DELETE
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response deleteData(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName,
+            @QueryParam("indexId") String indexId) {
+
+        fluentProducerTemplate.to("direct:delete")
+                .withBody(indexId)
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_NAME, indexName)
+                .withHeader(HEADER_COMPONENT, component)
+                .request();
+
+        return Response.noContent().build();
+    }
+
+    @Path("/delete/index")
+    @DELETE
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response deleteIndexData(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName) {
+
+        DeleteIndexRequest.Builder request = new DeleteIndexRequest.Builder().index(indexName);
+
+        Boolean result = fluentProducerTemplate.to("direct:deleteIndex")
+                .withBody(request)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(Boolean.class);
+
+        return Response.ok(result).build();
+    }
+
+    @Path("/ping")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response ping(@QueryParam("component") String component) {
+        Boolean result = fluentProducerTemplate.to("direct:ping")
+                .withHeader(HEADER_COMPONENT, component)
+                .request(Boolean.class);
+
+        return Response.ok(result).build();
+    }
+
+    @Path("/exists")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response exists(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName) {
+
+        Boolean result = fluentProducerTemplate.to("direct:exists")
+                .withHeader(ElasticsearchConstants.PARAM_INDEX_NAME, indexName)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(Boolean.class);
+
+        return Response.ok(result).build();
+    }
+
+    @Path("/bulk")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response bulk(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName) {
+
+        BulkRequest.Builder request = new BulkRequest.Builder();
+        request.operations(new BulkOperation.Builder()
+                .index(new IndexOperation.Builder<>().index(indexName).document(Map.of("camel", "quarkus")).build()).build());
+
+        BulkResponseItem[] result = fluentProducerTemplate.to("direct:bulk")
+                .withBody(request)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(BulkResponseItem[].class);
+
+        return Response.ok(result[0].id()).build();
+    }
+
+    @Path("/search")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response searchByMap(
+            @QueryParam("component") String component,
+            @QueryParam("indexKey") String indexKey,
+            String searchString) {
+
+        Map<String, Object> actualQuery = new HashMap<>();
+        actualQuery.put(indexKey, searchString);
+
+        Map<String, Object> match = new HashMap<>();
+        match.put("match", actualQuery);
+
+        Map<String, Object> query = new HashMap<>();
+        query.put("query", match);
+
+        HitsMetadata<?> result = fluentProducerTemplate.to("direct:search")
+                .withBody(query)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(HitsMetadata.class);
+
+        if (result.hits().size() > 0) {
+            Object source = result.hits().get(0).source();
+            if (source instanceof ObjectNode) {
+                return Response.ok(((ObjectNode) source).get(indexKey).asText()).build();
+            }
+        }
+        // return OK as it is called in loop
+        return Response.ok().build();
+    }
+
+    @Path("/search/json")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response searchByJSON(
+            @QueryParam("component") String component,
+            @QueryParam("indexKey") String indexKey,
+            String searchString) {
+
+        String query = "{\"query\":{\"match\":{\"%s\":\"%s\"}}}";
+
+        HitsMetadata<?> result = fluentProducerTemplate.to("direct:search")
+                .withBody(String.format(query, indexKey, searchString))
+                .withHeader(HEADER_COMPONENT, component)
+                .request(HitsMetadata.class);
+
+        if (result.hits().size() > 0) {
+            Object source = result.hits().get(0).source();
+            if (source instanceof ObjectNode) {
+                return Response.ok(((ObjectNode) source).get(indexKey).asText()).build();
+            }
+        }
+        // return OK as it is called in loop
+        return Response.ok().build();
+    }
+
+    @Path("/search/multi")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Response searchMulti(
+            @QueryParam("component") String component,
+            @QueryParam("indexName") String indexName,
+            @QueryParam("indexKey") String indexKey,
+            String searchStrings) {
+
+        final String[] searchTerms = searchStrings.split(",");
+        MsearchRequest.Builder builder = new MsearchRequest.Builder().index(indexName);
+        for (String searchTerm : searchTerms) {
+            builder.searches(new RequestItem.Builder().header(new MultisearchHeader.Builder().build())
+                    .body(new MultisearchBody.Builder().query(q -> q
+                            .matchPhrase(new MatchPhraseQuery.Builder().field(indexKey).query(searchTerm).build()))
+                            .build())
+                    .build());
+        }
+
+        MultiSearchResponseItem[] result = fluentProducerTemplate.to("direct:multiSearch")
+                .withBody(builder)
+                .withHeader(HEADER_COMPONENT, component)
+                .request(MultiSearchResponseItem[].class);
+
+        if (result.length > 0) {
+            int totalHits = 0;
+            for (MultiSearchResponseItem item : result) {
+                totalHits += item.result().hits().total().value();
+            }
+            return Response.ok(totalHits).build();
+        }
+        return Response.ok().build();
+    }
+
+    private Map<String, String> createIndexedData(String indexKey, String indexValue) {
+        Map<String, String> map = new HashMap<>();
+        map.put(indexKey, indexValue);
+        return map;
+    }
+}
diff --git a/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchRoutes.java b/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchRoutes.java
new file mode 100644
index 0000000000..e9177e341c
--- /dev/null
+++ b/integration-tests-jvm/elasticsearch/src/main/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchRoutes.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.elasticsearch.it;
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class ElasticsearchRoutes extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        from("direct:bulk")
+                .toD("${header.component}://elasticsearch?operation=Bulk");
+
+        from("direct:delete")
+                .toD("${header.component}://elasticsearch?operation=Delete");
+
+        from("direct:deleteIndex")
+                .toD("${header.component}://elasticsearch?operation=DeleteIndex");
+
+        from("direct:exists")
+                .toD("${header.component}://elasticsearch?operation=Exists");
+
+        from("direct:get")
+                .toD("${header.component}://elasticsearch?operation=GetById");
+
+        from("direct:index")
+                .toD("${header.component}://elasticsearch?operation=Index");
+
+        from("direct:multiGet")
+                .toD("${header.component}://elasticsearch?operation=MultiGet");
+
+        from("direct:multiSearch")
+                .toD("${header.component}://elasticsearch?operation=MultiSearch");
+
+        from("direct:ping")
+                .toD("${header.component}://elasticsearch?operation=Ping");
+
+        from("direct:search")
+                .toD("${header.component}://elasticsearch?operation=Search");
+
+        from("direct:update")
+                .toD("${header.component}://elasticsearch?operation=Update");
+    }
+
+}
diff --git a/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTest.java b/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTest.java
new file mode 100644
index 0000000000..59559b8e88
--- /dev/null
+++ b/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTest.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.elasticsearch.it;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.Matchers.is;
+
+@QuarkusTest
+@QuarkusTestResource(ElasticsearchTestResource.class)
+class ElasticsearchTest {
+
+    @AfterEach
+    public void afterEach() {
+        // Clean up all indexed data
+        RestAssured.given()
+                .queryParam("component", "elasticsearch")
+                .queryParam("indexName", "_all")
+                .delete("/elasticsearch/delete/index")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchBasicOperations(String component) {
+        String indexName = UUID.randomUUID().toString();
+        String indexKey = "test-key";
+        String indexValue = "Hello Camel Quarkus ElasticSearch";
+
+        // Verify the ElasticSearch server is available
+        RestAssured.given()
+                .queryParam("component", component)
+                .get("/elasticsearch/ping")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Index data
+        String indexId = RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .contentType(ContentType.TEXT)
+                .body(indexValue)
+                .post("/elasticsearch/index")
+                .then()
+                .statusCode(201)
+                .extract()
+                .body()
+                .asString();
+
+        // Verify index exists
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Retrieve indexed data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexId", indexId)
+                .queryParam("indexKey", indexKey)
+                .get("/elasticsearch/get")
+                .then()
+                .statusCode(200)
+                .body(is(indexValue));
+
+        // Update indexed data
+        String updatedIndexValue = indexValue + " Updated";
+        RestAssured.given()
+                .contentType(ContentType.TEXT)
+                .queryParam("component", component)
+                .queryParam("indexId", indexId)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .body(updatedIndexValue)
+                .patch("/elasticsearch/update")
+                .then()
+                .statusCode(200);
+
+        // Verify updated data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexId", indexId)
+                .queryParam("indexKey", indexKey)
+                .get("/elasticsearch/get")
+                .then()
+                .statusCode(200)
+                .body(is(updatedIndexValue));
+
+        // Delete indexed data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexId", indexId)
+                .delete("/elasticsearch/delete")
+                .then()
+                .statusCode(204);
+
+        // Verify data deleted
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexId", indexId)
+                .queryParam("indexKey", indexKey)
+                .get("/elasticsearch/get")
+                .then()
+                .statusCode(404);
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchBulk(String component) {
+        String indexName = UUID.randomUUID().toString();
+
+        String indexId = RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .post("/elasticsearch/bulk")
+                .then()
+                .statusCode(200)
+                .extract()
+                .body()
+                .asString();
+
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexId", indexId)
+                .queryParam("indexKey", "camel")
+                .get("/elasticsearch/get")
+                .then()
+                .statusCode(200)
+                .body(is("quarkus"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchDeleteIndex(String component) {
+        String indexName = UUID.randomUUID().toString();
+        String indexKey = "test-key";
+        String indexValue = "Hello Camel Quarkus ElasticSearch";
+
+        // Index data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .contentType(ContentType.TEXT)
+                .body(indexValue)
+                .post("/elasticsearch/index")
+                .then()
+                .statusCode(201)
+                .extract()
+                .body()
+                .asString();
+
+        // Verify index exists
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Delete indexed data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .delete("/elasticsearch/delete/index")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchSearch(String component) {
+        String indexName = UUID.randomUUID().toString();
+        String indexKey = "camel-quarkus";
+        String indexValue = "Sub Atomic, Super Fast Camel Quarkus";
+
+        // Index data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .contentType(ContentType.TEXT)
+                .body(indexValue)
+                .post("/elasticsearch/index")
+                .then()
+                .statusCode(201)
+                .extract()
+                .body()
+                .asString();
+
+        // Verify index exists
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Search data
+        Awaitility.await().pollInterval(50, TimeUnit.MILLISECONDS).atMost(10, TimeUnit.SECONDS).until(() -> {
+            String searchResult = RestAssured.given()
+                    .queryParam("component", component)
+                    .queryParam("indexKey", indexKey)
+                    .body("Super Fast")
+                    .get("/elasticsearch/search")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString();
+            return searchResult.equals(indexValue);
+        });
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchSearchJSON(String component) {
+        String indexName = UUID.randomUUID().toString();
+        String indexKey = "camel-quarkus";
+        String indexValue = "Sub Atomic, Super Fast Camel Quarkus";
+
+        // Index data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .contentType(ContentType.TEXT)
+                .body(indexValue)
+                .post("/elasticsearch/index")
+                .then()
+                .statusCode(201)
+                .extract()
+                .body()
+                .asString();
+
+        // Verify index exists
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Search data
+        Awaitility.await().pollInterval(50, TimeUnit.MILLISECONDS).atMost(10, TimeUnit.SECONDS).until(() -> {
+            String searchResult = RestAssured.given()
+                    .queryParam("component", component)
+                    .queryParam("indexKey", indexKey)
+                    .body("Super Fast")
+                    .get("/elasticsearch/search/json")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString();
+            return searchResult.equals(indexValue);
+        });
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void testElasticsearchMultiSearch(String component) {
+        String indexName = UUID.randomUUID().toString();
+        String indexKey = "camel-quarkus";
+        String indexValue = "Sub Atomic, Super Fast Camel Quarkus";
+
+        // Index data
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .queryParam("indexKey", indexKey)
+                .contentType(ContentType.TEXT)
+                .body(indexValue)
+                .post("/elasticsearch/index")
+                .then()
+                .statusCode(201)
+                .extract()
+                .body()
+                .asString();
+
+        // Verify index exists
+        RestAssured.given()
+                .queryParam("component", component)
+                .queryParam("indexName", indexName)
+                .get("/elasticsearch/exists")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+
+        // Search data
+        Awaitility.await().pollInterval(50, TimeUnit.MILLISECONDS).atMost(100, TimeUnit.SECONDS).until(() -> {
+            String hits = RestAssured.given()
+                    .queryParam("component", component)
+                    .queryParam("indexName", indexName)
+                    .queryParam("indexKey", indexKey)
+                    .body("Sub Atomic,Super Fast,Nonsense")
+                    .get("/elasticsearch/search/multi")
+                    .then()
+                    .statusCode(200)
+                    .extract()
+                    .body()
+                    .asString();
+            return hits.equals("2");
+        });
+    }
+
+    /**
+     * This method returns array of component names used in test routes.
+     * It can be handy e.g. for testing quarkus managed elasticsearch client.
+     *
+     * @return Component name used in route.
+     */
+    @SuppressWarnings("unused")
+    private static String[] componentNames() {
+        return new String[] { "elasticsearch" };
+    }
+}
diff --git a/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTestResource.java b/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTestResource.java
new file mode 100644
index 0000000000..b449938b1a
--- /dev/null
+++ b/integration-tests-jvm/elasticsearch/src/test/java/org/apache/camel/quarkus/component/elasticsearch/it/ElasticsearchTestResource.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.elasticsearch.it;
+
+import java.util.Map;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.util.CollectionHelper;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.TestcontainersConfiguration;
+
+public class ElasticsearchTestResource implements QuarkusTestResourceLifecycleManager {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTestResource.class);
+    private static final String ELASTICSEARCH_IMAGE = ConfigProvider.getConfig().getValue("elasticsearch.container.image",
+            String.class);
+    private static final String ELASTICSEARCH_USERNAME = "elastic";
+    private static final String ELASTICSEARCH_PASSWORD = "changeme";
+    private static final int ELASTICSEARCH_PORT = 9200;
+
+    private GenericContainer<?> container;
+
+    @Override
+    public Map<String, String> start() {
+        LOGGER.info(TestcontainersConfiguration.getInstance().toString());
+
+        try {
+            container = new GenericContainer<>(ELASTICSEARCH_IMAGE)
+                    .withExposedPorts(ELASTICSEARCH_PORT)
+                    .withLogConsumer(new Slf4jLogConsumer(LOGGER))
+                    .withEnv("discovery.type", "single-node")
+                    .withEnv("xpack.security.enabled", "true")
+                    .withEnv("action.destructive_requires_name", "false") // needed for deleting all indexes after each test (allowing _all wildcard)
+                    .withEnv("ELASTIC_USERNAME", ELASTICSEARCH_USERNAME)
+                    .withEnv("ELASTIC_PASSWORD", ELASTICSEARCH_PASSWORD)
+                    .waitingFor(Wait.forListeningPort());
+
+            container.start();
+
+            String hostAddresses = String.format("localhost:%s", container.getMappedPort(ELASTICSEARCH_PORT));
+
+            // Component configuration where the ElasticSearch client is managed by Camel (E.g autowiring disabled)
+            return CollectionHelper.mapOf(
+                    // camel
+                    "camel.component.elasticsearch.host-addresses", hostAddresses,
+                    "camel.component.elasticsearch.user", ELASTICSEARCH_USERNAME,
+                    "camel.component.elasticsearch.password", ELASTICSEARCH_PASSWORD);
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void stop() {
+        try {
+            if (container != null) {
+                container.stop();
+            }
+        } catch (Exception e) {
+            // Ignored
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index a83faf051f..3e2919b7d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,6 +217,7 @@
         <couchdb.container.image>docker.io/couchdb:2.3.1</couchdb.container.image>
         <derby.container.image>docker.io/az82/docker-derby:10.16</derby.container.image>
         <eclipse-mosquitto.container.image>docker.io/eclipse-mosquitto:1.6.15</eclipse-mosquitto.container.image>
+        <elasticsearch.container.image>docker.io/elasticsearch:8.8.1</elasticsearch.container.image>
         <fhir.container.image.base>docker.io/hapiproject/hapi</fhir.container.image.base>
         <fhir.container.image>${fhir.container.image.base}:v6.6.0</fhir.container.image>
         <fhir-dstu.container.image>${fhir.container.image.base}:v4.2.0</fhir-dstu.container.image>