From ec0e85d788b000480a5a62fed3ab7fa731ba2b66 Mon Sep 17 00:00:00 2001 From: Ba Thien Le Date: Wed, 21 Aug 2024 16:47:40 +0200 Subject: [PATCH] refactor(metadata-search): mock elasticsearch for metadata search tests (Cytomine-ULiege/Cytomine-core-spring#31) closes Cytomine-ULiege/Cytomine-core-spring#30 --- .../search/MetadataSearchServiceTests.java | 149 ++++++------------ .../be/cytomine/utils/WireMockHelper.java | 84 ++++++++++ 2 files changed, 128 insertions(+), 105 deletions(-) create mode 100644 src/test/java/be/cytomine/utils/WireMockHelper.java diff --git a/src/test/java/be/cytomine/service/search/MetadataSearchServiceTests.java b/src/test/java/be/cytomine/service/search/MetadataSearchServiceTests.java index f14fcd68..a8933713 100644 --- a/src/test/java/be/cytomine/service/search/MetadataSearchServiceTests.java +++ b/src/test/java/be/cytomine/service/search/MetadataSearchServiceTests.java @@ -15,23 +15,24 @@ * limitations under the License. */ -import be.cytomine.BasicInstanceBuilder; -import be.cytomine.CytomineCoreApplication; -import be.cytomine.domain.image.AbstractImage; -import be.cytomine.domain.meta.Property; -import be.cytomine.utils.JsonObject; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import javax.transaction.Transactional; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import javax.transaction.Transactional; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; +import be.cytomine.BasicInstanceBuilder; +import be.cytomine.CytomineCoreApplication; +import be.cytomine.domain.image.AbstractImage; +import be.cytomine.utils.JsonObject; +import be.cytomine.utils.WireMockHelper; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @@ -39,43 +40,39 @@ @Transactional public class MetadataSearchServiceTests { - private final IndexCoordinates index = IndexCoordinates.of("properties"); - @Autowired BasicInstanceBuilder builder; - @Autowired - ElasticsearchOperations operations; - @Autowired MetadataSearchService metadataSearchService; + private WireMockServer wireMockServer; + + private WireMockHelper wireMockHelper; + @BeforeEach public void setup() { - operations.indexOps(index).create(); + wireMockServer = new WireMockServer(9200); // Use port 9200 as it is the default Elasticsearch port + wireMockServer.start(); + wireMockHelper = new WireMockHelper(wireMockServer); + + WireMock.configureFor("localhost", wireMockServer.port()); } @AfterEach public void clean() { - operations.indexOps(index).delete(); + wireMockServer.stop(); } @Test void list_all_images_by_string_filters() { AbstractImage ai1 = builder.given_an_abstract_image(); AbstractImage ai2 = builder.given_an_abstract_image(); - Property p1 = builder.given_a_property(ai1, "key1", "value"); - Property p2 = builder.given_a_property(ai1, "key2", "2000"); - Property p3 = builder.given_a_property(ai2, "key3", "7000"); - - for (Property p : Arrays.asList(p1, p2, p3)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); HashMap query = new HashMap<>(); - query.put("key1", "val"); + query.put("test-key", "test-value"); + + wireMockHelper.stubElasticSearchApi(ai1.getId(), query.size()); HashMap parameters = new HashMap<>(); parameters.put("imageIds", Arrays.asList(ai1.getId().intValue(), ai2.getId().intValue())); @@ -93,17 +90,11 @@ void list_all_images_by_string_filters() { void list_all_images_by_number_filters() { AbstractImage ai1 = builder.given_an_abstract_image(); AbstractImage ai2 = builder.given_an_abstract_image(); - Property p1 = builder.given_a_property(ai1, "key1", "value"); - Property p2 = builder.given_a_property(ai1, "key2", "2000"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); HashMap query = new HashMap<>(); - query.put("key2", Arrays.asList(1000, 2000)); + query.put("test-key", Arrays.asList(0, 2000)); + + wireMockHelper.stubElasticSearchApi(ai1.getId(), query.size()); HashMap parameters = new HashMap<>(); parameters.put("imageIds", Arrays.asList(ai1.getId().intValue(), ai2.getId().intValue())); @@ -121,18 +112,12 @@ void list_all_images_by_number_filters() { void list_all_images_by_mixed_filters() { AbstractImage ai1 = builder.given_an_abstract_image(); AbstractImage ai2 = builder.given_an_abstract_image(); - Property p1 = builder.given_a_property(ai1, "key1", "value"); - Property p2 = builder.given_a_property(ai1, "key2", "2000"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); HashMap query = new HashMap<>(); query.put("key1", "val"); - query.put("key2", Arrays.asList(1000, 2000)); + query.put("test-key", Arrays.asList(0, 2000)); + + wireMockHelper.stubElasticSearchApi(ai1.getId(), query.size()); HashMap parameters = new HashMap<>(); parameters.put("imageIds", Arrays.asList(ai1.getId().intValue(), ai2.getId().intValue())); @@ -148,18 +133,9 @@ void list_all_images_by_mixed_filters() { @Test void list_no_suggestions_for_wrong_value() { - AbstractImage ai = builder.given_an_abstract_image(); - String key = "key"; - Property p1 = builder.given_a_property(ai, key, "value1"); - Property p2 = builder.given_a_property(ai, key, "value2"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); - - List actualSuggestions = metadataSearchService.searchAutoCompletion(key, "wrong"); + wireMockHelper.stubElasticAutoCompleteApiReturnEmpty(); + + List actualSuggestions = metadataSearchService.searchAutoCompletion("key", "wrong"); List expectedSuggestions = List.of(); assertThat(actualSuggestions).isEqualTo(expectedSuggestions); @@ -167,16 +143,7 @@ void list_no_suggestions_for_wrong_value() { @Test void list_no_suggestions_for_wrong_key() { - AbstractImage ai = builder.given_an_abstract_image(); - String key = "key"; - Property p1 = builder.given_a_property(ai, key, "value1"); - Property p2 = builder.given_a_property(ai, key, "value2"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); + wireMockHelper.stubElasticAutoCompleteApiReturnEmpty(); List actualSuggestions = metadataSearchService.searchAutoCompletion("wrong", ""); List expectedSuggestions = List.of(); @@ -186,18 +153,9 @@ void list_no_suggestions_for_wrong_key() { @Test void list_all_suggestions_for_partial_value() { - AbstractImage ai = builder.given_an_abstract_image(); - String key = "key"; - Property p1 = builder.given_a_property(ai, key, "value1"); - Property p2 = builder.given_a_property(ai, key, "value2"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); - - List actualSuggestions = metadataSearchService.searchAutoCompletion(key, "val"); + wireMockHelper.stubElasticAutoCompleteApiReturnResults(List.of("value1", "value2")); + + List actualSuggestions = metadataSearchService.searchAutoCompletion("key", "val"); List expectedSuggestions = List.of("value1", "value2"); assertThat(actualSuggestions).isEqualTo(expectedSuggestions); @@ -205,18 +163,9 @@ void list_all_suggestions_for_partial_value() { @Test void list_exact_suggestion_for_value() { - AbstractImage ai = builder.given_an_abstract_image(); - String key = "key"; - Property p1 = builder.given_a_property(ai, key, "value1"); - Property p2 = builder.given_a_property(ai, key, "value2"); - - for (Property p : Arrays.asList(p1, p2)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); - - List actualSuggestions = metadataSearchService.searchAutoCompletion(key, "value1"); + wireMockHelper.stubElasticAutoCompleteApiReturnResults(List.of("value1")); + + List actualSuggestions = metadataSearchService.searchAutoCompletion("key", "value1"); List expectedSuggestions = List.of("value1"); assertThat(actualSuggestions).isEqualTo(expectedSuggestions); @@ -224,19 +173,9 @@ void list_exact_suggestion_for_value() { @Test void list_all_suggestions_for_empty_value() { - AbstractImage ai = builder.given_an_abstract_image(); - String key = "key"; - Property p1 = builder.given_a_property(ai, key, "value1"); - Property p2 = builder.given_a_property(ai, key, "value2"); - Property p3 = builder.given_a_property(ai, key, "value3"); - - for (Property p : Arrays.asList(p1, p2, p3)) { - operations.save(p, index); - } - /* Let elasticsearch refresh the indices */ - operations.indexOps(index).refresh(); - - List actualSuggestions = metadataSearchService.searchAutoCompletion(key, ""); + wireMockHelper.stubElasticAutoCompleteApiReturnResults(List.of("value1", "value2", "value3")); + + List actualSuggestions = metadataSearchService.searchAutoCompletion("key", ""); List expectedSuggestions = List.of("value1", "value2", "value3"); assertThat(actualSuggestions).isEqualTo(expectedSuggestions); diff --git a/src/test/java/be/cytomine/utils/WireMockHelper.java b/src/test/java/be/cytomine/utils/WireMockHelper.java new file mode 100644 index 00000000..fa0d72bd --- /dev/null +++ b/src/test/java/be/cytomine/utils/WireMockHelper.java @@ -0,0 +1,84 @@ +package be.cytomine.utils; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.springframework.http.HttpStatus; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; + +public class WireMockHelper { + + private final WireMockServer wireMockServer; + + public WireMockHelper(WireMockServer wireMockServer) { + this.wireMockServer = wireMockServer; + } + + private String generateHitResult(String key, String value) { + return "{\"_index\":\"properties\",\"_id\":\"1041\",\"_score\":1,\"_version\":1," + + "\"_source\":{\"value\":\"" + value + "\",\"@timestamp\":\"2024-08-21T09:04:00.694446626Z\"," + + "\"domain_class_name\":\"be.cytomine.domain.image.AbstractImage\",\"domain_ident\":201," + + "\"key\":\"" + key + "\",\"id\":1041,\"@version\":\"1\"}}"; + } + + public void stubElasticSearchApi(long id, int filterSize) { + wireMockServer.stubFor(post(urlPathMatching("/properties/_search")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withHeader("X-Elastic-Product", "Elasticsearch") + .withBody("{\"took\":911," + + "\"timed_out\":false," + + "\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0,\"failed\":0}," + + "\"hits\":{\"total\":{\"value\":1,\"relation\":\"eq\"}," + + "\"max_score\":1,\"hits\":[{\"_index\":\"properties\",\"_id\":\"1041\",\"_score\":1," + + "\"_version\":1," + + "\"_source\":{\"value\":\"test-value\",\"@timestamp\":\"2024-08-21T09:04:00.694446626Z\"," + + "\"domain_class_name\":\"be.cytomine.domain.image.AbstractImage\",\"domain_ident\":201," + + "\"key\":\"test-key\",\"id\":1041,\"@version\":\"1\"}}]}," + + "\"aggregations\":{\"lterms#domain_ident_count\":{\"buckets\":" + + "[{\"key\":" + id + ",\"doc_count\":" + filterSize + "}]}}}" + ) + .withStatus(HttpStatus.OK.value()) + ) + ); + } + + public void stubElasticAutoCompleteApiReturnEmpty() { + wireMockServer.stubFor(post(urlPathMatching("/properties/_search")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withHeader("X-Elastic-Product", "Elasticsearch") + .withBody("{\"took\":911,\"timed_out\":false," + + "\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0,\"failed\":0}," + + "\"hits\":{\"total\":{\"value\":0,\"relation\":\"eq\"}," + + "\"max_score\":0,\"hits\":[]}}" + ) + .withStatus(HttpStatus.OK.value()) + ) + ); + } + + public void stubElasticAutoCompleteApiReturnResults(List values) { + String hits = values + .stream() + .map(value -> generateHitResult(UUID.randomUUID().toString(), value)) + .collect(Collectors.joining(",")); + + wireMockServer.stubFor(post(urlPathMatching("/properties/_search")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withHeader("X-Elastic-Product", "Elasticsearch") + .withBody("{\"took\":911," + + "\"timed_out\":false," + + "\"_shards\":{\"total\":1,\"successful\":1,\"skipped\":0,\"failed\":0}," + + "\"hits\":{\"total\":{\"value\":" + values.size() + ",\"relation\":\"eq\"}," + + "\"max_score\":1,\"hits\":[" + hits + "]}}" + ) + .withStatus(HttpStatus.OK.value()) + ) + ); + } +}