Skip to content

Commit

Permalink
Merge pull request #26 from ts4nfdi/feature/add-tests
Browse files Browse the repository at this point in the history
Feature: Add integration tests for the search service
  • Loading branch information
syphax-bouazzouni authored Aug 19, 2024
2 parents d662d4b + 7a1cc45 commit 7ca8c75
Show file tree
Hide file tree
Showing 13 changed files with 4,752 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ build/

# System files
.DS_Store

/src/frontend/
13 changes: 12 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>API-Gateway</name>
<description> API-Gateway Federated Terminology Services </description>
<description>API-Gateway Federated Terminology Services </description>

<properties>
<java.version>11</java.version>
Expand Down Expand Up @@ -81,6 +81,17 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,7 @@ public Map<String, Object> transformItem(Map<String, Object> item) {
transformedItem.put("backend_type", item.get("backend_type"));
}
if (item.containsKey("type") && item.get("type") != null) {
// the value of the key @type in OntoPortal is saved as an IRI
if (item.containsKey("backend_type") && String.valueOf(item.get("backend_type")).equals("ontoportal")) {
transformedItem.put("type",
ResourceFactory.createResource(String.valueOf(item.get("type"))).getLocalName().toLowerCase());
} else {
transformedItem.put("type", item.get("type"));
}
transformedItem.put("type", item.get("type"));
}
return transformedItem;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.apache.http.HttpStatus;
import org.apache.jena.rdf.model.ResourceFactory;
import org.semantics.apigateway.service.DynSearchService;
import org.semantics.apigateway.service.search.DynSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ private Map<String, Object> processItem(Map<String, Object> item, OntologyConfig
}

