Converted to java

This commit is contained in:
Marcin Grzejszczak
2019-07-01 15:48:52 +02:00
parent 8f0db8ffe2
commit 93f2044ef6
7 changed files with 395 additions and 276 deletions

View File

@@ -16,6 +16,10 @@
package org.springframework.cloud.contract.verifier.builder;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.cloud.contract.spec.internal.MatchingStrategy;
import org.springframework.cloud.contract.spec.internal.QueryParameter;
import org.springframework.cloud.contract.spec.internal.Request;
@@ -54,8 +58,19 @@ class MockMvcQueryParamsWhen implements When, MockMvcAcceptor, QueryParamsResolv
}
private void addQueryParameters(Url buildUrl) {
buildUrl.getQueryParameters().getParameters().stream()
.filter(this::allowedQueryParameter).forEach(this::addQueryParameter);
List<QueryParameter> queryParameters = buildUrl.getQueryParameters().getParameters().stream()
.filter(this::allowedQueryParameter)
.collect(Collectors.toList());
Iterator<QueryParameter> iterator = queryParameters.iterator();
while (iterator.hasNext()) {
QueryParameter parameter = iterator.next();
String text = addQueryParameter(parameter);
if (iterator.hasNext()) {
this.blockBuilder.addLine(text);
} else {
this.blockBuilder.addIndented(text);
}
}
}
private boolean allowedQueryParameter(Object o) {
@@ -68,10 +83,10 @@ class MockMvcQueryParamsWhen implements When, MockMvcAcceptor, QueryParamsResolv
return true;
}
private void addQueryParameter(QueryParameter queryParam) {
this.blockBuilder.addLine("." + QUERY_PARAM_METHOD + "("
private String addQueryParameter(QueryParameter queryParam) {
return "." + QUERY_PARAM_METHOD + "("
+ this.bodyParser.quotedLongText(queryParam.getName()) + ","
+ this.bodyParser.quotedLongText(resolveParamValue(queryParam)) + ")");
+ this.bodyParser.quotedLongText(resolveParamValue(queryParam)) + ")";
}
@Override

View File

@@ -1,151 +0,0 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import org.apache.commons.text.StringEscapeUtils
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.verifier.util.MapConverter
/**
* Representation of the request side to be used for response templating in
* the generated tests.
*
* @author Marcin Grzejszczak
* @since 1.1.0
*/
@CompileStatic
class TestSideRequestTemplateModel {
/**
* Request URL
*/
final String url
/**
* Map containing query parameters
*/
final Map<String, List<String>> query
/**
* List of path entries
*/
final Path path
/**
* Map containing request headers
*/
final Map<String, List<String>> headers
/**
* Request body as it would be sent to the controller
*/
final String body
/**
* Escaped request body that can be put into test
*/
final String escapedBody
private TestSideRequestTemplateModel(String url, Map<String, List<String>> query, Path path, Map<String, List<String>> headers, String body, String escapedBody) {
this.url = url
this.query = query
this.path = path
this.headers = headers
this.body = body
this.escapedBody = escapedBody
}
static TestSideRequestTemplateModel from(final Request request) {
String url = MapConverter.getTestSideValues(request.url ?: request.urlPath)
Path paths = new Path(buildPathsFromUrl(url))
Map<String, List<String>> query = (Map<String, List<String>>) (request.url ?: request.urlPath)
.queryParameters?.parameters?.groupBy { it.name }?.collectEntries {
[(it.key): it.value.collect { MapConverter.getTestSideValues(it) }]
}
String fullUrl = (query == null || query.isEmpty()) ? url :
url + "?" + query.collect { String name, List<String> values ->
return values.collect { "${name}=${it}"}.join("&") }.join("&")
Map<String, List<String>> headers = (Map<String, List<String>>) (request.headers?.entries?.groupBy {
it.name
}?.collectEntries {
List<Object> headerValues = []
for (Object value : it.value) {
headerValues.add(MapConverter.getTestSideValues(value))
}
[(it.key): headerValues]
})
String escapedBody = trimmedAndEscapedBody(request.body)
String body = getBodyAsRawJson(request.body)
return new TestSideRequestTemplateModel(fullUrl, query, paths, headers, body, escapedBody)
}
private static List<String> buildPathsFromUrl(String url) {
String fakeUrl = "https://foo.bar" + (url.startsWith("/") ? url : "/" + url)
List<String> paths = new URL(fakeUrl).path.split("/") as List<String>
if (!paths.isEmpty()) {
paths.remove(0)
}
return paths
}
private static String trimmedAndEscapedBody(Object body) {
String rawBody = getBodyAsRawJson(body)
return StringEscapeUtils.escapeJava(rawBody)
}
private static String getBodyAsRawJson(Object body) {
Object bodyValue = extractServerValueFromBody(body)
if (bodyValue instanceof GString || bodyValue instanceof String) {
return bodyValue.toString()
}
else if (bodyValue instanceof FromFileProperty) {
return null
}
return bodyValue != null ? new JsonOutput().toJson(bodyValue) : bodyValue
}
protected static Object extractServerValueFromBody(bodyValue) {
if (bodyValue instanceof GString) {
bodyValue = ContentUtils.
extractValue(bodyValue, { DslProperty dslProperty -> dslProperty.serverValue } as Closure)
}
else {
bodyValue = MapConverter.transformValues(bodyValue, {
it instanceof DslProperty ? it.serverValue : it
})
}
return bodyValue
}
}
@CompileStatic
class Path extends ArrayList<String> {
Path(List<String> list) {
this.addAll(list)
}
@Override
String toString() {
return "/" + this.join("/")
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import groovy.json.JsonOutput;
import groovy.lang.GString;
import groovy.transform.CompileStatic;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.QueryParameter;
import org.springframework.cloud.contract.spec.internal.QueryParameters;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Url;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
import org.springframework.cloud.contract.verifier.util.MapConverter;
/**
* Representation of the request side to be used for response templating in the generated
* tests.
*
* @author Marcin Grzejszczak
* @since 1.1.0
*/
public class TestSideRequestTemplateModel {
/**
* Request URL
*/
private final String url;
/**
* Map containing query parameters
*/
private final Map<String, List<Object>> query;
/**
* List of path entries
*/
private final Path path;
/**
* Map containing request headers
*/
private final Map<String, List<String>> headers;
/**
* Request body as it would be sent to the controller
*/
private final String body;
/**
* Escaped request body that can be put into test
*/
private final String escapedBody;
private TestSideRequestTemplateModel(String url, Map<String, List<Object>> query,
Path path, Map<String, List<String>> headers, String body,
String escapedBody) {
this.url = url;
this.query = query;
this.path = path;
this.headers = headers;
this.body = body;
this.escapedBody = escapedBody;
}
public String getUrl() {
return this.url;
}
public Map<String, List<Object>> getQuery() {
return this.query;
}
public Path getPath() {
return this.path;
}
public Map<String, List<String>> getHeaders() {
return this.headers;
}
public String getBody() {
return this.body;
}
public String getEscapedBody() {
return this.escapedBody;
}
public static TestSideRequestTemplateModel from(final Request request) {
Url urlPath = request.getUrl() != null ? request.getUrl() : request.getUrlPath();
String url = MapConverter.getTestSideValues(urlPath).toString();
Path paths = new Path(buildPathsFromUrl(url));
QueryParameters queryParameters = urlPath.getQueryParameters();
Map<String, List<Object>> query = query(queryParameters);
boolean queryParamsPresent = query == null || query.isEmpty();
String fullUrl = fullUrl(url, query, queryParamsPresent);
boolean headersEntriesPresent = request.getHeaders() != null
&& !request.getHeaders().getEntries().isEmpty();
Map<String, List<String>> headers = headers(request, headersEntriesPresent);
String escapedBody = trimmedAndEscapedBody(request.getBody());
String body = getBodyAsRawJson(request.getBody());
return new TestSideRequestTemplateModel(fullUrl, query, paths, headers, body,
escapedBody);
}
private static Map<String, List<String>> headers(Request request,
boolean headersEntriesPresent) {
if (!headersEntriesPresent) {
return new HashMap<>();
}
return request.getHeaders().getEntries().stream()
.collect(Collectors.groupingBy(Header::getName,
Collectors.mapping(
(Function<Object, String>) o -> MapConverter
.getTestSideValues(o).toString(),
Collectors.toList())));
}
private static String fullUrl(String url, Map<String, List<Object>> query,
boolean queryParamsPresent) {
if (queryParamsPresent) {
return url;
}
String joinedParams = query.entrySet().stream()
.map(entry -> entry.getValue().stream().map(s -> entry.getKey() + "=" + s)
.collect(Collectors.joining("&")))
.collect(Collectors.joining("&"));
return url + "?" + joinedParams;
}
private static Map<String, List<Object>> query(QueryParameters queryParameters) {
if (queryParameters == null) {
return new HashMap<>();
}
return queryParameters.getParameters().stream()
.collect(Collectors.groupingBy(QueryParameter::getName, Collectors
.mapping(MapConverter::getTestSideValues, Collectors.toList())));
}
private static List<String> buildPathsFromUrl(String url) {
String fakeUrl = "https://foo.bar" + (url.startsWith("/") ? url : "/" + url);
List<String> paths;
try {
paths = new LinkedList<>(
Arrays.asList(new URL(fakeUrl).getPath().split("/")));
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}
if (!paths.isEmpty()) {
paths.remove(0);
}
return paths;
}
private static String trimmedAndEscapedBody(Object body) {
String rawBody = getBodyAsRawJson(body);
return StringEscapeUtils.escapeJava(rawBody);
}
private static String getBodyAsRawJson(Object body) {
Object bodyValue = extractServerValueFromBody(body);
if (bodyValue instanceof GString || bodyValue instanceof String) {
return bodyValue.toString();
}
else if (bodyValue instanceof FromFileProperty) {
return null;
}
return bodyValue != null ? new JsonOutput().toJson(bodyValue) : null;
}
private static Object extractServerValueFromBody(Object bodyValue) {
if (bodyValue instanceof GString) {
bodyValue = ContentUtils.extractValue((GString) bodyValue,
ContentUtils.GET_TEST_SIDE_FUNCTION);
}
else {
bodyValue = MapConverter.transformValues(bodyValue,
ContentUtils.GET_TEST_SIDE);
}
return bodyValue;
}
}
@CompileStatic
class Path extends ArrayList<String> {
Path(List<String> list) {
this.addAll(list);
}
@Override
public String toString() {
return "/" + String.join("/", this);
}
}

View File

@@ -14,15 +14,17 @@
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder.handlebars
package org.springframework.cloud.contract.verifier.builder.handlebars;
import com.github.tomakehurst.wiremock.extension.responsetemplating.RequestTemplateModel
import groovy.transform.CompileStatic
import org.apache.commons.text.StringEscapeUtils
import wiremock.com.github.jknack.handlebars.Helper
import wiremock.com.github.jknack.handlebars.Options
import java.io.IOException;
import java.util.Map;
import org.springframework.cloud.contract.verifier.builder.TestSideRequestTemplateModel
import com.github.tomakehurst.wiremock.extension.responsetemplating.RequestTemplateModel;
import org.apache.commons.text.StringEscapeUtils;
import wiremock.com.github.jknack.handlebars.Helper;
import wiremock.com.github.jknack.handlebars.Options;
import org.springframework.cloud.contract.verifier.builder.TestSideRequestTemplateModel;
/**
* A Handlebars helper for the {@code escapejsonbody} helper function.
@@ -30,30 +32,31 @@ import org.springframework.cloud.contract.verifier.builder.TestSideRequestTempla
* @author Marcin Grzejszczak
* @since 1.1.0
*/
@CompileStatic
class HandlebarsEscapeHelper implements Helper<Map<String, Object>> {
public class HandlebarsEscapeHelper implements Helper<Map<String, Object>> {
public static final String NAME = "escapejsonbody"
public static final String REQUEST_MODEL_NAME = "request"
public static final String NAME = "escapejsonbody";
public static final String REQUEST_MODEL_NAME = "request";
@Override
Object apply(Map<String, Object> context, Options options) throws IOException {
Object model = context.get(REQUEST_MODEL_NAME)
public Object apply(Map<String, Object> context, Options options) throws IOException {
Object model = context.get(REQUEST_MODEL_NAME);
if (model instanceof TestSideRequestTemplateModel) {
return StringEscapeUtils.escapeJson(returnObjectForTest(model).toString())
return StringEscapeUtils.escapeJson(returnObjectForTest(model).toString());
}
else if (model instanceof RequestTemplateModel) {
return StringEscapeUtils.escapeJson(returnObjectForStub(model).toString())
return StringEscapeUtils.escapeJson(returnObjectForStub(model).toString());
}
throw new IllegalArgumentException("Unsupported model")
throw new IllegalArgumentException("Unsupported model");
}
private Object returnObjectForStub(Object model) {
return ((RequestTemplateModel) model).body
return ((RequestTemplateModel) model).getBody();
}
private Object returnObjectForTest(Object model) {
return ((TestSideRequestTemplateModel) model).escapedBody
return ((TestSideRequestTemplateModel) model).getEscapedBody();
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder.handlebars
import com.github.tomakehurst.wiremock.extension.responsetemplating.RequestTemplateModel
import com.github.tomakehurst.wiremock.extension.responsetemplating.helpers.WireMockHelpers
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import groovy.transform.CompileStatic
import wiremock.com.github.jknack.handlebars.Helper
import wiremock.com.github.jknack.handlebars.Options
import org.springframework.cloud.contract.verifier.builder.TestSideRequestTemplateModel
/**
* A Handlebars helper for the {@code jsonpath} helper function.
*
* @author Marcin Grzejszczak
* @since 1.1.0
*/
@CompileStatic
class HandlebarsJsonPathHelper implements Helper<Object> {
public static final String NAME = "jsonpath"
public static final String REQUEST_MODEL_NAME = "request"
@Override
Object apply(Object context, Options options) throws IOException {
if (context instanceof Map<String, Object>) {
// legacy
Map<String, Object> oldContext = (Map<String, Object>) context
String jsonPath = options.param(0)
Object model = oldContext.get(REQUEST_MODEL_NAME)
if (model instanceof TestSideRequestTemplateModel) {
return returnObjectForTest(model, jsonPath)
}
else if (model instanceof RequestTemplateModel) {
return returnObjectForStub(model, jsonPath)
}
throw new IllegalArgumentException("Unsupported model")
}
else if (context instanceof String) {
Object value = WireMockHelpers.jsonPath.apply(context, options)
if (testSideModel(options)) {
return processTestResponseValue(value)
}
return value
}
throw new IllegalArgumentException("Unsupported context")
}
private boolean testSideModel(Options options) {
Object model = options.context.model()
if (!(model instanceof Map)) {
return false
}
Map map = (Map) model
return map.values().any { it instanceof TestSideRequestTemplateModel }
}
private Object returnObjectForStub(Object model, String jsonPath) {
DocumentContext documentContext = JsonPath.
parse(((RequestTemplateModel) model).body)
return documentContext.read(jsonPath)
}
private Object returnObjectForTest(TestSideRequestTemplateModel model, String jsonPath) {
String body = removeSurroundingQuotes(model.escapedBody).replace('\\"', '"')
DocumentContext documentContext = JsonPath.parse(body)
Object value = documentContext.read(jsonPath)
return processTestResponseValue(value)
}
private Object processTestResponseValue(Object value) {
if (value instanceof Long) {
return String.valueOf(value) + "L"
}
return value
}
private String removeSurroundingQuotes(String body) {
if (body.startsWith('"') && body.endsWith('"')) {
return body.substring(1, body.length() - 1)
}
return body
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder.handlebars;
import java.io.IOException;
import java.util.Map;
import com.github.tomakehurst.wiremock.extension.responsetemplating.RequestTemplateModel;
import com.github.tomakehurst.wiremock.extension.responsetemplating.helpers.WireMockHelpers;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import wiremock.com.github.jknack.handlebars.Helper;
import wiremock.com.github.jknack.handlebars.Options;
import org.springframework.cloud.contract.verifier.builder.TestSideRequestTemplateModel;
/**
* A Handlebars helper for the {@code jsonpath} helper function.
*
* @author Marcin Grzejszczak
* @since 1.1.0
*/
public class HandlebarsJsonPathHelper implements Helper<Object> {
public static final String NAME = "jsonpath";
public static final String REQUEST_MODEL_NAME = "request";
@Override
public Object apply(Object context, Options options) throws IOException {
if (context instanceof Map) {
// legacy
Map<String, Object> oldContext = (Map<String, Object>) context;
String jsonPath = options.param(0);
Object model = oldContext.get(REQUEST_MODEL_NAME);
if (model instanceof TestSideRequestTemplateModel) {
return returnObjectForTest((TestSideRequestTemplateModel) model,
jsonPath);
}
else if (model instanceof RequestTemplateModel) {
return returnObjectForStub(model, jsonPath);
}
throw new IllegalArgumentException("Unsupported model");
}
else if (context instanceof String) {
Object value = WireMockHelpers.jsonPath.apply(context, options);
if (testSideModel(options)) {
return processTestResponseValue(value);
}
return value;
}
throw new IllegalArgumentException("Unsupported context");
}
private boolean testSideModel(Options options) {
Object model = options.context.model();
if (!(model instanceof Map)) {
return false;
}
Map map = (Map) model;
return map.values().stream()
.anyMatch(o -> o instanceof TestSideRequestTemplateModel);
}
private Object returnObjectForStub(Object model, String jsonPath) {
DocumentContext documentContext = JsonPath
.parse(((RequestTemplateModel) model).getBody());
return documentContext.read(jsonPath);
}
private Object returnObjectForTest(TestSideRequestTemplateModel model,
String jsonPath) {
String body = removeSurroundingQuotes(model.getEscapedBody()).replace("\\\"",
"\"");
DocumentContext documentContext = JsonPath.parse(body);
Object value = documentContext.read(jsonPath);
return processTestResponseValue(value);
}
private Object processTestResponseValue(Object value) {
if (value instanceof Long) {
return (long) value + "L";
}
return value;
}
private String removeSurroundingQuotes(String body) {
if (body.startsWith("\"") && body.endsWith("\"")) {
return body.substring(1, body.length() - 1);
}
return body;
}
}

View File

@@ -71,6 +71,20 @@ class ContentUtils {
it instanceof DslProperty ? it.serverValue : it
}
public static final Function GET_STUB_SIDE_FUNCTION = new Function() {
@Override
Object apply(Object it) {
return it instanceof DslProperty ? it.clientValue : it
}
}
public static final Function GET_TEST_SIDE_FUNCTION = new Function() {
@Override
Object apply(Object it) {
return it instanceof DslProperty ? it.serverValue : it
}
}
private static final Pattern TEMPORARY_PATTERN_HOLDER = Pattern.
compile('.*REGEXP>>(.*)<<.*')
private static final Pattern TEMPORARY_EXECUTION_PATTERN_HOLDER = Pattern.
@@ -238,6 +252,10 @@ class ContentUtils {
)
}
static Object extractValue(GString bodyAsValue, Function valueProvider) {
return extractValue(bodyAsValue, UNKNOWN, { valueProvider.apply(it) })
}
static Object extractValue(GString bodyAsValue, Closure valueProvider) {
return extractValue(bodyAsValue, UNKNOWN, valueProvider)
}