Added support for fromRequest.path

without this change you can't referrence path of the request in the response
with this change that functionality is added

also Removed lang3 dependency and changed it to commons-text

fixes #388
This commit is contained in:
Marcin Grzejszczak
2017-09-01 17:21:03 +02:00
parent 4c08408e66
commit 5f8fd5058c
19 changed files with 136 additions and 26 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

@@ -130,8 +130,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

@@ -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)
@@ -177,4 +180,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

@@ -1759,6 +1759,8 @@ class WireMockGroovyDslSpec extends Specification implements WireMockStubVerifie
}
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,7 +1798,7 @@ 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]}}};foo"
},