if (responseMapping.getDescription() != null && item.containsKey(responseMapping.getDescription())) {
newItem.put("description", item.get(responseMapping.getDescription()));
List<String> list = (List<String>) item.get(responseMapping.getDescription());
newItem.put("description", list);
}
if (responseMapping.getOntology() != null && item.containsKey(responseMapping.getOntology())) {
if (responseMapping.getOntology().equals("links")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.semantics.apigateway.config.DatabaseConfig;
import org.semantics.apigateway.config.OntologyConfig;
import org.semantics.apigateway.model.DynTransformResponse;
import org.semantics.apigateway.service.search.DynSearchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,73 @@
package org.semantics.apigateway.service;
package org.semantics.apigateway.service.search;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.Setter;
import org.apache.http.HttpStatus;
import org.semantics.apigateway.config.OntologyConfig;
import org.semantics.apigateway.model.DynDatabaseTransform;
import org.semantics.apigateway.model.DynTransformResponse;
import org.semantics.apigateway.model.JsonLdTransform;
import org.semantics.apigateway.service.ConfigurationLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;





@Service
public class DynSearchService {



@Autowired
private ConfigurationLoader configurationLoader;

@Autowired
private SearchLocalIndexerService localIndexer;


@Setter
private RestTemplate restTemplate;


private static final Logger logger = LoggerFactory.getLogger(DynSearchService.class);
private final RestTemplate restTemplate = new RestTemplate();



private final DynTransformResponse dynTransformResponse = new DynTransformResponse();

private List<OntologyConfig> ontologyConfigs;

private Map<String, Map<String, String>> responseMappings;
private SearchLocalIndexerService localIndexer;

@Autowired
public DynSearchService(ConfigurationLoader configurationLoader, SearchLocalIndexerService localIndexer) {
this.configurationLoader = configurationLoader;
this.ontologyConfigs = configurationLoader.getOntologyConfigs();
this.responseMappings = configurationLoader.getResponseMappings();
this.localIndexer = localIndexer;
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
this.restTemplate = new RestTemplate(factory);
}

// Constructs the URL for the API call based on the query and configuration
Expand All @@ -68,7 +93,12 @@ public CompletableFuture<List<Map<String, Object>>> search(String query, Ontolog
try {
String url = constructUrl(query, config);
logger.info("Accessing URL: {}", url);
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
ResponseEntity<Map> response;
try {
response = restTemplate.getForEntity(url, Map.class);
}catch (Exception e) {
response = ResponseEntity.status(404).body(new HashMap<>());
}

if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
logger.info("Raw API Response: {}", response.getBody());
Expand Down Expand Up @@ -122,7 +152,7 @@ public CompletableFuture<Object> performDynFederatedSearch(

logger.info("Combined results before transformation: {}", combinedResults);

List<Map<String, Object>> newResults = this.localIndexer.reIndexResults(query, combinedResults, logger);
List<Map<String, Object>> newResults = this.localIndexer.reIndexResults(query.replace("*",""), combinedResults, logger);

if (targetDbSchema != null && !targetDbSchema.isEmpty()) {
Object transformedResults = transformAndStructureResults(newResults, targetDbSchema);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package org.semantics.apigateway.service;
package org.semantics.apigateway.service.search;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.NoArgsConstructor;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queries.spans.SpanMultiTermQueryWrapper;
import org.apache.lucene.queries.spans.SpanNearQuery;
import org.apache.lucene.queries.spans.SpanQuery;
import org.apache.lucene.queries.spans.SpanTermQuery;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.*;
import org.apache.lucene.store.ByteBuffersDirectory;
Expand All @@ -19,7 +17,9 @@
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Service
@NoArgsConstructor
Expand All @@ -31,7 +31,10 @@ public class SearchLocalIndexerService {
public List<Map<String, Object>> reIndexResults(String query, List<Map<String, Object>> combinedResults , Logger logger) throws IOException, ParseException {
Directory index = indexResults(combinedResults);

return localIndexSearch(query, logger, index, INDEXED_FIELD);
List<Map<String, Object>> localIndexedResult = localIndexSearch(query, logger, index, INDEXED_FIELD);

return localIndexedResult.stream().map(x -> combinedResults.stream().filter(y -> y.get("iri").equals(x.get("iri")) && y.get("backend_type").equals(x.get("backend_type")))
.findFirst().orElse(null)).collect(Collectors.toList());
}

private static List<Map<String, Object>> localIndexSearch(String query, Logger logger, Directory index, String field) throws IOException {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/response-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
{
"database": "ols",
"url": "https://www.ebi.ac.uk/ols4/api/search?q=%s",
"url": "https://ebi.ac.uk/ols4/api/search?q=%s",
"apiKey": "",
"responseMapping": {
"nestedJson": "response",
Expand Down
115 changes: 115 additions & 0 deletions src/test/java/org/semantics/apigateway/SearchServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.semantics.apigateway;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.semantics.apigateway.config.OntologyConfig;
import org.semantics.apigateway.service.ConfigurationLoader;
import org.semantics.apigateway.service.search.DynSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Type;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SearchServiceTest {

@Autowired
private DynSearchService searchService;

@Mock
private RestTemplate restTemplate; // Mock RestTemplate

@Autowired
private ConfigurationLoader configurationLoader; // Autowire the real ConfigurationLoader




@Test
public void testSearchAllDatabases() {

searchService.setRestTemplate(restTemplate);

List<OntologyConfig> configs = configurationLoader.getOntologyConfigs();

when(restTemplate.getForEntity(
anyString(),
any(Class.class)))
.thenAnswer(invocation -> {
String url = invocation.getArgument(0, String.class);
ResponseEntity<Map<String, Object>> response = ResponseEntity.status(404).body(new HashMap<>());

for (OntologyConfig config : configs) {
String serviceName = String.format("src/test/resources/mocks/search/%s.json", config.getDatabase());
String jsonResponse = new String(Files.readAllBytes(Paths.get(serviceName)));

String configHost = new URL(config.getUrl()).getHost();
String currentURLHost = new URL(url).getHost();


if (configHost.equals(currentURLHost)) {

Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> map = gson.fromJson(jsonResponse, mapType);

response = ResponseEntity.status(200).body(map);
return response;
}
}
return response;
});


// Perform the search
CompletableFuture<Object> r = searchService.performDynFederatedSearch("plant", "", "", "");

List<Map<String, Object>> responseList = (List<Map<String, Object>>) r.join();

assertThat(responseList).hasSize(100);

Map<String, Object> firstPlant = responseList.get(0);
assertThat(firstPlant.get("iri")).isEqualTo("http://sweetontology.net/matrPlant/Plant");
assertThat(firstPlant.get("backend_type")).isEqualTo("ontoportal");
assertThat(firstPlant.get("short_form")).isEqualTo("plant");
assertThat(firstPlant.get("label")).isEqualTo("plant");
assertThat(firstPlant.get("source")).isEqualTo("https://data.biodivportal.gfbio.dev");
assertThat(firstPlant.get("type")).isEqualTo("class");
assertThat(firstPlant.get("ontology")).isEqualTo("sweet");

Map<String, Object> secondPlant = responseList.get(1);
assertThat(secondPlant.get("iri")).isEqualTo("http://purl.obolibrary.org/obo/NCIT_C14258");
assertThat(secondPlant.get("backend_type")).isEqualTo("ols");
assertThat(secondPlant.get("short_form")).isEqualTo("NCIT_C14258");
assertThat(secondPlant.get("description"))
.isEqualTo(new ArrayList<String>(List.of(new String[]{"Any living organism that typically synthesizes its food from inorganic substances, possesses cellulose cell walls, responds slowly and often permanently to a stimulus, lacks specialized sense organs and nervous system, and has no powers of locomotion. (EPA Terminology Reference System)"})));
assertThat(secondPlant.get("label")).isEqualTo("Plant");
assertThat(secondPlant.get("source")).isEqualTo("https://ebi.ac.uk/ols4/api");
assertThat(secondPlant.get("type")).isEqualTo("class");
assertThat(secondPlant.get("ontology")).isEqualTo("ncit");


assertThat(responseList.stream().map(x -> x.get("backend_type")).distinct().sorted().collect(Collectors.toList()))
.isEqualTo(configs.stream().map(OntologyConfig::getDatabase).sorted().collect(Collectors.toList()));
}
}
Loading

0 comments on commit 7ca8c75

Please sign in to comment.