From f9de435948c01c6b231688ba6e91e6c5768727c0 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Wed, 26 Jul 2023 15:53:23 -0400 Subject: [PATCH] [ALS-4412] - Implement query ID API endpoints (#111) * Refactoring AggDataSharingResourceRS - Make logging more consistent - Check for 2xx response codes, not just 200 - Deduplicate constructor code - Make some constants final - Fix 2xx predicates * Unit tests + bug fixes - Added coverage to AggregateDataSharingResourceRS - Made a helper class for those tests - Fixed a marshalling bug * Do not persist to common area repo --- .../AggregateDataSharingResourceRS.java | 180 +++++++++--------- ...eDataSharingResourceRSAcceptanceTests.java | 136 +++++++++++-- .../avillach/ProxyPostEndpointMocker.java | 67 +++++++ .../dbmi/avillach/util/HttpClientUtil.java | 4 + 4 files changed, 279 insertions(+), 108 deletions(-) create mode 100644 pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/ProxyPostEndpointMocker.java diff --git a/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/main/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRS.java b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/main/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRS.java index 61ae2837..ffb9ae37 100644 --- a/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/main/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRS.java +++ b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/main/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRS.java @@ -1,10 +1,7 @@ package edu.harvard.hms.dbmi.avillach; import static edu.harvard.dbmi.avillach.service.ResourceWebClient.QUERY_METADATA_FIELD; -import static edu.harvard.dbmi.avillach.util.HttpClientUtil.composeURL; import static edu.harvard.dbmi.avillach.util.HttpClientUtil.readObjectFromResponse; -import static edu.harvard.dbmi.avillach.util.HttpClientUtil.retrievePostResponse; -import static edu.harvard.dbmi.avillach.util.HttpClientUtil.throwResponseError; import java.io.IOException; import java.util.*; @@ -23,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import edu.harvard.dbmi.avillach.data.entity.Resource; import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; +import edu.harvard.dbmi.avillach.util.HttpClientUtil; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -54,42 +52,29 @@ public class AggregateDataSharingResourceRS implements IResourceRS { private static final ObjectMapper objectMapper = new ObjectMapper(); - private Header[] headers; + private final Header[] headers; private static final String BEARER_STRING = "Bearer "; - private Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final int threshold; private final int variance; private final String randomSalt; - public static final List ALLOWED_RESULT_TYPES = Arrays.asList(new String [] { - "COUNT", "CROSS_COUNT", "INFO_COLUMN_LISTING", "OBSERVATION_COUNT", - "OBSERVATION_CROSS_COUNT", "CATEGORICAL_CROSS_COUNT", "CONTINUOUS_CROSS_COUNT" - }); - public AggregateDataSharingResourceRS() { - logger.info("initialize Aggregate Resource NO INJECTION"); - - if (properties == null) { - properties = new ApplicationProperties(); - properties.init("pic-sure-aggregate-resource"); - } - - threshold = properties.getTargetPicsureObfuscationThreshold(); - variance = properties.getTargetPicsureObfuscationVariance(); - randomSalt = properties.getTargetPicsureObfuscationSalt(); - - headers = new Header[] {new BasicHeader(HttpHeaders.AUTHORIZATION, BEARER_STRING + properties.getTargetPicsureToken())}; - + this(null); } @Inject public AggregateDataSharingResourceRS(ApplicationProperties applicationProperties) { this.properties = applicationProperties; - logger.info("initialize Aggregate Resource Injected " + applicationProperties); + if (applicationProperties == null) { + logger.info("initialize Aggregate Resource NO INJECTION"); + } else { + logger.info("initialize Aggregate Resource Injected " + applicationProperties); + } if (properties == null) { properties = new ApplicationProperties(); @@ -132,12 +117,12 @@ public ResourceInfo info(QueryRequest infoRequest) { } String payload = objectMapper.writeValueAsString(chainRequest); - - HttpResponse response = retrievePostResponse(composeURL(properties.getTargetPicsureUrl(), pathName), headers, payload); - if (response.getStatusLine().getStatusCode() != 200) { + String composedURL = HttpClientUtil.composeURL(properties.getTargetPicsureUrl(), pathName); + HttpResponse response = HttpClientUtil.retrievePostResponse(composedURL, headers, payload); + if (!HttpClientUtil.is2xx(response)) { logger.error("{}{} did not return a 200: {} {} ", properties.getTargetPicsureUrl(), pathName, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); - throwResponseError(response, properties.getTargetPicsureUrl()); + HttpClientUtil.throwResponseError(response, properties.getTargetPicsureUrl()); } //if we are proxying an info request, we need to return our own resource ID @@ -160,58 +145,30 @@ public ResourceInfo info(QueryRequest infoRequest) { @Override public SearchResults search(QueryRequest searchRequest) { logger.debug("Calling Aggregate Data Sharing Search"); - if (searchRequest == null) { - throw new ProtocolException(ProtocolException.MISSING_DATA); - } - Object search = searchRequest.getQuery(); - if (search == null) { - throw new ProtocolException((ProtocolException.MISSING_DATA)); - } - - String pathName = "/search"; - try { - QueryRequest chainRequest = new QueryRequest(); - chainRequest.setQuery(searchRequest.getQuery()); - chainRequest.setResourceCredentials(searchRequest.getResourceCredentials()); - - if(properties.getTargetResourceId() != null && !properties.getTargetResourceId().isEmpty()) { - chainRequest.setResourceUUID(UUID.fromString(properties.getTargetResourceId())); - } else { - chainRequest.setResourceUUID(searchRequest.getResourceUUID()); - } - - String payload = objectMapper.writeValueAsString(chainRequest); - - HttpResponse response = retrievePostResponse(composeURL(properties.getTargetPicsureUrl(), pathName), headers, payload); - if (response.getStatusLine().getStatusCode() != 200) { - logger.error("{}{} did not return a 200: {} {} ", properties.getTargetPicsureUrl(), pathName, - response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); - throwResponseError(response, properties.getTargetPicsureUrl()); - } - return readObjectFromResponse(response, SearchResults.class); - } catch (IOException e) { - // Note: this shouldn't ever happen - logger.error("Error encoding search payload", e); - throw new ApplicationException( - "Error encoding search for resource with id " + searchRequest.getResourceUUID()); - } + checkQuery(searchRequest); + HttpResponse response = postRequest(searchRequest, "/search"); + return readObjectFromResponse(response, SearchResults.class); } @POST @Path("/query") @Override - public QueryStatus query(QueryRequest queryJson) { + public QueryStatus query(QueryRequest queryRequest) { logger.debug("Calling Aggregate Data Sharing Resource query()"); - throw new UnsupportedOperationException("Query is not implemented in this resource. Please use query/sync"); + checkQuery(queryRequest); + HttpResponse response = postRequest(queryRequest, "/query"); + return readObjectFromResponse(response, QueryStatus.class); + } @POST @Path("/query/{resourceQueryId}/status") @Override - public QueryStatus queryStatus(@PathParam("resourceQueryId") String queryId, QueryRequest statusQuery) { + public QueryStatus queryStatus(@PathParam("resourceQueryId") String queryId, QueryRequest statusRequest) { logger.debug("Calling Aggregate Data Sharing Resource queryStatus() for query {}", queryId); - throw new UnsupportedOperationException( - "Query status is not implemented in this resource. Please use query/sync"); + checkQuery(statusRequest); + HttpResponse response = postRequest(statusRequest, "/query/" + queryId + "/status"); + return readObjectFromResponse(response, QueryStatus.class); } @POST @@ -219,8 +176,35 @@ public QueryStatus queryStatus(@PathParam("resourceQueryId") String queryId, Que @Override public Response queryResult(@PathParam("resourceQueryId") String queryId, QueryRequest resultRequest) { logger.debug("Calling Aggregate Data Sharing Resource queryResult() for query {}", queryId); - throw new UnsupportedOperationException( - "Query result is not implemented in this resource. Please use query/sync"); + checkQuery(resultRequest); + HttpResponse response = postRequest(resultRequest, "/query/" + queryId + "/result"); + try { + return Response.ok(response.getEntity().getContent()).build(); + } catch (IOException e) { + throw new ApplicationException( + "Error encoding query for resource with id " + resultRequest.getResourceUUID() + ); + } + } + + private HttpResponse postRequest(QueryRequest statusRequest, String pathName) { + try { + QueryRequest chainRequest = createChainRequest(statusRequest); + String payload = objectMapper.writeValueAsString(chainRequest); + String composedURL = HttpClientUtil.composeURL(properties.getTargetPicsureUrl(), pathName); + HttpResponse response = HttpClientUtil.retrievePostResponse(composedURL, headers, payload); + if (!HttpClientUtil.is2xx(response)) { + logger.error("{}{} did not return a 200: {} {} ", properties.getTargetPicsureUrl(), pathName, + response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); + HttpClientUtil.throwResponseError(response, properties.getTargetPicsureUrl()); + } + return response; + } catch (IOException e) { + // Note: this shouldn't ever happen + logger.error("Error encoding search payload", e); + throw new ApplicationException( + "Error encoding search for resource with id " + statusRequest.getResourceUUID()); + } } @POST @@ -228,9 +212,7 @@ public Response queryResult(@PathParam("resourceQueryId") String queryId, QueryR @Override public Response querySync(QueryRequest queryRequest) { logger.debug("Calling Aggregate Data Sharing Resource querySync()"); - if (queryRequest == null || queryRequest.getQuery() == null) { - throw new ProtocolException(ProtocolException.MISSING_DATA); - } + checkQuery(queryRequest); try { Object query = queryRequest.getQuery(); @@ -242,7 +224,11 @@ public Response querySync(QueryRequest queryRequest) { } String expectedResultType = jsonNode.get("expectedResultType").asText(); - if (!ALLOWED_RESULT_TYPES.contains(expectedResultType)) { + Set allowedResultTypes = Set.of( + "COUNT", "CROSS_COUNT", "INFO_COLUMN_LISTING", "OBSERVATION_COUNT", + "OBSERVATION_CROSS_COUNT", "CATEGORICAL_CROSS_COUNT", "CONTINUOUS_CROSS_COUNT" + ); + if (!allowedResultTypes.contains(expectedResultType)) { logger.warn("Incorrect Result Type: " + expectedResultType); return Response.status(Response.Status.BAD_REQUEST).build(); } @@ -274,16 +260,16 @@ public Response querySync(QueryRequest queryRequest) { private HttpResponse getHttpResponse(QueryRequest queryRequest, UUID resourceUUID, String pathName, String targetPicsureUrl) throws JsonProcessingException { String queryString = objectMapper.writeValueAsString(queryRequest); - String composedURL = composeURL(targetPicsureUrl, pathName); + String composedURL = HttpClientUtil.composeURL(targetPicsureUrl, pathName); logger.debug("Aggregate Data Sharing Resource, sending query: " + queryString + ", to: " + composedURL); - HttpResponse response = retrievePostResponse(composedURL, headers, queryString); - if (response.getStatusLine().getStatusCode() != 200) { + HttpResponse response = HttpClientUtil.retrievePostResponse(composedURL, headers, queryString); + if (!HttpClientUtil.is2xx(response)) { logger.error("Not 200 status!"); logger.error( composedURL + " calling resource with id " + resourceUUID + " did not return a 200: {} {} ", response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); - throwResponseError(response, targetPicsureUrl); + HttpClientUtil.throwResponseError(response, targetPicsureUrl); } return response; } @@ -413,28 +399,20 @@ private SearchResults getAllStudyConsents() { @POST @Path("/query/format") public Response queryFormat(QueryRequest queryRequest) { - if (queryRequest == null) { - throw new ProtocolException(ProtocolException.MISSING_DATA); - } - Object search = queryRequest.getQuery(); - if (search == null) { - throw new ProtocolException((ProtocolException.MISSING_DATA)); - } + checkQuery(queryRequest); UUID resourceUUID = queryRequest.getResourceUUID(); String pathName = "/query/format"; try { - String targetPicsureUrl = properties.getTargetPicsureUrl(); String queryString = objectMapper.writeValueAsString(queryRequest); - String composedURL = composeURL(targetPicsureUrl, pathName); - HttpResponse response = retrievePostResponse(composeURL(properties.getTargetPicsureUrl(), pathName), headers, queryString); - if (response.getStatusLine().getStatusCode() != 200) { - logger.error("Not 200 status!"); + String composedURL = HttpClientUtil.composeURL(properties.getTargetPicsureUrl(), pathName); + HttpResponse response = HttpClientUtil.retrievePostResponse(composedURL, headers, queryString); + if (!HttpClientUtil.is2xx(response)) { logger.error( composedURL + " calling resource with id " + resourceUUID + " did not return a 200: {} {} ", response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); - throwResponseError(response, targetPicsureUrl); + HttpClientUtil.throwResponseError(response, properties.getTargetPicsureUrl()); } return Response.ok(response.getEntity().getContent()).build(); @@ -660,9 +638,27 @@ private boolean isCrossCountObfuscated(Map crossCounts, int gene * entityString and then taking the hashcode of the result. The variance will be the hashcode mod the * variance * 2 + 1 - variance. * - * @param entityString The entityString that will be used to generate the variance * @return int The variance for the request */ + private QueryRequest createChainRequest(QueryRequest queryRequest) { + QueryRequest chainRequest = new QueryRequest(); + chainRequest.setQuery(queryRequest.getQuery()); + chainRequest.setResourceCredentials(queryRequest.getResourceCredentials()); + + if(properties.getTargetResourceId() != null && !properties.getTargetResourceId().isEmpty()) { + chainRequest.setResourceUUID(UUID.fromString(properties.getTargetResourceId())); + } else { + chainRequest.setResourceUUID(queryRequest.getResourceUUID()); + } + return chainRequest; + } + + private static void checkQuery(QueryRequest searchRequest) { + if (searchRequest == null || searchRequest.getQuery() == null) { + throw new ProtocolException(ProtocolException.MISSING_DATA); + } + } + private int generateRequestVariance(String entityString) { return Math.abs((entityString + randomSalt).hashCode()) % (variance * 2 + 1) - variance; } diff --git a/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRSAcceptanceTests.java b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRSAcceptanceTests.java index 69e38f15..3899a786 100644 --- a/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRSAcceptanceTests.java +++ b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/AggregateDataSharingResourceRSAcceptanceTests.java @@ -1,7 +1,9 @@ package edu.harvard.hms.dbmi.avillach; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; +import edu.harvard.dbmi.avillach.domain.QueryStatus; +import edu.harvard.dbmi.avillach.util.exception.ResourceInterfaceException; +import org.glassfish.jersey.message.internal.OutboundJaxrsResponse; +import org.glassfish.jersey.message.internal.OutboundMessageContext; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -18,8 +20,8 @@ import static org.mockito.Mockito.*; import java.io.IOException; -import java.util.LinkedHashMap; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,11 +43,11 @@ public class AggregateDataSharingResourceRSAcceptanceTests { private ApplicationProperties mockProperties; - private AggregateDataSharingResourceRS objectUnderTest; + private AggregateDataSharingResourceRS subject; // Pick a random port between 20k and 52k - private final static int port = (int) ((Math.random()*32000)+20000); - private final static String testURL = "http://localhost:"+port+"/"; + private final static int port = 40000; + private final static String testURL = "http://localhost:40000/"; @Rule public WireMockClassRule wireMockRule = new WireMockClassRule(port); @@ -59,7 +61,7 @@ public void setup() throws IOException { when(mockProperties.getTargetPicsureObfuscationSalt()).thenReturn("abc123"); when(mockProperties.getTargetPicsureUrl()).thenReturn(testURL); when(mockProperties.getTargetPicsureToken()).thenReturn("This actually is not needed here, only for the proxy resource."); - objectUnderTest = new AggregateDataSharingResourceRS(mockProperties); + subject = new AggregateDataSharingResourceRS(mockProperties); // Whenever the ADSRRS submits a search for "any" we return the contents of open_access_search_result.json wireMockRule.stubFor(post(urlEqualTo("/search")) @@ -272,14 +274,6 @@ private QueryRequest getTestQuery() throws JsonProcessingException, JsonMappingE return mapper.readValue(getTestJson("test_cross_count_query"), QueryRequest.class); } - private String getObfuscatedTestQueryJson() throws JsonProcessingException, JsonMappingException, IOException { - return getTestJson("obfuscated_cross_count_query"); - } - - private String getObfuscatedTestQueryJson2() throws JsonProcessingException, JsonMappingException, IOException { - return getTestJson("obfuscated_cross_count_query2"); - } - private String getTestJson(String json_file_name) throws IOException { return IOUtils.toString(this.getClass().getClassLoader().getResourceAsStream(json_file_name + ".json"), Charsets.UTF_8); } @@ -308,7 +302,7 @@ private String getObfuscatedResponseForResult(String originalResult) .withStatus(200) .withBody(getTestJson(originalResult)))); - Response response = objectUnderTest.querySync(getTestQuery()); + Response response = subject.querySync(getTestQuery()); // TODO: This is what should be sent // Response response = objectUnderTest.querySync(mapper.readValue(getObfuscatedTestQueryJson(), QueryRequest.class)); @@ -316,4 +310,114 @@ private String getObfuscatedResponseForResult(String originalResult) return responseJson; } + @Test + public void shouldPostQuery() { + UUID targetResourceId = UUID.fromString(mockProperties.getTargetResourceId()); + QueryRequest originalRequest = + createRequest("I seek the holy grail.", Map.of("name", "Sir Lancelot"), UUID.randomUUID()); + QueryRequest postedRequest = + createRequest("I seek the holy grail.", Map.of("name", "Sir Lancelot"), targetResourceId); + + QueryStatus expectedResponse = new QueryStatus(); + expectedResponse.setResourceID(targetResourceId); + + ProxyPostEndpointMocker.start(wireMockRule) + .withPath("/query") + .withRequestBody(postedRequest) + .withResponseBody(expectedResponse) + .withStatusCode(201) + .commit(); + + QueryStatus actual = subject.query(originalRequest); + + // equality isn't defined for QueryRequest, and I'm scared to define it, so let's + // just compare resource IDs + assertEquals(expectedResponse.getResourceID(), actual.getResourceID()); + } + + @Test + public void shouldPostQueryStatus() { + UUID targetResourceId = UUID.fromString(mockProperties.getTargetResourceId()); + QueryRequest originalRequest = + createRequest("I seek the holy grail.", Map.of("name", "King Arthur"), UUID.randomUUID()); + QueryRequest postedRequest = + createRequest("I seek the holy grail.", Map.of("name", "King Arthur"), targetResourceId); + + QueryStatus expectedResponse = new QueryStatus(); + expectedResponse.setResourceID(targetResourceId); + + ProxyPostEndpointMocker.start(wireMockRule) + .withPath("/query/aaaaaaaaaaaaaah/status") + .withRequestBody(postedRequest) + .withResponseBody(expectedResponse) + .withStatusCode(200) + .commit(); + + QueryStatus actual = subject.queryStatus("aaaaaaaaaaaaaah", originalRequest); + + // equality isn't defined for QueryRequest, and I'm scared to define it, so let's + // just compare resource IDs + assertEquals(expectedResponse.getResourceID(), actual.getResourceID()); + } + + @Test + public void shouldPostQueryResult() { + UUID targetResourceId = UUID.fromString(mockProperties.getTargetResourceId()); + QueryRequest originalRequest = + createRequest("I seek the holy grail.", Map.of("name", "King Arthur"), UUID.randomUUID()); + QueryRequest postedRequest = + createRequest("I seek the holy grail.", Map.of("name", "King Arthur"), targetResourceId); + + Response expectedResponse = new OutboundJaxrsResponse(Response.Status.OK, new OutboundMessageContext()); + + ProxyPostEndpointMocker.start(wireMockRule) + .withPath("/query/aaaaaaaaaaaaaah/result") + .withRequestBody(postedRequest) + .withResponseBody(expectedResponse) + .withStatusCode(200) + .commit(); + + Response actual = subject.queryResult("aaaaaaaaaaaaaah", originalRequest); + + assertEquals(expectedResponse.getStatus(), actual.getStatus()); + } + + @Test + public void shouldHandleErrorResponse() { + UUID targetResourceId = UUID.fromString(mockProperties.getTargetResourceId()); + QueryRequest originalRequest = + createRequest("I seek the holy grail.", Map.of("name", "Sir Lancelot"), UUID.randomUUID()); + QueryRequest postedRequest = + createRequest("I seek the holy grail.", Map.of("name", "Sir Lancelot"), targetResourceId); + + QueryStatus expectedResponse = new QueryStatus(); + expectedResponse.setResourceID(targetResourceId); + + ProxyPostEndpointMocker.start(wireMockRule) + .withPath("/query") + .withRequestBody(postedRequest) + .withResponseBody(expectedResponse) + .withStatusCode(500) + .commit(); + + + ResourceInterfaceException exception = null; + + try { + subject.query(originalRequest); + } catch (ResourceInterfaceException ex) { + exception = ex; + } + + assertNotNull(exception); + } + + private static QueryRequest createRequest(String query, Map credentials, UUID resourceUUID) { + QueryRequest originalRequest = new QueryRequest(); + originalRequest.setQuery(query); + originalRequest.setResourceCredentials(credentials); + originalRequest.setResourceUUID(resourceUUID); + return originalRequest; + } + } \ No newline at end of file diff --git a/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/ProxyPostEndpointMocker.java b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/ProxyPostEndpointMocker.java new file mode 100644 index 00000000..256382d5 --- /dev/null +++ b/pic-sure-resources/pic-sure-aggregate-data-sharing-resource/src/test/java/edu/harvard/hms/dbmi/avillach/ProxyPostEndpointMocker.java @@ -0,0 +1,67 @@ +package edu.harvard.hms.dbmi.avillach; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; + +/** + * Prettier interface for mocking a http response. Only supports POST rn. + */ +public class ProxyPostEndpointMocker { + private WireMockClassRule rule; + + private String responseBody; + + private int status; + + private String requestBody; + + private String path; + + + public static ProxyPostEndpointMocker start(WireMockClassRule rule) { + ProxyPostEndpointMocker mock = new ProxyPostEndpointMocker(); + mock.rule = rule; + return mock; + } + + public ProxyPostEndpointMocker withPath(String path) { + this.path = path; + return this; + } + + public ProxyPostEndpointMocker withResponseBody(Object body) { + this.responseBody = toJsonString(body); + return this; + } + + public ProxyPostEndpointMocker withRequestBody(Object body) { + this.requestBody = toJsonString(body); + return this; + } + + public ProxyPostEndpointMocker withStatusCode(int status) { + this.status = status; + return this; + } + + public void commit() { + rule.stubFor(WireMock.post(WireMock.urlEqualTo(path)) + .withRequestBody(WireMock.equalToJson(requestBody)) + .willReturn(WireMock.aResponse() + .withStatus(status) + .withBody(responseBody) + ) + ); + } + + private String toJsonString(Object o) { + try { + return new ObjectMapper().writeValueAsString(o); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java index ef112598..ebaff1f4 100644 --- a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java +++ b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java @@ -48,6 +48,10 @@ public class HttpClientUtil { private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class); + public static boolean is2xx(HttpResponse response) { + return response.getStatusLine().getStatusCode() / 100 == 2; + } + public static HttpResponse retrieveGetResponse(String uri, List
headers) { return retrieveGetResponse(uri, headers.toArray(new Header[headers.size()])); }