diff --git a/docs/src/main/asciidoc/elasticsearch.adoc b/docs/src/main/asciidoc/elasticsearch.adoc index 5013776a04cda6..468ba4c700c20f 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 f1157a7dedf731..336ab924998070 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..25641503bdb139 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 @@ -6,13 +6,14 @@ import java.util.UUID; import jakarta.inject.Inject; -import jakarta.ws.rs.BadRequestException; 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 { @@ -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..541c80ebc15f85 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 @@ -9,10 +9,17 @@ 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 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 { @@ -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..4e2044f825819b 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,46 +23,36 @@ 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) - .when().post("/fruits") - .then() - .statusCode(201); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(fruit) + .when().post("/fruits") + .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); - // wait a few ms for the indexing to happened - Thread.sleep(1000); + assertThat(result).isNotNull().isEqualTo(fruit); // 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" + + "{\n" + "\"query\": {\n" + " \"prefix\": {\n" + " \"name\": {\n" + @@ -72,11 +62,38 @@ 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); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(fruits) + .when().post("/fruits/bulk") + .then() + .statusCode(200); + }); + result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNotNull().isEqualTo(pomegranate); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(List.of(pomegranate.id)) + .when().delete("/fruits/bulk") + .then() + .statusCode(200); + }); + 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 4881a82eb3505e..ea12fad7acb18d 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..b12b2d4b9af9c4 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,22 @@ 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..ac0c35babd281b 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 @@ -6,13 +6,14 @@ import java.util.UUID; import jakarta.inject.Inject; -import jakarta.ws.rs.BadRequestException; 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 { @@ -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 b0413434b2600f..7a483236fccff3 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,12 +2,14 @@ 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 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; @@ -20,42 +22,63 @@ 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) - .when().post("/fruits") - .then() - .statusCode(201); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(fruit) + .when().post("/fruits") + .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); - // wait a few ms for the indexing to happened - Thread.sleep(1000); + assertThat(result).isNotNull().isEqualTo(fruit); // 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); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(fruits) + .when().post("/fruits/bulk") + .then() + .statusCode(200); + }); + result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNotNull().isEqualTo(pomegranate); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { + given() + .contentType("application/json") + .body(List.of(pomegranate.id)) + .when().delete("/fruits/bulk") + .then() + .statusCode(200); + }); + result = get("/fruits/2").as(Fruit.class); + assertThat(result).isNull(); } @Test