Merge branch 'master' into 2.0.x
This commit is contained in:
@@ -132,3 +132,11 @@ The flow for setting the generated tests package name will look like this:
|
||||
|
||||
Related to
|
||||
https://github.com/spring-cloud/spring-cloud-contract/issues/260[issue 260].
|
||||
|
||||
==== New methods in TemplateProcessor
|
||||
|
||||
In order to add support for `fromRequest.path` some methods had to be added to the
|
||||
`TemplateProcessor` interface.
|
||||
|
||||
Related to
|
||||
https://github.com/spring-cloud/spring-cloud-contract/issues/388[issue 388].
|
||||
4
pom.xml
4
pom.xml
@@ -129,8 +129,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.6</version>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
||||
@@ -23,7 +23,7 @@ interface ContractTemplate {
|
||||
String closingTemplate()
|
||||
|
||||
/**
|
||||
* Returns the template for retrieving a URL from request
|
||||
* Returns the template for retrieving a URL path and query from request
|
||||
*/
|
||||
String url()
|
||||
|
||||
@@ -40,6 +40,17 @@ interface ContractTemplate {
|
||||
*/
|
||||
String query(String key, int index)
|
||||
|
||||
/**
|
||||
* Returns the template for retrieving a URL path
|
||||
*/
|
||||
String path()
|
||||
|
||||
/**
|
||||
* Returns the template for retrieving nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}
|
||||
* @param index
|
||||
*/
|
||||
String path(int index)
|
||||
|
||||
/**
|
||||
* Returns the template for retrieving the first value of a request header e.g. {{{ request.headers.X-Request-Id }}}
|
||||
* @param key
|
||||
|
||||
@@ -2,13 +2,13 @@ package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.springframework.cloud.contract.spec.ContractTemplate
|
||||
|
||||
/**
|
||||
* Helper class to reference the request body parameters
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
@CompileStatic
|
||||
class FromRequest {
|
||||
|
||||
@@ -46,6 +46,21 @@ class FromRequest {
|
||||
return new DslProperty(template.query(key, index))
|
||||
}
|
||||
|
||||
/**
|
||||
* URL path
|
||||
*/
|
||||
DslProperty path() {
|
||||
return new DslProperty(template.path())
|
||||
}
|
||||
|
||||
/**
|
||||
* nth value of a URL path (zero indexed) e.g. {{{ request.path.[2] }}}
|
||||
* @param index
|
||||
*/
|
||||
DslProperty path(int index) {
|
||||
return new DslProperty(template.path(index))
|
||||
}
|
||||
|
||||
/**
|
||||
* First value of a request header e.g. request.headers.X-Request-Id
|
||||
* @param key
|
||||
|
||||
@@ -38,6 +38,16 @@ class HandlebarsContractTemplate implements ContractTemplate {
|
||||
return wrapped("request.query.${key}.[${index}]")
|
||||
}
|
||||
|
||||
@Override
|
||||
String path() {
|
||||
return wrapped("request.path")
|
||||
}
|
||||
|
||||
@Override
|
||||
String path(int index) {
|
||||
return wrapped("request.path.[${index}]")
|
||||
}
|
||||
|
||||
@Override
|
||||
String header(String key) {
|
||||
return header(key, 0)
|
||||
|
||||
@@ -27,7 +27,7 @@ import repackaged.nl.flotsam.xeger.Xeger
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJava
|
||||
|
||||
/**
|
||||
* Converts WireMock stubs into the DSL format
|
||||
|
||||
@@ -20,9 +20,8 @@ import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import wiremock.com.google.common.collect.ListMultimap
|
||||
import groovy.transform.PackageScope
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import wiremock.com.google.common.collect.ListMultimap
|
||||
|
||||
import org.springframework.cloud.contract.spec.ContractVerifierException
|
||||
import org.springframework.cloud.contract.verifier.builder.JavaTestGenerator
|
||||
@@ -31,13 +30,13 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig
|
||||
import org.springframework.cloud.contract.verifier.file.ContractFileScanner
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader
|
||||
import org.springframework.util.StringUtils
|
||||
|
||||
import static org.springframework.cloud.contract.verifier.util.NamesUtil.afterLast
|
||||
import static org.springframework.cloud.contract.verifier.util.NamesUtil.beforeLast
|
||||
import static org.springframework.cloud.contract.verifier.util.NamesUtil.convertIllegalPackageChars
|
||||
import static org.springframework.cloud.contract.verifier.util.NamesUtil.directoryToPackage
|
||||
import static org.springframework.cloud.contract.verifier.util.NamesUtil.toLastDot
|
||||
|
||||
/**
|
||||
* @author Jakub Kubrynski, codearte.io
|
||||
*/
|
||||
@@ -104,7 +103,7 @@ class TestGenerator {
|
||||
|
||||
private String relativizeContractPath(Map.Entry<Path, Collection<Path>> entry) {
|
||||
Path relativePath = configProperties.contractsDslDir.toPath().relativize(entry.getKey())
|
||||
if (StringUtils.isBlank(relativePath.toString())) {
|
||||
if (StringUtils.isEmpty(relativePath.toString())) {
|
||||
return DEFAULT_CLASS_PREFIX
|
||||
}
|
||||
return relativePath.toString()
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.jayway.jsonpath.PathNotFoundException
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TypeChecked
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import org.apache.commons.logging.Log
|
||||
import org.apache.commons.logging.LogFactory
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJava
|
||||
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getGroovyMultipartFileParameterContent
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,8 @@ package org.springframework.cloud.contract.verifier.builder
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.Immutable
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Request
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
@@ -29,6 +30,11 @@ class TestSideRequestTemplateModel {
|
||||
*/
|
||||
final Map<String, List<String>> query
|
||||
|
||||
/**
|
||||
* List of path entries
|
||||
*/
|
||||
final Path path
|
||||
|
||||
/**
|
||||
* Map containing request headers
|
||||
*/
|
||||
@@ -46,10 +52,14 @@ class TestSideRequestTemplateModel {
|
||||
|
||||
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 {
|
||||
@@ -57,7 +67,14 @@ class TestSideRequestTemplateModel {
|
||||
})
|
||||
String body = trimmedAndEscapedBody(request.body)
|
||||
String rawBody = getBodyAsRawJson(request.body)
|
||||
return new TestSideRequestTemplateModel(url, query, headers, body, rawBody)
|
||||
return new TestSideRequestTemplateModel(fullUrl, query, paths, headers, body, rawBody)
|
||||
}
|
||||
|
||||
private static List<String> buildPathsFromUrl(String url) {
|
||||
String fakeUrl = "http://foo.bar" + (url.startsWith("/") ? url : "/" + url)
|
||||
List<String> paths = new URL(fakeUrl).path.split("/") as List<String>
|
||||
paths.remove(0)
|
||||
return paths
|
||||
}
|
||||
|
||||
private static String trimmedAndEscapedBody(Object body) {
|
||||
@@ -81,3 +98,16 @@ class TestSideRequestTemplateModel {
|
||||
return bodyValue
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
class Path extends ArrayList<String> {
|
||||
|
||||
Path(List<String> list) {
|
||||
this.addAll(list)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return "/" + this.join("/")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import wiremock.com.github.jknack.handlebars.Helper
|
||||
import wiremock.com.github.jknack.handlebars.Options
|
||||
import com.github.tomakehurst.wiremock.extension.responsetemplating.RequestTemplateModel
|
||||
import groovy.transform.CompileStatic
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import org.springframework.cloud.contract.verifier.builder.TestSideRequestTemplateModel
|
||||
/**
|
||||
* A Handlebars helper for the {@code escapejsonbody} helper function.
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.Request
|
||||
import org.springframework.cloud.contract.spec.internal.Response
|
||||
import org.springframework.cloud.contract.verifier.util.ContentType
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromContent
|
||||
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromHeader
|
||||
@@ -51,7 +52,7 @@ class WireMockResponseStubStrategy extends BaseWireMockStubStrategy {
|
||||
return null
|
||||
}
|
||||
ResponseDefinitionBuilder builder = new ResponseDefinitionBuilder()
|
||||
.withStatus(response.status.clientValue as Integer)
|
||||
.withStatus(MapConverter.getStubSideValues(response.status) as Integer)
|
||||
appendHeaders(builder)
|
||||
appendBody(builder)
|
||||
appendResponseDelayTime(builder)
|
||||
@@ -62,7 +63,7 @@ class WireMockResponseStubStrategy extends BaseWireMockStubStrategy {
|
||||
private void appendHeaders(ResponseDefinitionBuilder builder) {
|
||||
if (response.headers) {
|
||||
builder.withHeaders(new HttpHeaders(response.headers.entries?.collect {
|
||||
new HttpHeader(it.name, it.clientValue.toString())
|
||||
new HttpHeader(it.name, MapConverter.getStubSideValues(it.clientValue).toString())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import wiremock.com.google.common.collect.ArrayListMultimap
|
||||
import wiremock.com.google.common.collect.ListMultimap
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Slf4j
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
|
||||
@@ -43,6 +42,10 @@ import java.util.regex.Pattern
|
||||
@Slf4j
|
||||
class ContractFileScanner {
|
||||
|
||||
private static final String OS_NAME = System.getProperty("os.name");
|
||||
private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
|
||||
protected static final boolean IS_OS_WINDOWS = getOSMatchesName(OS_NAME_WINDOWS_PREFIX);
|
||||
|
||||
private static final String MATCH_PREFIX = "glob:"
|
||||
private static final Pattern SCENARIO_STEP_FILENAME_PATTERN = Pattern.compile("[0-9]+_.*")
|
||||
private final File baseDir
|
||||
@@ -62,7 +65,7 @@ class ContractFileScanner {
|
||||
return patterns.collect({
|
||||
String syntaxAndPattern = MATCH_PREFIX + '**' + File.separator + it
|
||||
// FIXME: This looks strange, need to be checked on windows
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
if (IS_OS_WINDOWS) {
|
||||
syntaxAndPattern = syntaxAndPattern.replace("\\", "\\\\")
|
||||
}
|
||||
fileSystem.getPathMatcher(syntaxAndPattern)
|
||||
@@ -179,4 +182,31 @@ class ContractFileScanner {
|
||||
}
|
||||
return path.substring(extIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if the operating system matches.
|
||||
*
|
||||
* @param osNamePrefix the prefix for the os name
|
||||
* @return true if matches, or false if not or can't determine
|
||||
*/
|
||||
private static boolean getOSMatchesName(final String osNamePrefix) {
|
||||
return isOSNameMatch(OS_NAME, osNamePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if the operating system matches.
|
||||
* <p>
|
||||
* This method is package private instead of private to support unit test invocation.
|
||||
* </p>
|
||||
*
|
||||
* @param osName the actual OS name
|
||||
* @param osNamePrefix the prefix for the expected OS name
|
||||
* @return true if matches, or false if not or can't determine
|
||||
*/
|
||||
private static boolean isOSNameMatch(final String osName, final String osNamePrefix) {
|
||||
if (osName == null) {
|
||||
return false;
|
||||
}
|
||||
return osName.startsWith(osNamePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.springframework.cloud.contract.verifier.template
|
||||
|
||||
import org.springframework.cloud.contract.spec.internal.Request
|
||||
|
||||
/**
|
||||
* Contract for conversion of templated responses.
|
||||
*
|
||||
|
||||
@@ -32,9 +32,9 @@ import org.springframework.cloud.contract.spec.internal.OptionalProperty
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJson
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeXml11
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJava
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJson
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeXml11
|
||||
|
||||
/**
|
||||
* A utility class that can operate on a message body basing on the provided Content Type.
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util;
|
||||
|
||||
import com.toomuchcoding.jsonassert.JsonVerifiable;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;
|
||||
import com.toomuchcoding.jsonassert.JsonVerifiable;
|
||||
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJava;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link MethodBufferingJsonVerifiable} that contains a list
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.cloud.contract.spec.util.RegexpUtils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeJson
|
||||
import static org.apache.commons.text.StringEscapeUtils.escapeJson
|
||||
import static ContentType.*
|
||||
import static ContentUtils.extractValue
|
||||
|
||||
|
||||
@@ -2291,6 +2291,8 @@ DocumentContext parsedJson = JsonPath.parse(json);
|
||||
}
|
||||
body(
|
||||
url: fromRequest().url(),
|
||||
path: fromRequest().path(),
|
||||
pathIndex: fromRequest().path(1),
|
||||
param: fromRequest().query("foo"),
|
||||
paramIndex: fromRequest().query("foo", 1),
|
||||
authorization: fromRequest().header("Authorization"),
|
||||
@@ -2312,7 +2314,9 @@ DocumentContext parsedJson = JsonPath.parse(json);
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
|
||||
then:
|
||||
!test.contains('''DslProperty''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['url']").isEqualTo("/api/v1/xxxx")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['url']").isEqualTo("/api/v1/xxxx?foo=bar&foo=bar2")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['path']").isEqualTo("/api/v1/xxxx")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['pathIndex']").isEqualTo("v1")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['fullBody']").isEqualTo("{\\"foo\\":\\"bar\\",\\"baz\\":5}")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['paramIndex']").isEqualTo("bar2")''')
|
||||
test.contains('''assertThatJson(parsedJson).field("['responseFoo']").isEqualTo("bar")''')
|
||||
|
||||
@@ -1755,10 +1755,12 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
|
||||
response {
|
||||
status 200
|
||||
headers {
|
||||
header(authorization(), fromRequest().header(authorization()))
|
||||
header(authorization(), "${fromRequest().header(authorization())};foo")
|
||||
}
|
||||
body(
|
||||
url: fromRequest().url(),
|
||||
path: fromRequest().path(),
|
||||
pathIndex: fromRequest().path(1),
|
||||
param: fromRequest().query("foo"),
|
||||
paramIndex: fromRequest().query("foo", 1),
|
||||
authorization: fromRequest().header("Authorization"),
|
||||
@@ -1796,9 +1798,9 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
|
||||
},
|
||||
"response" : {
|
||||
"status" : 200,
|
||||
"body" : "{\\"url\\":\\"{{{request.url}}}\\",\\"param\\":\\"{{{request.query.foo.[0]}}}\\",\\"paramIndex\\":\\"{{{request.query.foo.[1]}}}\\",\\"authorization\\":\\"{{{request.headers.Authorization.[0]}}}\\",\\"authorization2\\":\\"{{{request.headers.Authorization.[1]}}}\\",\\"fullBody\\":\\"{{{escapejsonbody}}}\\",\\"responseFoo\\":\\"{{{jsonpath this '$.foo'}}}\\",\\"responseBaz\\":{{{jsonpath this '$.baz'}}} ,\\"responseBaz2\\":\\"Bla bla {{{jsonpath this '$.foo'}}} bla bla\\"}",
|
||||
"body" : "{\\"url\\":\\"{{{request.url}}}\\",\\"path\\":\\"{{{request.path}}}\\",\\"pathIndex\\":\\"{{{request.path.[1]}}}\\",\\"param\\":\\"{{{request.query.foo.[0]}}}\\",\\"paramIndex\\":\\"{{{request.query.foo.[1]}}}\\",\\"authorization\\":\\"{{{request.headers.Authorization.[0]}}}\\",\\"authorization2\\":\\"{{{request.headers.Authorization.[1]}}}\\",\\"fullBody\\":\\"{{{escapejsonbody}}}\\",\\"responseFoo\\":\\"{{{jsonpath this '$.foo'}}}\\",\\"responseBaz\\":{{{jsonpath this '$.baz'}}} ,\\"responseBaz2\\":\\"Bla bla {{{jsonpath this '$.foo'}}} bla bla\\"}",
|
||||
"headers" : {
|
||||
"Authorization" : "{{{request.headers.Authorization.[0]}}}"
|
||||
"Authorization" : "{{{request.headers.Authorization.[0]}}};foo"
|
||||
},
|
||||
"transformers" : [ "response-template" ]
|
||||
}
|
||||
@@ -1813,7 +1815,7 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
|
||||
server.addStubMapping(WireMockStubMapping.buildFrom(json))
|
||||
then:
|
||||
ResponseEntity<String> entity = call(port)
|
||||
entity.headers.find { it.key == "Authorization" && it.value.contains("secret") }
|
||||
entity.headers.find { it.key == "Authorization" && it.value.contains("secret;foo") }
|
||||
and:
|
||||
AssertionUtil.assertThatJsonsAreEqual(('''
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user