Merge branch 'master' into 2.0.x

This commit is contained in:
Marcin Grzejszczak
2017-09-01 17:22:36 +02:00
20 changed files with 142 additions and 31 deletions

View File

@@ -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].

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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
/**

View File

@@ -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("/")
}
}

View File

@@ -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.

View File

@@ -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())
}))
}
}

View File

@@ -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);
}
}

View File

@@ -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.
*

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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")''')

View File

@@ -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(('''
{