diff --git a/pom.xml b/pom.xml index a3ae0a9..f40a04a 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ UTF-8 - 2.8.2 + 2.8.8 1.0.6 @@ -62,53 +62,28 @@ org.apache.commons commons-lang3 - 3.4 + 3.5 org.springframework spring-core - 4.2.5.RELEASE + 4.3.8.RELEASE com.flipkart.zjsonpatch zjsonpatch - 0.2.1 + 0.3.1 - com.google.guava - guava - 19.0 - - - com.mashape.unirest - unirest-java - 1.4.5 - - - org.jglue.fluent-json - fluent-json - 2.0.0 + org.apache.httpcomponents + httpclient + 4.5.3 com.github.fge json-schema-validator 2.2.6 - - junit - junit - 4.12 - - - com.samskivert - jmustache - 1.13 - - - org.assertj - assertj-core - 3.4.1 - com.fasterxml.jackson.core jackson-core @@ -124,11 +99,23 @@ jackson-dataformat-yaml ${jackson.version} + + com.samskivert + jmustache + 1.13 + true + + + junit + junit + 4.12 + test + org.codehaus.groovy groovy-all - 2.4.6 + 2.4.10 test @@ -143,6 +130,12 @@ ${jackson.version} test + + org.assertj + assertj-core + 3.6.2 + test + diff --git a/src/main/java/rocks/bastion/core/FileRequest.java b/src/main/java/rocks/bastion/core/FileRequest.java index 1d6fa88..a84406b 100644 --- a/src/main/java/rocks/bastion/core/FileRequest.java +++ b/src/main/java/rocks/bastion/core/FileRequest.java @@ -17,7 +17,7 @@ /** * An HTTP request which takes any arbitrary file/resource, using the data within as its content body. The {@linkplain FileRequest} will not perform * any conversions or validation on any user-supplied body content. Use the static factory methods, such as {@link #post(String, String)} - * or {@link #delete(String, String)} to initialise a new {@linkplain FileRequest}. + * or {@link #patch(String, String)} to initialise a new {@linkplain FileRequest}. *

* By default, this request will contain no headers (except for the content-type) and no query parameters. Use the {@link #addHeader(String, String)} * and {@link #addQueryParam(String, String)}} to add them. Also, initially, Bastion will attempt to guess the MIME type to send as part of the @@ -54,31 +54,6 @@ public static FileRequest post(String url, String resource) throws UnreadableRes return new FileRequest(HttpMethod.POST, url, resource); } - /** - * Construct an HTTP request, using the DELETE method, to be sent on the specified URL. The request's content will be loaded - * from the specified resource URL. Bastion will attempt to guess the MIME type to send by looking at the given file. - *

- * The resource source is specified as a resource URL as described in {@link ResourceLoader}. Valid resource URLs include (but - * are not limited to): - *

- * - *

- * For more information about which resource URLs are accepted see the documentation for {@link ResourceLoader}. - *

- * - * @param url A non-{@literal null} URL to send the request on - * @param resource A non-{@literal null} resource URL to load the data from, for this request - * @return An HTTP request using the DELETE method - * @throws UnreadableResourceException Thrown if the specified resource exists but cannot be read (because it is a directory, for example) - * @throws ResourceNotFoundException Thrown if the specified resource does not exist - */ - public static FileRequest delete(String url, String resource) throws UnreadableResourceException, ResourceNotFoundException { - return new FileRequest(HttpMethod.DELETE, url, resource); - } - /** * Construct an HTTP request, using the PUT method, to be sent on the specified URL. The request's content will be loaded * from the specified resource URL. Bastion will attempt to guess the MIME type to send by looking at the given file. diff --git a/src/main/java/rocks/bastion/core/RequestExecutor.java b/src/main/java/rocks/bastion/core/RequestExecutor.java index 3641c2c..a400b16 100644 --- a/src/main/java/rocks/bastion/core/RequestExecutor.java +++ b/src/main/java/rocks/bastion/core/RequestExecutor.java @@ -1,13 +1,27 @@ package rocks.bastion.core; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; -import com.mashape.unirest.request.HttpRequestWithBody; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import rocks.bastion.core.configuration.Configuration; -import java.io.InputStream; +import java.io.IOException; import java.net.SocketTimeoutException; +import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -15,8 +29,6 @@ import java.util.Objects; import java.util.stream.Collectors; -import rocks.bastion.core.configuration.Configuration; - /** * Responsible for executing a Bastion remote request built using the {@link BastionBuilderImpl} builder and prepare a response object. */ @@ -24,19 +36,24 @@ public class RequestExecutor { private Configuration configuration; private HttpRequest bastionHttpRequest; - private com.mashape.unirest.request.HttpRequest executableHttpRequest; + private HttpUriRequest executableHttpRequest; private Collection headers; private String resolvedUrl; + private URIBuilder uriBuilder; public RequestExecutor(HttpRequest bastionHttpRequest, Configuration configuration) { - Objects.requireNonNull(bastionHttpRequest); - this.bastionHttpRequest = bastionHttpRequest; - this.configuration = configuration; - executableHttpRequest = prepareHttpRequest(); - applyHeaders(); - applyQueryParameters(); - applyRouteParameters(); - applyBody(); + try { + Objects.requireNonNull(bastionHttpRequest); + this.bastionHttpRequest = bastionHttpRequest; + this.configuration = configuration; + uriBuilder = new URIBuilder(applyRouteParameters()); + applyQueryParameters(); + executableHttpRequest = prepareHttpRequest(); + applyHeaders(); + applyBody(); + } catch (URISyntaxException exception) { + throw new IllegalStateException("Could not compute URI", exception); + } } public String getMethod() { @@ -58,96 +75,109 @@ public Collection getHeaders() { */ public Response execute() { try { - HttpResponse httpResponse = performRequest(); + CloseableHttpResponse httpResponse = performRequest(); return convertToRawResponse(httpResponse); - } catch (UnirestException exception) { - if (exception.getCause() instanceof SocketTimeoutException) { - throw new AssertionError(String.format("Failed to receive response before timeout of [%s] ms", resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration))); - } + } catch (SocketTimeoutException exception) { + throw new AssertionError(String.format("Failed to receive response before timeout of [%s] ms", + resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration)), exception); + } catch (IOException exception) { throw new IllegalStateException("Failed executing request", exception); } } - private com.mashape.unirest.request.HttpRequest prepareHttpRequest() { + private HttpUriRequest prepareHttpRequest() throws URISyntaxException { long timeout = resolveTimeoutOrFallbackToGlobal(bastionHttpRequest, configuration); - Unirest.setTimeouts(timeout, timeout); - com.mashape.unirest.request.HttpRequest request; + HttpRequestBase request; + resolvedUrl = uriBuilder.build().toString(); switch (bastionHttpRequest.method().getValue()) { case "GET": - request = Unirest.get(bastionHttpRequest.url()); + request = new HttpGet(resolvedUrl); break; case "POST": - request = Unirest.post(bastionHttpRequest.url()); + request = new HttpPost(resolvedUrl); break; case "PATCH": - request = Unirest.patch(bastionHttpRequest.url()); + request = new HttpPost(resolvedUrl); break; case "DELETE": - request = Unirest.delete(bastionHttpRequest.url()); + request = new HttpDelete(resolvedUrl); break; case "PUT": - request = Unirest.put(bastionHttpRequest.url()); + request = new HttpPut(resolvedUrl); break; case "OPTIONS": - request = Unirest.options(bastionHttpRequest.url()); + request = new HttpOptions(resolvedUrl); break; case "HEAD": - request = Unirest.head(bastionHttpRequest.url()); + request = new HttpHead(resolvedUrl); break; default: - throw new UnsupportedOperationException(String.format("We cannot perform a request of type %s.", bastionHttpRequest.method().getValue())); + throw new UnsupportedOperationException(String.format("We cannot perform a request of type %s.", + bastionHttpRequest.method().getValue())); } + request.setConfig(RequestConfig.custom() + .setConnectTimeout((int) timeout) + .setSocketTimeout((int) timeout) + .setConnectionRequestTimeout((int) timeout) + .build()); return request; } - private static long resolveTimeoutOrFallbackToGlobal(HttpRequest request, Configuration configuration) { - if (request.timeout() == HttpRequest.USE_GLOBAL_TIMEOUT) { - return configuration.getGlobalRequestAttributes().getGlobalRequestTimeout(); - } else { - return request.timeout(); - } - } - private void applyHeaders() { headers = new LinkedList<>(configuration.getGlobalRequestAttributes().getGlobalHeaders()); headers.addAll(bastionHttpRequest.headers()); - if (headers.stream().noneMatch(header -> header.getName().equalsIgnoreCase("content-type")) && bastionHttpRequest.contentType().isPresent()) { + if (headers.stream().noneMatch(header -> header.getName().equalsIgnoreCase("content-type")) && bastionHttpRequest.contentType() + .isPresent()) { headers.add(new ApiHeader("Content-Type", bastionHttpRequest.contentType().get().toString())); } - headers.forEach(header -> executableHttpRequest.header(header.getName(), header.getValue())); + headers.forEach(header -> executableHttpRequest.addHeader(header.getName(), header.getValue())); } private void applyQueryParameters() { List apiQueryParams = new ArrayList<>(configuration.getGlobalRequestAttributes().getGlobalQueryParams()); apiQueryParams.addAll(bastionHttpRequest.queryParams()); - apiQueryParams.forEach(queryParam -> executableHttpRequest.queryString(queryParam.getName(), queryParam.getValue())); - resolvedUrl = executableHttpRequest.getUrl(); + apiQueryParams.forEach(queryParam -> uriBuilder.addParameter(queryParam.getName(), queryParam.getValue())); } - private void applyRouteParameters() { + private String applyRouteParameters() { List routeParams = new ArrayList<>(configuration.getGlobalRequestAttributes().getGlobalRouteParams()); routeParams.addAll(bastionHttpRequest.routeParams()); - routeParams.forEach(routeParam -> executableHttpRequest.routeParam(routeParam.getName(), routeParam.getValue())); - resolvedUrl = executableHttpRequest.getUrl(); + String urlWithPlaceholders = bastionHttpRequest.url(); + for (RouteParam routeParam : routeParams) { + urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\Q{" + routeParam.getName() + "}\\E", routeParam.getValue()); + } + return urlWithPlaceholders; } private void applyBody() { - if (executableHttpRequest instanceof HttpRequestWithBody) { - ((HttpRequestWithBody) executableHttpRequest).body(bastionHttpRequest.body().toString()); + if (executableHttpRequest instanceof HttpEntityEnclosingRequest) { + ((HttpEntityEnclosingRequest) executableHttpRequest).setEntity(new StringEntity( + bastionHttpRequest.body().toString(), + bastionHttpRequest.contentType().orElse(null) + )); } } - private HttpResponse performRequest() throws UnirestException { - return executableHttpRequest.asBinary(); + private CloseableHttpResponse performRequest() throws IOException { + CloseableHttpClient httpClient = HttpClients.createDefault(); + return httpClient.execute(executableHttpRequest); } - private Response convertToRawResponse(HttpResponse httpResponse) { - return new RawResponse(httpResponse.getStatus(), - httpResponse.getStatusText(), - httpResponse.getHeaders().entrySet().stream().flatMap(header -> - header.getValue().stream().map(headerValue -> - new ApiHeader(header.getKey(), headerValue))).collect(Collectors.toList()), - httpResponse.getBody()); + private Response convertToRawResponse(CloseableHttpResponse httpResponse) throws IOException { + return new RawResponse(httpResponse.getStatusLine().getStatusCode(), + httpResponse.getStatusLine().getReasonPhrase(), + Arrays.stream(httpResponse.getAllHeaders()) + .map(header -> new ApiHeader(header.getName(), header.getValue())) + .collect(Collectors.toList()), + httpResponse.getEntity().getContent()); + } + + private static long resolveTimeoutOrFallbackToGlobal(HttpRequest request, Configuration configuration) { + if (request.timeout() == HttpRequest.USE_GLOBAL_TIMEOUT) { + return configuration.getGlobalRequestAttributes().getGlobalRequestTimeout(); + } else { + return request.timeout(); + } } } diff --git a/src/main/java/rocks/bastion/core/json/JsonRequest.java b/src/main/java/rocks/bastion/core/json/JsonRequest.java index 30c280f..035af0c 100644 --- a/src/main/java/rocks/bastion/core/json/JsonRequest.java +++ b/src/main/java/rocks/bastion/core/json/JsonRequest.java @@ -1,13 +1,21 @@ package rocks.bastion.core.json; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.entity.ContentType; -import rocks.bastion.core.*; +import rocks.bastion.core.ApiHeader; +import rocks.bastion.core.ApiQueryParam; +import rocks.bastion.core.CommonRequestAttributes; +import rocks.bastion.core.HttpMethod; +import rocks.bastion.core.HttpRequest; +import rocks.bastion.core.RouteParam; +import rocks.bastion.core.TemplateCompilationException; +import rocks.bastion.core.TemplateContentCompiler; import rocks.bastion.core.resource.ResourceLoader; import rocks.bastion.core.resource.ResourceNotFoundException; import rocks.bastion.core.resource.UnreadableResourceException; +import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -264,6 +272,17 @@ public static JsonRequest deleteFromResource(String url, String jsonSource) thro * the request will have the "application/json" HTTP header and no other additional headers and no query parameters. * It will also have a descriptive name which is generated by combining the HTTP method with the URL. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param method The HTTP method to use for this request * @param url The URL to send this request on @@ -304,6 +323,17 @@ public static JsonRequest fromTemplate(HttpMethod method, String url, String jso * the request will have the "application/json" HTTP header and no other additional headers and no query parameters. * It will also have a descriptive name which is generated by combining the HTTP method with the URL. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param url The URL to send this request on * @param jsonTemplateSource The resource URL to load the template file from, for this request @@ -341,6 +371,17 @@ public static JsonRequest postFromTemplate(String url, String jsonTemplateSource * the request will have the "application/json" HTTP header and no other additional headers and no query parameters. * It will also have a descriptive name which is generated by combining the HTTP method with the URL. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param url The URL to send this request on * @param jsonTemplateSource The resource URL to load the template file from, for this request @@ -378,6 +419,17 @@ public static JsonRequest putFromTemplate(String url, String jsonTemplateSource, * the request will have the "application/json" HTTP header and no other additional headers and no query parameters. * It will also have a descriptive name which is generated by combining the HTTP method with the URL. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param url The URL to send this request on * @param jsonTemplateSource The resource URL to load the template file from, for this request @@ -415,6 +467,17 @@ public static JsonRequest patchFromTemplate(String url, String jsonTemplateSourc * the request will have the "application/json" HTTP header and no other additional headers and no query parameters. * It will also have a descriptive name which is generated by combining the HTTP method with the URL. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param url The URL to send this request on * @param jsonTemplateSource The resource URL to load the template file from, for this request @@ -628,9 +691,11 @@ public JsonRequest setTimeout(long timeout) { private void validateJson() throws InvalidJsonException { String jsonBody = requestAttributes.body().toString(); try { - new JsonParser().parse(jsonBody); - } catch (JsonSyntaxException parseException) { + new ObjectMapper().getFactory().createParser(jsonBody).readValueAsTree(); + } catch (JsonParseException parseException) { throw new InvalidJsonException(parseException, jsonBody); + } catch (IOException ioException) { + throw new RuntimeException("Unexpected error occurred parsing JSON", ioException); } } } diff --git a/src/main/java/rocks/bastion/core/json/JsonResponseAssertions.java b/src/main/java/rocks/bastion/core/json/JsonResponseAssertions.java index f006411..4196330 100644 --- a/src/main/java/rocks/bastion/core/json/JsonResponseAssertions.java +++ b/src/main/java/rocks/bastion/core/json/JsonResponseAssertions.java @@ -1,20 +1,27 @@ package rocks.bastion.core.json; import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.flipkart.zjsonpatch.JsonDiff; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; import org.apache.http.entity.ContentType; -import org.junit.Assert; -import rocks.bastion.core.*; +import rocks.bastion.core.Assertions; +import rocks.bastion.core.ModelResponse; +import rocks.bastion.core.Response; +import rocks.bastion.core.TemplateCompilationException; +import rocks.bastion.core.TemplateContentCompiler; import rocks.bastion.core.resource.ResourceLoader; import rocks.bastion.core.resource.ResourceNotFoundException; import rocks.bastion.core.resource.UnreadableResourceException; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; import static java.lang.String.format; @@ -91,6 +98,17 @@ public static JsonResponseAssertions fromResource(int expectedStatusCode, String * The loaded JSON, after variables are resolved, must be syntactically correct; otherwise, an exception is * thrown to indicate that the expected JSON string is invalid. *

+ *

+ * IMPORTANT: Before you can use this method, the JMustache library MUST BE on your classpath. If you're using Maven, + * you should add the following to your POM file: + *
+ * <dependency>
+ * <groupId>com.samskivert</groupId>
+ * <artifactId>jmustache</artifactId>
+ * <version>1.13</version>
+ * </dependency>
+ *
+ *

* * @param expectedStatusCode The expected HTTP status code * @param expectedJsonSource The resource to load the expected JSON object template from @@ -200,7 +218,9 @@ public JsonResponseAssertions overrideContentType(ContentType contentType) { @Override public void execute(int statusCode, ModelResponse response, Object model) throws AssertionError { try { - Assert.assertEquals("Response Status Code", expectedStatusCode, statusCode); + if (expectedStatusCode != statusCode) { + throw new AssertionError(format("Received status code <%d> is not as expected <%d>", statusCode, expectedStatusCode)); + } assertContentTypeHeader(response); JsonNode jsonPatch = computeJsonPatch(response); assertJsonPatchIsEmpty(jsonPatch); @@ -211,7 +231,7 @@ public void execute(int statusCode, ModelResponse response, Object model) thr private static void assertJsonPatchIsEmpty(JsonNode jsonPatch) { if (jsonPatch.size() != 0) { - Assert.fail(format("Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need " + + throw new AssertionError(format("Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need " + "to perform to transform the actual response body into the expected response body:\n %s", jsonPatch.toString())); } } @@ -235,15 +255,19 @@ private String sanitizePropertyName(String field) { private void validateExpectedJson() throws InvalidJsonException { try { - new JsonParser().parse(expectedJson); + new ObjectMapper().getFactory().createParser(expectedJson).readValueAsTree(); } catch (JsonParseException parseException) { throw new InvalidJsonException(parseException, expectedJson); + } catch (IOException ioException) { + throw new RuntimeException("Unexpected error occurred parsing JSON", ioException); } } private void assertContentTypeHeader(Response response) { - Assert.assertTrue("Content-type exists in response", response.getContentType().isPresent()); - Assert.assertEquals("Content-type MIME type", contentType.getMimeType(), response.getContentType().get().getMimeType()); + ContentType contentType = response.getContentType().orElseThrow(() -> new AssertionError("Content-type expected to be in response")); + if (!Objects.equals(contentType.getMimeType(), this.contentType.getMimeType())) { + throw new AssertionError(format("Expected content-type header <%s> to be as expected <%s>", contentType.getMimeType(), this.contentType.getMimeType())); + } } private JsonNode computeJsonPatch(Response response) throws IOException { diff --git a/src/main/java/rocks/bastion/core/json/JsonSchemaAssertions.java b/src/main/java/rocks/bastion/core/json/JsonSchemaAssertions.java index 530c389..9692c15 100644 --- a/src/main/java/rocks/bastion/core/json/JsonSchemaAssertions.java +++ b/src/main/java/rocks/bastion/core/json/JsonSchemaAssertions.java @@ -9,7 +9,6 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchemaFactory; import org.apache.http.entity.ContentType; -import org.junit.Assert; import rocks.bastion.core.Assertions; import rocks.bastion.core.ModelResponse; import rocks.bastion.core.Response; @@ -20,6 +19,8 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import static java.lang.String.format; + /** * Asserts that an API response conforms to a given JSON schema. */ @@ -59,7 +60,7 @@ public void execute(int statusCode, throw new RuntimeException("An unknown error occurred while processing the JSON schema and API response", e); } } - + /** * The assertions object will initially check that the content-type header returned by the actual response is * "application/json". This can be overriden to check for a different content-type header using this method. Despite @@ -82,12 +83,14 @@ private JsonNode convertResponseToJsonNode(ModelResponse response) throws IOE private void assertResponseConformsToSchema(JsonNode response) throws ProcessingException, IOException { ProcessingReport validationReport = JsonSchemaFactory.byDefault() - .getJsonSchema(getExpectedSchema()).validate(response); + .getJsonSchema(getExpectedSchema()).validate(response); if (!validationReport.isSuccess()) { String messages = StreamSupport.stream(validationReport.spliterator(), false) - .map(ProcessingMessage::getMessage) - .collect(Collectors.joining(", ")); - Assert.fail(String.format("Actual response body is not as specified. The following message(s) where produced during validation; %s.", messages)); + .map(ProcessingMessage::getMessage) + .collect(Collectors.joining(", ")); + throw new AssertionError(format( + "Actual response body is not as specified. The following message(s) where produced during validation; %s.", + messages)); } } @@ -97,8 +100,12 @@ private JsonNode getExpectedSchema() throws IOException { } private void assertContentTypeHeader(Response response) { - Assert.assertTrue("Content-type exists in response", response.getContentType().isPresent()); - Assert.assertEquals("Content-type MIME type", contentType.getMimeType(), response.getContentType().get().getMimeType()); + if (!response.getContentType().isPresent()) { + throw new AssertionError("Response content-type should not be missing."); + } + if (!response.getContentType().get().getMimeType().equals(contentType.getMimeType())) { + throw new AssertionError(format("Response content-type should be \"%s\" but got \"%s\" instead", contentType.getMimeType(), + response.getContentType().get().getMimeType())); + } } - } diff --git a/src/test/java/rocks/bastion/core/FileRequestTest.java b/src/test/java/rocks/bastion/core/FileRequestTest.java index 984fcd6..1515066 100644 --- a/src/test/java/rocks/bastion/core/FileRequestTest.java +++ b/src/test/java/rocks/bastion/core/FileRequestTest.java @@ -26,14 +26,6 @@ public void put() throws Exception { .call(); } - @Test - public void delete() throws Exception { - Bastion.request("Create Sushi", FileRequest.delete("http://localhost:9876/sushi", "classpath:/json/create_sushi_request.json")) - .bind(Sushi.class) - .withAssertions(JsonResponseAssertions.fromResource(201, "classpath:/json/create_sushi_response.json").ignoreValuesForProperties("/id")) - .call(); - } - @Test public void patch() throws Exception { Bastion.request("Create Sushi", FileRequest.patch("http://localhost:9876/sushi", "classpath:/json/create_sushi_request.json")) diff --git a/src/test/java/rocks/bastion/core/assertions/JsonResponseAssertionsTest.java b/src/test/java/rocks/bastion/core/assertions/JsonResponseAssertionsTest.java index 8c42e04..d49130d 100644 --- a/src/test/java/rocks/bastion/core/assertions/JsonResponseAssertionsTest.java +++ b/src/test/java/rocks/bastion/core/assertions/JsonResponseAssertionsTest.java @@ -63,7 +63,7 @@ public void execute_notIgnoredOrderForArrayFieldDisorderedAssertion_shouldThrowE } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", assertionError.getMessage(), "Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need to perform to transform the actual response body into the expected response body:" + "\n" + - " [{\"op\":\"move\",\"path\":\"/array/2\",\"from\":\"/array/0\"}]"); + " [{\"op\":\"move\",\"from\":\"/array/0\",\"path\":\"/array/2\"}]"); return; } Assert.fail("An assertion error should have been thrown by the JSON Response Assertions"); @@ -93,7 +93,7 @@ public void execute_ignoredOrderForArrayFieldExtraElement_shouldThrowErrorWithDi } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", assertionError.getMessage(), "Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need to perform to transform the actual response body into the expected response body:" + "\n" + - " [{\"op\":\"remove\",\"path\":\"/array/0\"},{\"op\":\"replace\",\"path\":\"/array/2\",\"value\":\"third\"}]"); + " [{\"op\":\"remove\",\"path\":\"/array/0\",\"value\":\"third\"},{\"op\":\"replace\",\"path\":\"/array/2\",\"value\":\"third\"}]"); return; } Assert.fail("An assertion error should have been thrown by the JSON Response Assertions"); @@ -108,7 +108,7 @@ public void execute_fromStringJsonMismatches_shouldThrowErrorWithDiff() throws E } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", assertionError.getMessage(), "Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need to perform to transform the actual response body into the expected response body:" + "\n" + - " [{\"op\":\"replace\",\"path\":\"/key\",\"value\":\"kyle\"},{\"op\":\"remove\",\"path\":\"/array\"}]"); + " [{\"op\":\"replace\",\"path\":\"/key\",\"value\":\"kyle\"},{\"op\":\"remove\",\"path\":\"/array\",\"value\":[1,2]}]"); return; } Assert.fail("An assertion error should have been thrown by the JSON Response Assertions"); @@ -131,7 +131,7 @@ public void execute_fromFileJsonMismatches_shouldThrowErrorWithDiff() throws Exc assertions.execute(200, response, response.getModel()); } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", assertionError.getMessage(), "Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need to perform to transform the actual response body into the expected response body:\n" + - " [{\"op\":\"move\",\"path\":\"/timestamp\",\"from\":\"/timestamp1\"},{\"op\":\"remove\",\"path\":\"/colours\"},{\"op\":\"remove\",\"path\":\"/favourites/country\"},{\"op\":\"add\",\"path\":\"/favourites/colours\",\"value\":[\"blue\",\"red\"]}]"); + " [{\"op\":\"move\",\"from\":\"/timestamp1\",\"path\":\"/timestamp\"},{\"op\":\"remove\",\"path\":\"/colours\",\"value\":[\"blue\"]},{\"op\":\"remove\",\"path\":\"/favourites/country\",\"value\":\"Spain\"},{\"op\":\"add\",\"path\":\"/favourites/colours\",\"value\":[\"blue\",\"red\"]}]"); return; } Assert.fail("An assertion error should have been thrown by the JSON Response Assertions"); @@ -156,7 +156,8 @@ public void execute_fromTemplateJsonMismatches_shouldThrowErrorWithDiff() throws assertions.execute(200, response, response.getModel()); } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", assertionError.getMessage(), "Actual response body is not as expected. The following JSON Patch (as per RFC-6902) tells you what operations you need to perform to transform the actual response body into the expected response body:\n" + - " [{\"op\":\"move\",\"path\":\"/timestamp\",\"from\":\"/timestamp1\"},{\"op\":\"remove\",\"path\":\"/colours\"},{\"op\":\"remove\",\"path\":\"/favourites/country\"},{\"op\":\"add\",\"path\":\"/favourites/colours\",\"value\":[\"blue\",\"red\"]}]"); + " [{\"op\":\"move\",\"from\":\"/timestamp1\",\"path\":\"/timestamp\"},{\"op\":\"remove\",\"path\":\"/colours\",\"value\":[\"blue\"]}," + + "{\"op\":\"remove\",\"path\":\"/favourites/country\",\"value\":\"Spain\"},{\"op\":\"add\",\"path\":\"/favourites/colours\",\"value\":[\"blue\",\"red\"]}]"); return; } Assert.fail("An assertion error should have been thrown by the JSON Response Assertions"); diff --git a/src/test/java/rocks/bastion/core/assertions/JsonSchemaAssertionsTest.java b/src/test/java/rocks/bastion/core/assertions/JsonSchemaAssertionsTest.java index d100cd8..4e034e2 100644 --- a/src/test/java/rocks/bastion/core/assertions/JsonSchemaAssertionsTest.java +++ b/src/test/java/rocks/bastion/core/assertions/JsonSchemaAssertionsTest.java @@ -55,7 +55,7 @@ public void execute_fromStringContentTypeMismatch_assertionErrorShouldBeThrown() assertions.execute(201, response, response.getModel()); } catch (AssertionError assertionError) { Assert.assertEquals("Assertion Failed Message", - "Content-type MIME type expected:<[application/jso]n> but was:<[text/plai]n>", + "Response content-type should be \"application/json\" but got \"text/plain\" instead", assertionError.getMessage()); return; } diff --git a/src/test/java/rocks/bastion/support/CreateSushiRequest.java b/src/test/java/rocks/bastion/support/CreateSushiRequest.java index ee932e7..2993dd7 100644 --- a/src/test/java/rocks/bastion/support/CreateSushiRequest.java +++ b/src/test/java/rocks/bastion/support/CreateSushiRequest.java @@ -1,8 +1,13 @@ package rocks.bastion.support; -import com.google.gson.Gson; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.entity.ContentType; -import rocks.bastion.core.*; +import rocks.bastion.core.ApiHeader; +import rocks.bastion.core.ApiQueryParam; +import rocks.bastion.core.HttpMethod; +import rocks.bastion.core.HttpRequest; +import rocks.bastion.core.RouteParam; import rocks.bastion.support.embedded.Sushi; import java.util.Collection; @@ -48,7 +53,11 @@ public Collection routeParams() { @Override public Object body() { - return new Gson().toJson(Sushi.newSushi().name("happiness").build()); + try { + return new ObjectMapper().writeValueAsString(Sushi.newSushi().name("happiness").build()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } } diff --git a/src/test/java/rocks/bastion/support/embedded/Sushi.java b/src/test/java/rocks/bastion/support/embedded/Sushi.java index 4e51a05..3ed7659 100644 --- a/src/test/java/rocks/bastion/support/embedded/Sushi.java +++ b/src/test/java/rocks/bastion/support/embedded/Sushi.java @@ -1,5 +1,7 @@ package rocks.bastion.support.embedded; +import com.fasterxml.jackson.annotation.JsonInclude; + import java.math.BigDecimal; /** @@ -14,6 +16,7 @@ public static Builder newSushi() { private long id; private String name; private BigDecimal price; + @JsonInclude(JsonInclude.Include.NON_NULL) private Type type; protected Sushi() { diff --git a/src/test/java/rocks/bastion/support/embedded/SushiService.java b/src/test/java/rocks/bastion/support/embedded/SushiService.java index 27d1bc0..eb7d428 100644 --- a/src/test/java/rocks/bastion/support/embedded/SushiService.java +++ b/src/test/java/rocks/bastion/support/embedded/SushiService.java @@ -1,9 +1,13 @@ package rocks.bastion.support.embedded; -import com.google.gson.Gson; -import com.google.gson.JsonParseException; -import spark.*; - +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import spark.ResponseTransformer; +import spark.Route; +import spark.Spark; + +import java.io.IOException; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; @@ -146,15 +150,23 @@ public void stop() { */ private class JsonTransformer implements ResponseTransformer { - private Gson gson = new Gson(); + private ObjectMapper objectMapper = new ObjectMapper(); @Override public String render(Object model) { - return gson.toJson(model); + try { + return objectMapper.writeValueAsString(model); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } public T fromJson(String json, Class type) { - return gson.fromJson(json, type); + try { + return objectMapper.readValue(json, type); + } catch (IOException e) { + throw new RuntimeException(e); + } } } } \ No newline at end of file diff --git a/src/test/java/rocks/bastion/support/embedded/TestWithProxiedEmbeddedServer.java b/src/test/java/rocks/bastion/support/embedded/TestWithProxiedEmbeddedServer.java index eadc3ec..2ca2f4e 100644 --- a/src/test/java/rocks/bastion/support/embedded/TestWithProxiedEmbeddedServer.java +++ b/src/test/java/rocks/bastion/support/embedded/TestWithProxiedEmbeddedServer.java @@ -1,8 +1,5 @@ package rocks.bastion.support.embedded; -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; import org.apache.http.HttpHost; import org.apache.http.client.HttpClient; import org.apache.http.config.RegistryBuilder; @@ -32,13 +29,11 @@ public static void setupProxying() { DefaultSchemePortResolver schemePortResolver = prepareSchemePortResolver(); BasicHttpClientConnectionManager connManager = prepareConnectionManager(dnsResolver, schemePortResolver); HttpClient httpClient = prepareHttpClient(connManager); - originalHttpClient = (HttpClient) Options.getOption(Option.HTTPCLIENT); - Unirest.setHttpClient(httpClient); } @AfterClass public static void cleanupProxying() { - Unirest.setHttpClient(originalHttpClient); + // TODO } private static DefaultSchemePortResolver prepareSchemePortResolver() {