From cb2cc54a693b5dc3eb152b2ce6bccb6d303b6d1b Mon Sep 17 00:00:00 2001 From: Akulov S V Date: Thu, 8 Aug 2024 23:30:58 +0300 Subject: [PATCH] Elasticsearch docs update Added code snippet about Bulk API usage via low level RestClient expanded Bulk API usage description rewritten low level Rest Client example added Java API example added awaitility added test cases for bulk operations --- docs/src/main/asciidoc/elasticsearch.adoc | 157 ++++++++++++++++-- .../elasticsearch-java-client/pom.xml | 10 ++ .../it/elasticsearch/java/FruitResource.java | 15 ++ .../it/elasticsearch/java/FruitService.java | 44 ++++- .../it/elasticsearch/FruitResourceTest.java | 74 ++++++--- .../elasticsearch-rest-client/pom.xml | 18 ++ .../io/quarkus/it/elasticsearch/Fruit.java | 17 ++ .../it/elasticsearch/FruitResource.java | 15 ++ .../it/elasticsearch/FruitService.java | 45 +++++ .../it/elasticsearch/FruitResourceTest.java | 67 +++++--- 10 files changed, 395 insertions(+), 67 deletions(-) diff --git a/docs/src/main/asciidoc/elasticsearch.adoc b/docs/src/main/asciidoc/elasticsearch.adoc index 2fb01d9eba966d..feffac5f93b66d 100644 --- a/docs/src/main/asciidoc/elasticsearch.adoc +++ b/docs/src/main/asciidoc/elasticsearch.adoc @@ -126,10 +126,13 @@ package org.acme.elasticsearch; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; @@ -151,13 +154,49 @@ public class FruitService { restClient.performRequest(request); //<4> } + public void index(List list) throws IOException { + + var entityList = new ArrayList(); + + for (var fruit : list) { + + entityList.add(new JsonObject().put("index", new JsonObject()//<5> + .put("_index", "fruits").put("_id", fruit.id))); + entityList.add(JsonObject.mapFrom(fruit)); + } + + Request request = new Request( + "POST", "fruits/_bulk?pretty"); + request.setEntity(new StringEntity( + toNdJsonString(entityList),//<6> + ContentType.create("application/x-ndjson")));//<7> + restClient.performRequest(request); + } + + public void delete(List identityList) throws IOException { + + var entityList = new ArrayList(); + + for (var id : identityList) { + entityList.add(new JsonObject().put("delete", + new JsonObject().put("_index", "fruits").put("_id", id)));//<8> + } + + Request request = new Request( + "POST", "fruits/_bulk?pretty"); + request.setEntity(new StringEntity( + toNdJsonString(entityList), + ContentType.create("application/x-ndjson"))); + restClient.performRequest(request); + } + public Fruit get(String id) throws IOException { Request request = new Request( "GET", "/fruits/_doc/" + id); Response response = restClient.performRequest(request); String responseBody = EntityUtils.toString(response.getEntity()); - JsonObject json = new JsonObject(responseBody); //<5> + JsonObject json = new JsonObject(responseBody); //<9> return json.getJsonObject("_source").mapTo(Fruit.class); } @@ -191,13 +230,42 @@ public class FruitService { } return results; } + + private static String toNdJsonString(List objects) { + return objects.stream() + .map(JsonObject::encode) + .collect(Collectors.joining("\n", "", "\n")); + } } ---- <1> We inject an Elasticsearch low level `RestClient` into our service. <2> We create an Elasticsearch request. <3> We use Vert.x `JsonObject` to serialize the object before sending it to Elasticsearch, you can use whatever you want to serialize your objects to JSON. <4> We send the request (indexing request here) to Elasticsearch. -<5> In order to deserialize the object from Elasticsearch, we again use Vert.x `JsonObject`. +<5> As we `index` collection of objects we should use `index`, `create` or `update` action. +<6> We use `toNdJsonString(entityList)` call to produce output like below ++ +[source, json] +---- +{"index", {"_index" : "fruits", "_id", "1"}} +{"id": "1", "name": "apple", "color": "red"} +... ... ... ... +{"create", {"_index" : "fruits", "_id", "N"}} +{"id": "N", "name": "dragonfruit", "color": "pink"} + +---- +<7> Pass the content type that is expected by the search backend for bulk requests. +<8> The bulk API's delete operation JSON already contains all the required information; hence, there is no request body following this operation in the Bulk API request body. ++ +[source, json] +---- +{"delete", {"_index" : "fruits", "_id", "1"}} +{"delete", {"_index" : "fruits", "_id", "2"}} +... ... ... ... +{"delete", {"_index" : "fruits", "_id", "N"}} + +---- +<9> In order to deserialize the object from Elasticsearch, we again use Vert.x JsonObject. Now, create the `org.acme.elasticsearch.FruitResource` class as follows: @@ -205,18 +273,20 @@ Now, create the `org.acme.elasticsearch.FruitResource` class as follows: ---- package org.acme.elasticsearch; -import jakarta.inject.Inject; -import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.UUID; -import org.jboss.resteasy.reactive.RestQuery; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.BadRequestException; @Path("/fruits") public class FruitResource { @@ -233,6 +303,20 @@ public class FruitResource { return Response.created(URI.create("/fruits/" + fruit.id)).build(); } + @Path("bulk") + @DELETE + public Response delete(List identityList) throws IOException { + fruitService.delete(identityList); + return Response.ok().build(); + } + + @Path("bulk") + @POST + public Response index(List list) throws IOException { + fruitService.index(list); + return Response.ok().build(); + } + @GET @Path("/{id}") public Fruit get(String id) throws IOException { @@ -384,18 +468,26 @@ Here is a version of the `FruitService` using the Elasticsearch Java Client inst [source,java] ---- +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.IndexRequest; import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; -import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.HitsMetadata; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.acme.elasticsearch.Fruit; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; +import co.elastic.clients.elasticsearch.core.GetRequest; +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.BulkResponse; @ApplicationScoped public class FruitService { @@ -410,6 +502,37 @@ public class FruitService { client.index(request); // <4> } + public void index(List list) throws IOException { + + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (var fruit : list) { + br.operations(op -> op + .index(idx -> idx.index("fruits").id(fruit.id).document(fruit))); + } + + BulkResponse result = client.bulk(br.build()); + + if (result.errors()) { + throw new RuntimeException("The indexing operation encountered errors."); + } + } + + public void delete(List list) throws IOException { + + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (var id : list) { + br.operations(op -> op.delete(idx -> idx.index("fruits").id(id))); + } + + BulkResponse result = client.bulk(br.build()); + + if (result.errors()) { + throw new RuntimeException("The indexing operation encountered errors."); + } + } + public Fruit get(String id) throws IOException { GetRequest getRequest = GetRequest.of( b -> b.index("fruits") diff --git a/integration-tests/elasticsearch-java-client/pom.xml b/integration-tests/elasticsearch-java-client/pom.xml index c370065d77d146..9b998438c96876 100644 --- a/integration-tests/elasticsearch-java-client/pom.xml +++ b/integration-tests/elasticsearch-java-client/pom.xml @@ -37,6 +37,16 @@ rest-assured test + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + diff --git a/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitResource.java b/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitResource.java index 4d27587777d059..cd7f7f14976a66 100644 --- a/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitResource.java +++ b/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitResource.java @@ -7,6 +7,7 @@ import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -54,4 +55,18 @@ public List searchUnsafe(@QueryParam("json") String json) throws IOExcept return fruitService.searchWithJson(json); } + @Path("bulk") + @DELETE + public Response delete(List identityList) throws IOException { + fruitService.delete(identityList); + return Response.ok().build(); + } + + @Path("bulk") + @POST + public Response index(List list) throws IOException { + fruitService.index(list); + return Response.ok().build(); + } + } diff --git a/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitService.java b/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitService.java index 2c9427e982ecee..b4eea5603a577e 100644 --- a/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitService.java +++ b/integration-tests/elasticsearch-java-client/src/main/java/io/quarkus/it/elasticsearch/java/FruitService.java @@ -11,7 +11,14 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; -import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.BulkResponse; +import co.elastic.clients.elasticsearch.core.GetRequest; +import co.elastic.clients.elasticsearch.core.GetResponse; +import co.elastic.clients.elasticsearch.core.IndexRequest; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.HitsMetadata; @ApplicationScoped @@ -53,7 +60,7 @@ private List search(String term, String match) throws IOException { SearchResponse searchResponse = client.search(searchRequest, Fruit.class); HitsMetadata hits = searchResponse.hits(); - return hits.hits().stream().map(hit -> hit.source()).collect(Collectors.toList()); + return hits.hits().stream().map(Hit::source).collect(Collectors.toList()); } public List searchWithJson(String json) throws IOException { @@ -63,6 +70,37 @@ public List searchWithJson(String json) throws IOException { } SearchResponse searchResponse = client.search(searchRequest, Fruit.class); HitsMetadata hits = searchResponse.hits(); - return hits.hits().stream().map(hit -> hit.source()).collect(Collectors.toList()); + return hits.hits().stream().map(Hit::source).collect(Collectors.toList()); + } + + public void index(List list) throws IOException { + + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (var fruit : list) { + br.operations(op -> op + .index(idx -> idx.index("fruits").id(fruit.id).document(fruit))); + } + + BulkResponse result = client.bulk(br.build()); + + if (result.errors()) { + throw new RuntimeException("The indexing operation encountered errors."); + } + } + + public void delete(List list) throws IOException { + + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (var id : list) { + br.operations(op -> op.delete(idx -> idx.index("fruits").id(id))); + } + + BulkResponse result = client.bulk(br.build()); + + if (result.errors()) { + throw new RuntimeException("The indexing operation encountered errors."); + } } } diff --git a/integration-tests/elasticsearch-java-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java b/integration-tests/elasticsearch-java-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java index e4e0f80ff52178..ed07d76b4a3b78 100644 --- a/integration-tests/elasticsearch-java-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java +++ b/integration-tests/elasticsearch-java-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java @@ -2,13 +2,13 @@ import static io.restassured.RestAssured.get; import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -23,12 +23,13 @@ public class FruitResourceTest { }; @Test - public void testEndpoint() throws InterruptedException { + public void testEndpoint() { // create a Fruit Fruit fruit = new Fruit(); fruit.id = "1"; fruit.name = "Apple"; fruit.color = "Green"; + given() .contentType("application/json") .body(fruit) @@ -36,30 +37,19 @@ public void testEndpoint() throws InterruptedException { .then() .statusCode(201); - // get the Fruit - Fruit result = get("/fruits/1").as(Fruit.class); - assertNotNull(result); - assertEquals("1", result.id); - assertEquals("Apple", result.name); - assertEquals("Green", result.color); + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + // get the Fruit + Fruit result = get("/fruits/1").as(Fruit.class); + assertThat(result).isNotNull().isEqualTo(fruit); - // wait a few ms for the indexing to happened - Thread.sleep(1000); + }); // search the Fruit List results = get("/fruits/search?color=Green").as(LIST_OF_FRUIT_TYPE_REF); - assertNotNull(results); - assertFalse(results.isEmpty()); - assertEquals("1", results.get(0).id); - assertEquals("Apple", results.get(0).name); - assertEquals("Green", results.get(0).color); + assertThat(results).hasSize(1).contains(fruit); results = get("/fruits/search?name=Apple").as(LIST_OF_FRUIT_TYPE_REF); - assertNotNull(results); - assertFalse(results.isEmpty()); - assertEquals("1", results.get(0).id); - assertEquals("Apple", results.get(0).name); - assertEquals("Green", results.get(0).color); + assertThat(results).hasSize(1).contains(fruit); results = RestAssured.given().queryParam("json", "{\n" + @@ -72,11 +62,41 @@ public void testEndpoint() throws InterruptedException { "}\n" + "}\n") .get("/fruits/search/unsafe").as(LIST_OF_FRUIT_TYPE_REF); - assertNotNull(results); - assertFalse(results.isEmpty()); - assertEquals("1", results.get(0).id); - assertEquals("Apple", results.get(0).name); - assertEquals("Green", results.get(0).color); + assertThat(results).hasSize(1).contains(fruit); + + //create new fruit index via bulk operation + Fruit pomegranate = new Fruit(); + pomegranate.id = "2"; + pomegranate.name = "Pomegranate"; + pomegranate.color = "Red"; + + List fruits = List.of(pomegranate); + + given() + .contentType("application/json") + .body(fruits) + .when().post("/fruits/bulk") + .then() + .statusCode(200); + + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + Fruit result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNotNull().isEqualTo(pomegranate); + + }); + + given() + .contentType("application/json") + .body(List.of(pomegranate.id)) + .when().delete("/fruits/bulk") + .then() + .statusCode(200); + + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + Fruit result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNull(); + }); + } @Test diff --git a/integration-tests/elasticsearch-rest-client/pom.xml b/integration-tests/elasticsearch-rest-client/pom.xml index 64720ca0c3079e..fe8f1f3d764a36 100644 --- a/integration-tests/elasticsearch-rest-client/pom.xml +++ b/integration-tests/elasticsearch-rest-client/pom.xml @@ -37,6 +37,16 @@ rest-assured test + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + @@ -112,6 +122,14 @@ true + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + diff --git a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/Fruit.java b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/Fruit.java index ff28481a534bf6..16909719331515 100644 --- a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/Fruit.java +++ b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/Fruit.java @@ -1,7 +1,24 @@ package io.quarkus.it.elasticsearch; +import java.util.Objects; + public class Fruit { public String id; public String name; public String color; + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Fruit)) + return false; + Fruit fruit = (Fruit) o; + return Objects.equals(id, fruit.id) && Objects.equals(name, fruit.name) && Objects.equals(color, fruit.color); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, color); + } } diff --git a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitResource.java b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitResource.java index 4cec914a9ae378..1115271e20a43b 100644 --- a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitResource.java +++ b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitResource.java @@ -7,6 +7,7 @@ import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -46,4 +47,18 @@ public List search(@QueryParam("name") String name, @QueryParam("color") } } + @Path("bulk") + @DELETE + public Response delete(List identityList) throws IOException { + fruitService.delete(identityList); + return Response.ok().build(); + } + + @Path("bulk") + @POST + public Response index(List list) throws IOException { + fruitService.index(list); + return Response.ok().build(); + } + } diff --git a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitService.java b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitService.java index aba58dad86b04f..7ce74da36334a6 100644 --- a/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitService.java +++ b/integration-tests/elasticsearch-rest-client/src/main/java/io/quarkus/it/elasticsearch/FruitService.java @@ -3,10 +3,13 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; @@ -68,4 +71,46 @@ private List search(String term, String match) throws IOException { } return results; } + + public void index(List list) throws IOException { + + var entityList = new ArrayList(); + + for (var fruit : list) { + + entityList.add(new JsonObject().put("index", new JsonObject() + .put("_index", "fruits").put("_id", fruit.id))); + entityList.add(JsonObject.mapFrom(fruit)); + } + + Request request = new Request( + "POST", "fruits/_bulk?pretty"); + request.setEntity(new StringEntity( + toNdJsonString(entityList), + ContentType.create("application/x-ndjson"))); + restClient.performRequest(request); + } + + public void delete(List identityList) throws IOException { + + var entityList = new ArrayList(); + + for (var id : identityList) { + entityList.add(new JsonObject().put("delete", + new JsonObject().put("_index", "fruits").put("_id", id))); + } + + Request request = new Request( + "POST", "fruits/_bulk?pretty"); + request.setEntity(new StringEntity( + toNdJsonString(entityList), + ContentType.create("application/x-ndjson"))); + restClient.performRequest(request); + } + + private static String toNdJsonString(List objects) { + return objects.stream() + .map(JsonObject::encode) + .collect(Collectors.joining("\n", "", "\n")); + } } diff --git a/integration-tests/elasticsearch-rest-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java b/integration-tests/elasticsearch-rest-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java index e4745164ae8007..a24e4e2a899339 100644 --- a/integration-tests/elasticsearch-rest-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java +++ b/integration-tests/elasticsearch-rest-client/src/test/java/io/quarkus/it/elasticsearch/FruitResourceTest.java @@ -2,10 +2,12 @@ import static io.restassured.RestAssured.get; import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import java.util.List; +import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -17,12 +19,13 @@ public class FruitResourceTest { }; @Test - public void testEndpoint() throws InterruptedException { + public void testEndpoint() { // create a Fruit Fruit fruit = new Fruit(); fruit.id = "1"; fruit.name = "Apple"; fruit.color = "Green"; + given() .contentType("application/json") .body(fruit) @@ -30,28 +33,52 @@ public void testEndpoint() throws InterruptedException { .then() .statusCode(201); - // get the Fruit - Fruit result = get("/fruits/1").as(Fruit.class); - Assertions.assertNotNull(result); - Assertions.assertEquals("1", result.id); - Assertions.assertEquals("Apple", result.name); - Assertions.assertEquals("Green", result.color); + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + // get the Fruit + Fruit result = get("/fruits/1").as(Fruit.class); + + assertThat(result).isNotNull().isEqualTo(fruit); - // wait a few ms for the indexing to happened - Thread.sleep(1000); + }); // search the Fruit List results = get("/fruits/search?color=Green").as(LIST_OF_FRUIT_TYPE_REF); - Assertions.assertNotNull(results); - Assertions.assertFalse(results.isEmpty()); - Assertions.assertEquals("1", results.get(0).id); - Assertions.assertEquals("Apple", results.get(0).name); - Assertions.assertEquals("Green", results.get(0).color); + assertThat(results).hasSize(1).contains(fruit); + results = get("/fruits/search?name=Apple").as(LIST_OF_FRUIT_TYPE_REF); - Assertions.assertNotNull(results); - Assertions.assertFalse(results.isEmpty()); - Assertions.assertEquals("1", results.get(0).id); - Assertions.assertEquals("Apple", results.get(0).name); - Assertions.assertEquals("Green", results.get(0).color); + assertThat(results).hasSize(1).contains(fruit); + + //create new fruit index via bulk operation + Fruit pomegranate = new Fruit(); + pomegranate.id = "2"; + pomegranate.name = "Pomegranate"; + pomegranate.color = "Red"; + + List fruits = List.of(pomegranate); + + given() + .contentType("application/json") + .body(fruits) + .when().post("/fruits/bulk") + .then() + .statusCode(200); + + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + Fruit result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNotNull().isEqualTo(pomegranate); + }); + + given() + .contentType("application/json") + .body(List.of(pomegranate.id)) + .when().delete("/fruits/bulk") + .then() + .statusCode(200); + + await().atMost(2, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).untilAsserted(() -> { + Fruit result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNull(); + }); + } }