Rewrite spring-cloud-contract-verifier module: ContractToYaml and Reg… (#1534)

* Rewrite spring-cloud-contract-verifier module: ContractToYaml and RegexpBuilders

* Rewrite MapConverter and SpringCloudContractAssertions

* Fix build failure

* Rewrite YamlToContract and BaseWireMockStubStrategy

* Fix problems avec merge

* Rewrite WireMockRequestStubStrategy

* Rewrite ContractVerifierDslConverter

* Rewrite ContractFileScanner

* Rewrite ContractFileScannerBuilder
This commit is contained in:
Stessy Delcroix
2021-04-13 09:12:22 +02:00
committed by GitHub
parent 789b34cdb7
commit a5b3f6b919
33 changed files with 3526 additions and 3476 deletions

View File

@@ -36,7 +36,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.file.ContractFileScanner;
import org.springframework.cloud.contract.verifier.file.ContractFileScannerBuilder;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
import org.springframework.cloud.contract.verifier.util.NamesUtil;
import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter;
@@ -80,7 +79,7 @@ public class RecursiveFilesConverter {
}
public void processFiles() {
ContractFileScanner scanner = new ContractFileScannerBuilder().baseDir(contractsDslDir)
ContractFileScanner scanner = ContractFileScanner.builder().baseDir(contractsDslDir)
.excluded(new HashSet<>(excludedFiles)).ignored(new HashSet<>()).included(new HashSet<>())
.includeMatcher(includedContracts).build();
MultiValueMap<Path, ContractMetadata> contracts = scanner.findContractsRecursively();

View File

@@ -63,6 +63,10 @@
<artifactId>jakarta.jms-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>

View File

@@ -1,19 +0,0 @@
package org.springframework.cloud.contract.verifier.assertion
import groovy.transform.CompileStatic
import org.assertj.core.api.Assertions
@CompileStatic
public class SpringCloudContractAssertions extends Assertions {
/**
* Creates a new instance of <code>{@link CollectionAssert}</code>.
* @param <ELEMENT> type to assert
* @param actual the actual value.
* @return the created assertion object.
*/
public static <ELEMENT> CollectionAssert<ELEMENT> assertThat(
Iterable<? extends ELEMENT> actual) {
return new CollectionAssert<>(actual);
}
}

View File

@@ -1,461 +0,0 @@
/*
* Copyright 2013-2020 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.converter
import java.util.regex.Pattern
import groovy.transform.PackageScope
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.Cookies
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.spec.internal.Headers
import org.springframework.cloud.contract.spec.internal.MatchingStrategy
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.Multipart
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Url
import org.springframework.cloud.contract.verifier.converter.YamlContract.RegexType
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
import org.springframework.cloud.contract.verifier.util.MapConverter
import static org.springframework.cloud.contract.verifier.util.ContentType.XML
import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
*/
@PackageScope
class ContractsToYaml {
List<YamlContract> convertTo(Collection<Contract> contracts) {
return contracts.collect { Contract contract ->
YamlContract yamlContract = new YamlContract()
if (contract == null) {
return yamlContract
}
yamlContract.name = contract.name
yamlContract.ignored = contract.ignored
yamlContract.inProgress = contract.inProgress
yamlContract.description = contract.description
yamlContract.label = contract.label
yamlContract.metadata = contract.metadata
request(contract, yamlContract)
response(yamlContract, contract)
input(contract, yamlContract)
output(contract, yamlContract)
return yamlContract
}
}
protected void output(Contract contract, YamlContract yamlContract) {
if (!contract.outputMessage) {
return
}
ContentType contentType = evaluateClientSideContentType(contract.response?.headers,
contract.response?.body)
yamlContract.outputMessage = new YamlContract.OutputMessage()
yamlContract.outputMessage.sentTo = MapConverter.
getStubSideValues(contract.outputMessage.sentTo)
yamlContract.outputMessage.headers = (contract.outputMessage?.headers as Headers)?.
asStubSideMap()
yamlContract.outputMessage.body = MapConverter.getStubSideValues(
contract.outputMessage?.body)
contract.outputMessage?.bodyMatchers?.matchers()?.each { BodyMatcher matcher ->
yamlContract.outputMessage.matchers.body << new YamlContract.BodyTestMatcher(
path: matcher.path(),
type: testMatcherType(matcher.matchingType()),
value: matcher.value()?.toString(),
minOccurrence: matcher.minTypeOccurrence(),
maxOccurrence: matcher.maxTypeOccurrence()
)
}
if (XML != contentType) {
setOutputBodyMatchers(contract.outputMessage?.body,
yamlContract.outputMessage.matchers.body)
}
setOutputHeadersMatchers(contract.outputMessage?.headers,
yamlContract.outputMessage.matchers.headers)
}
protected void input(Contract contract, YamlContract yamlContract) {
if (!contract.input) {
return
}
ContentType contentType = evaluateClientSideContentType(contract.input?.messageHeaders,
contract.input?.messageBody)
yamlContract.input = new YamlContract.Input()
yamlContract.input.assertThat = MapConverter.
getTestSideValues(contract.input?.assertThat?.toString())
yamlContract.input.triggeredBy = MapConverter.
getTestSideValues(contract.input?.triggeredBy?.toString())
yamlContract.input.messageHeaders = (contract.input?.messageHeaders as Headers)?.
asTestSideMap()
yamlContract.input.messageBody = MapConverter.
getTestSideValues(contract.input?.messageBody)
yamlContract.input.messageFrom = MapConverter.
getTestSideValues(contract.input?.messageFrom)
contract.input?.bodyMatchers?.matchers()?.each { BodyMatcher matcher ->
yamlContract.input.matchers.body << new YamlContract.BodyStubMatcher(
path: matcher.path(),
type: stubMatcherType(matcher.matchingType()),
value: matcher.value()?.toString()
)
}
if (XML != contentType) {
setInputBodyMatchers(contract.input?.messageBody, yamlContract.input.matchers.body)
}
setInputHeadersMatchers(contract.input?.messageHeaders as Headers, yamlContract.input.matchers.headers)
}
protected void request(Contract contract, YamlContract yamlContract) {
if (!contract.request) {
return
}
ContentType requestContentType = evaluateClientSideContentType(contract.request.headers,
contract.request.body)
yamlContract.request = new YamlContract.Request()
yamlContract.request.with { YamlContract.Request request ->
request.method = contract.request?.method?.serverValue
request.url = contract.request?.url?.serverValue
request.urlPath = contract.request?.urlPath?.serverValue
request.matchers = new YamlContract.StubMatchers()
Url requestUrl = contract.request.url ?: contract.request.urlPath
if (requestUrl.queryParameters != null) {
request.queryParameters = requestUrl.queryParameters
.parameters.collectEntries {
def testSide = MapConverter.getTestSideValuesForNonBody(it)
def stubSide = it.clientValue
if (stubSide instanceof RegexProperty || stubSide instanceof Pattern) {
request.matchers.queryParameters.add(new YamlContract.QueryParameterMatcher(key: it.name, type: YamlContract.MatchingType.matching, value: new RegexProperty(stubSide).pattern()))
}
else if (stubSide instanceof MatchingStrategy) {
request.matchers.queryParameters.add(new YamlContract.QueryParameterMatcher(key: it.name, type: YamlContract.MatchingType.from(stubSide.getType().name), value: MapConverter.getStubSideValuesForNonBody(stubSide)))
}
return [(it.name): testSide]
}
}
request.headers = (contract.request?.headers as Headers)?.asMap {
String headerName, DslProperty prop ->
def testSideValue = MapConverter.getTestSideValues(prop)
if (testSideValue instanceof ExecutionProperty) {
return MapConverter.getStubSideValuesForNonBody(prop).toString()
}
return testSideValue.toString()
}
request.cookies = (contract.request?.cookies as Cookies)?.asTestSideMap()
Object body = contract.request?.body?.serverValue
if (body instanceof FromFileProperty) {
if (body.isByte()) {
request.bodyFromFileAsBytes = body.fileName()
}
if (body.isString()) {
request.bodyFromFile = body.fileName()
}
}
else {
request.body = MapConverter.getTestSideValues(contract.request?.body)
}
Multipart multipart = contract.request.multipart
if (multipart) {
request.multipart = new YamlContract.Multipart()
Map<String, Object> map = (Map<String, Object>) MapConverter.
getTestSideValues(multipart)
map.each { String key, Object value ->
if (value instanceof NamedProperty) {
Object fileName = value.name?.serverValue
Object contentType = value.contentType?.serverValue
Object fileContent = value.value?.serverValue
request.multipart.named << new YamlContract.Named(paramName: key,
fileName: fileName instanceof String ? value.name?.serverValue as String : null,
fileContent: fileContent instanceof String ? fileContent as String : null,
fileContentAsBytes: fileContent instanceof FromFileProperty ? fileContent.asBytes().toString() : null,
fileContentFromFileAsBytes: resolveFileNameAsBytes(fileContent),
contentType: contentType instanceof String ? contentType as String : null,
fileNameCommand: fileName instanceof ExecutionProperty ? fileName.toString() : null,
fileContentCommand: fileContent instanceof ExecutionProperty ? fileContent.toString() : null,
contentTypeCommand: contentType instanceof ExecutionProperty ? contentType.toString() : null)
}
else {
request.multipart.params.put(key, value != null ? value.toString() : null)
}
}
}
contract.request?.bodyMatchers?.matchers()?.each { BodyMatcher matcher ->
request.matchers.body << new YamlContract.BodyStubMatcher(
path: matcher.path(),
type: stubMatcherType(matcher.matchingType()),
value: matcher.value()?.toString(),
minOccurrence: matcher.minTypeOccurrence(),
maxOccurrence: matcher.maxTypeOccurrence(),
)
}
Object url = contract.request.url?.clientValue
request.matchers.url = url instanceof RegexProperty ?
new YamlContract.KeyValueMatcher(regex: url.pattern()) :
url instanceof ExecutionProperty ?
new YamlContract.KeyValueMatcher(command: url.toString()) : null
Object urlPath = contract.request.urlPath?.clientValue
request.matchers.url = urlPath instanceof RegexProperty ?
new YamlContract.KeyValueMatcher(regex: urlPath.pattern()) :
urlPath instanceof ExecutionProperty ?
new YamlContract.KeyValueMatcher(command: urlPath.toString()) : null
if (multipart) {
request.matchers.multipart = new YamlContract.MultipartStubMatcher()
Map<String, Object> map = (Map<String, Object>) MapConverter.
getStubSideValues(multipart)
map.each { String key, Object value ->
if (value instanceof NamedProperty) {
Object fileName = value.name?.clientValue
Object fileContent = value.value?.clientValue
Object contentType = value.contentType?.clientValue
if (fileName instanceof RegexProperty ||
fileContent instanceof RegexProperty ||
contentType instanceof RegexProperty) {
request.matchers.multipart.named << new YamlContract.MultipartNamedStubMatcher(
paramName: key,
fileName: valueMatcher(fileName),
fileContent: valueMatcher(fileContent),
contentType: valueMatcher(contentType),
)
}
}
else if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
request.matchers.multipart.params.
add(new YamlContract.KeyValueMatcher(
key: key,
regex: property.pattern(),
regexType: regexType(property.clazz())
))
}
}
}
// TODO: Cookie matchers - including absent
if (XML != requestContentType) {
setInputBodyMatchers(contract.request?.body, request.matchers.body)
}
setInputHeadersMatchers(contract.request?.headers as Headers, yamlContract.request.matchers.headers)
}
}
protected String resolveFileNameAsBytes(Object value) {
if (!(value instanceof FromFileProperty)) {
return null
}
FromFileProperty property = (FromFileProperty) value
return property.fileName()
}
protected YamlContract.ValueMatcher valueMatcher(Object o) {
return o instanceof RegexProperty ? new YamlContract.ValueMatcher(regex: o.
pattern()) : null
}
protected void setInputBodyMatchers(DslProperty body, List<YamlContract.BodyStubMatcher> bodyMatchers) {
def testSideValues = MapConverter.getTestSideValues(body)
JsonPaths paths = new JsonToJsonPathsConverter().
transformToJsonPathWithStubsSideValues(body)
paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking())
bodyMatchers << new YamlContract.BodyStubMatcher(
path: it.keyBeforeChecking(),
type: YamlContract.StubMatcherType.by_regex,
value: (it.valueBeforeChecking() as Pattern).pattern(),
regexType: regexType(element)
)
}
}
protected RegexType regexType(Object from) {
return regexType(from.class)
}
protected RegexType regexType(Class clazz) {
switch (clazz) {
case Boolean:
return RegexType.as_boolean
case Long:
return RegexType.as_long
case Short:
return RegexType.as_short
case Integer:
return RegexType.as_integer
case Float:
return RegexType.as_float
case Double:
return RegexType.as_double
default:
return RegexType.as_string
}
}
protected void setOutputBodyMatchers(DslProperty body,
List<YamlContract.BodyTestMatcher> bodyMatchers) {
def testSideValues = MapConverter.getTestSideValues(body)
JsonPaths paths = new JsonToJsonPathsConverter().
transformToJsonPathWithTestsSideValues(body)
paths?.findAll { it.valueBeforeChecking() instanceof Pattern }?.each {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, it.keyBeforeChecking())
bodyMatchers << new YamlContract.BodyTestMatcher(
path: it.keyBeforeChecking(),
type: YamlContract.TestMatcherType.by_regex,
value: (it.valueBeforeChecking() as Pattern).pattern(),
regexType: regexType(element)
)
}
if (body?.serverValue instanceof Pattern) {
bodyMatchers << new YamlContract.BodyTestMatcher(
type: YamlContract.TestMatcherType.by_regex,
value: ((Pattern) body.serverValue).pattern()
)
}
}
protected void response(YamlContract yamlContract, Contract contract) {
if (!contract.response) {
return
}
ContentType contentType = evaluateClientSideContentType(contract.response?.headers,
contract.response?.body)
yamlContract.response = new YamlContract.Response()
yamlContract.response.with { YamlContract.Response response ->
response.async = contract.response.async
response.fixedDelayMilliseconds = contract.response?.delay?.clientValue as Integer
response.status = contract.response?.status?.clientValue as Integer
response.headers = (contract.response?.headers as Headers)?.asMap {
String headerName, DslProperty prop ->
MapConverter.getStubSideValues(prop).toString()
}
response.cookies = (contract.response?.cookies as Cookies)?.asStubSideMap()
Object body = contract.response?.body?.clientValue
if (body instanceof FromFileProperty) {
if (body.isByte()) {
response.bodyFromFileAsBytes = body.fileName()
}
if (body.isString()) {
response.bodyFromFile = body.fileName()
}
}
else {
response.body = MapConverter.getStubSideValues(contract.response?.body)
}
contract.response?.bodyMatchers?.matchers()?.each { BodyMatcher matcher ->
response.matchers.body << new YamlContract.BodyTestMatcher(
path: matcher.path(),
type: testMatcherType(matcher.matchingType()),
value: matcher.value()?.toString(),
minOccurrence: matcher.minTypeOccurrence(),
maxOccurrence: matcher.maxTypeOccurrence()
)
}
if (XML != contentType) {
setOutputBodyMatchers(contract.response?.body,
yamlContract.response.matchers.body)
}
setOutputHeadersMatchers(contract.response?.headers,
yamlContract.response.matchers.headers)
}
}
protected void setInputHeadersMatchers(Headers headers, List<YamlContract.KeyValueMatcher> headerMatchers) {
headers?.asStubSideMap()?.each { String key, Object value ->
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
headerMatchers << new YamlContract.KeyValueMatcher(
key: key,
regex: property.pattern(),
regexType: regexType(property.clazz())
)
}
}
}
protected void setOutputHeadersMatchers(Headers headers, List<YamlContract.TestHeaderMatcher> headerMatchers) {
headers?.asTestSideMap()?.each { String key, Object value ->
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value)
headerMatchers << new YamlContract.TestHeaderMatcher(
key: key,
regex: property.pattern(),
regexType: regexType(property.clazz())
)
}
else if (value instanceof ExecutionProperty) {
headerMatchers << new YamlContract.TestHeaderMatcher(
key: key,
command: value.executionCommand,
)
}
else if (value instanceof NotToEscapePattern) {
headerMatchers << new YamlContract.TestHeaderMatcher(
key: key,
regex: ((Pattern) value.serverValue).pattern(),
)
}
}
}
protected YamlContract.TestMatcherType testMatcherType(MatchingType matchingType) {
switch (matchingType) {
case MatchingType.EQUALITY:
return YamlContract.TestMatcherType.by_equality
case MatchingType.TYPE:
return YamlContract.TestMatcherType.by_type
case MatchingType.COMMAND:
return YamlContract.TestMatcherType.by_command
case MatchingType.DATE:
return YamlContract.TestMatcherType.by_date
case MatchingType.TIME:
return YamlContract.TestMatcherType.by_time
case MatchingType.TIMESTAMP:
return YamlContract.TestMatcherType.by_timestamp
case MatchingType.REGEX:
return YamlContract.TestMatcherType.by_regex
case MatchingType.NULL:
return YamlContract.TestMatcherType.by_null
}
return null
}
protected YamlContract.StubMatcherType stubMatcherType(MatchingType matchingType) {
switch (matchingType) {
case MatchingType.EQUALITY:
return YamlContract.StubMatcherType.by_equality
case MatchingType.TYPE:
case MatchingType.COMMAND:
throw new UnsupportedOperationException("No type for client side")
case MatchingType.DATE:
return YamlContract.StubMatcherType.by_date
case MatchingType.TIME:
return YamlContract.StubMatcherType.by_time
case MatchingType.TIMESTAMP:
return YamlContract.StubMatcherType.by_timestamp
case MatchingType.REGEX:
return YamlContract.StubMatcherType.by_regex
}
return null
}
}

View File

@@ -1,785 +0,0 @@
/*
* Copyright 2013-2020 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.converter
import java.nio.file.Files
import java.util.regex.Pattern
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper
import groovy.transform.PackageScope
import org.yaml.snakeyaml.Yaml
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.spec.internal.Header
import org.springframework.cloud.contract.spec.internal.Headers
import org.springframework.cloud.contract.spec.internal.MatchingTypeValue
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.NamesUtil
import org.springframework.util.StringUtils
import static java.util.stream.Collectors.toSet
import static org.springframework.cloud.contract.verifier.util.ContentType.XML
import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Tim Ysewyn
*/
@PackageScope
class YamlToContracts {
Collection<Contract> convertFrom(File contractFile) {
ClassLoader classLoader = YamlContractConverter.getClassLoader()
YAMLMapper mapper = new YAMLMapper()
try {
Iterable<Object> iterables = new Yaml().
loadAll(Files.newInputStream(contractFile.toPath()))
Collection<Contract> contracts = []
int counter = 0
for (Object document : iterables) {
List<Contract> processedYaml =
processYaml(counter, document, mapper, classLoader, contractFile)
contracts.addAll(processedYaml)
counter = counter + 1
}
return contracts
}
catch (FileNotFoundException e) {
throw new IllegalStateException(e)
}
catch (IllegalStateException ise) {
throw ise
}
catch (Exception e1) {
throw new IllegalStateException("Exception occurred while processing the file [" + contractFile + "]", e1)
}
finally {
Thread.currentThread().setContextClassLoader(classLoader)
}
}
protected List<Contract> processYaml(int counter, Object document, ObjectMapper mapper, ClassLoader classLoader, File contractFile) {
List<YamlContract> yamlContracts = convert(mapper, document)
Thread.currentThread().setContextClassLoader(
updatedClassLoader(contractFile.getParentFile(), classLoader))
List<Contract> contracts = []
for (YamlContract yamlContract : yamlContracts) {
Contract contract = Contract.make {
if (yamlContract.description) {
description(yamlContract.description)
}
if (yamlContract.label) {
label(yamlContract.label)
}
name(StringUtils.hasText(yamlContract.name) ? yamlContract.name
: NamesUtil.defaultContractName(contractFile, yamlContracts, counter))
if (yamlContract.priority) {
priority(yamlContract.priority)
}
if (yamlContract.ignored) {
ignored()
}
if (yamlContract.inProgress) {
inProgress()
}
if (yamlContract.metadata) {
metadata(yamlContract.metadata)
}
if (yamlContract.request?.method) {
request {
method(yamlContract.request?.method)
if (yamlContract.request?.url) {
url(urlValue(yamlContract.request?.url, yamlContract.request?.matchers?.url)) {
if (yamlContract.request.queryParameters) {
queryParameters {
yamlContract.request.queryParameters.
each { String key, Object value ->
if (value instanceof List) {
((List) value).each {
parameter(key, it)
}
}
else {
parameter(key, value)
}
}
}
}
}
}
if (yamlContract.request?.urlPath) {
urlPath(
urlValue(yamlContract.request?.urlPath, yamlContract.request?.matchers?.url)) {
if (yamlContract.request.queryParameters) {
queryParameters {
yamlContract.request.queryParameters.
each { String key, Object value ->
if (value instanceof List) {
((List) value).each {
parameter(key,
queryParamValue(yamlContract, key, it))
}
}
else {
parameter(key,
queryParamValue(yamlContract, key, value))
}
}
}
}
}
}
if (yamlContract.request?.headers) {
headers {
yamlContract.request.headers.
each { String key, Object value ->
List<YamlContract.KeyValueMatcher> matchers =
yamlContract.request.matchers.headers.
findAll { it.key == key }
matchers.
each { YamlContract.KeyValueMatcher matcher ->
if (value instanceof List) {
((List) value).each {
header(key,
clientValue(it, matcher, key).clientValue)
}
}
else {
header(key, new DslProperty(
clientValue(value, matcher, key).clientValue,
serverValue(value, matcher)))
}
}
if (!matchers) {
header(key, value)
}
}
}
}
if (yamlContract.request?.cookies) {
cookies {
yamlContract.request?.cookies?.
each { String key, Object value ->
YamlContract.KeyValueMatcher matcher = yamlContract.request.matchers.cookies.
find { it.key == key }
cookie(key, clientValue(value, matcher, key))
}
}
}
if (yamlContract.request.body != null) {
body(yamlContract.request.body)
}
if (yamlContract.request.bodyFromFile != null) {
body(file(yamlContract.request.bodyFromFile))
}
if (yamlContract.request.bodyFromFileAsBytes != null) {
body(fileAsBytes(yamlContract.request.bodyFromFileAsBytes))
}
if (yamlContract.request.multipart) {
Map multipartMap = [:] as Map
Map<String, DslProperty> multiPartParams = yamlContract.request
.multipart.params.
collectEntries { String paramKey, String paramValue ->
YamlContract.KeyValueMatcher matcher = yamlContract.request.matchers
.multipart.params.find {
it.key == paramKey
}
Object value = paramValue
if (matcher) {
value = matcher.regex ? Pattern.
compile(matcher.regex) :
predefinedToPattern(matcher.predefined)
}
return [(paramKey), new DslProperty<>(value, paramValue)]
} as Map<String, DslProperty>
multipartMap.putAll(multiPartParams)
yamlContract.request.multipart.named.
each { YamlContract.Named namedParam ->
YamlContract.MultipartNamedStubMatcher matcher = yamlContract.request.matchers.multipart.named.
find {
it.paramName == namedParam.paramName
}
Object fileNameValue = namedParam.fileName
Object fileContentValue = namedParam.fileContent
String fileContentAsBytes = namedParam.fileContentAsBytes
String fileContentFromFileAsBytes = namedParam.fileContentFromFileAsBytes
String contentTypeCommand = namedParam.contentTypeCommand
String fileContentCommand = namedParam.fileContentCommand
String fileNameCommand = namedParam.fileNameCommand
Object contentTypeValue = namedParam.contentType
if (matcher && matcher.fileName) {
fileNameValue = matcher.fileName.regex ? Pattern.
compile(matcher.fileName.regex) :
predefinedToPattern(matcher.fileName.predefined)
}
if (matcher && matcher.fileContent) {
fileContentValue = matcher.fileContent.regex ? Pattern.
compile(matcher.fileContent.regex) :
predefinedToPattern(matcher.fileContent.predefined)
}
if (matcher && matcher.contentType) {
contentTypeValue = matcher.contentType.regex ? Pattern.
compile(matcher.contentType.regex) :
predefinedToPattern(matcher.contentType.predefined)
}
multipartMap.
put(namedParam.paramName, new NamedProperty(
new DslProperty<>(fileNameValue, fileNameCommand ? new ExecutionProperty(fileNameCommand)
: namedParam.fileName),
new DslProperty<>(fileContentValue, namedParam.fileContent ? namedParam.fileContent : fileContentFromFileAsBytes ?
fileAsBytes(namedParam.fileContentFromFileAsBytes) : fileContentAsBytes ? fileContentAsBytes.bytes : new ExecutionProperty(fileContentCommand)),
new DslProperty(contentTypeValue, contentTypeCommand ? new ExecutionProperty(contentTypeCommand)
: namedParam.contentType)))
}
multipart(multipartMap)
}
bodyMatchers {
yamlContract.request.matchers?.body?.
each { YamlContract.BodyStubMatcher matcher ->
ContentType contentType =
evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContract.request?.headers),
yamlContract.request?.body)
MatchingTypeValue value = null
switch (matcher.type) {
case YamlContract.StubMatcherType.by_date:
value = byDate()
break
case YamlContract.StubMatcherType.by_time:
value = byTime()
break
case YamlContract.StubMatcherType.by_timestamp:
value = byTimestamp()
break
case YamlContract.StubMatcherType.by_regex:
String regex = matcher.value
if (matcher.predefined) {
regex =
predefinedToPattern(matcher.predefined).
pattern()
}
value = byRegex(regex)
break
case YamlContract.StubMatcherType.by_equality:
value = byEquality()
break
case YamlContract.StubMatcherType.by_type:
value = byType {
if (matcher.minOccurrence != null) {
minOccurrence(matcher.minOccurrence)
}
if (matcher.maxOccurrence != null) {
maxOccurrence(matcher.maxOccurrence)
}
}
break
case YamlContract.StubMatcherType.by_null:
// do nothing
break
default:
throw new UnsupportedOperationException("The type [" + matcher.type + "] is unsupported. Hint: If you're using <predefined> remember to pass <type: by_regex>")
}
if (value) {
if (XML == contentType) {
xPath(matcher.path, value)
}
else {
jsonPath(matcher.path, value)
}
}
}
}
}
response {
status(yamlContract.response.status)
headers {
yamlContract.response?.headers?.
each { String key, Object value ->
YamlContract.TestHeaderMatcher matcher = yamlContract.response.matchers.headers.
find { it.key == key }
if (value instanceof List) {
((List) value).each {
Object serverValue =
serverValue(it, matcher, key)
header(key, new DslProperty(it, serverValue))
}
}
else {
Object serverValue =
serverValue(value, matcher, key)
header(key, new DslProperty(value, serverValue))
}
}
}
if (yamlContract.response?.cookies) {
cookies {
yamlContract.response?.cookies?.
each { String key, Object value ->
YamlContract.TestCookieMatcher matcher = yamlContract.response.matchers.cookies.
find { it.key == key }
DslProperty cookieValue =
serverCookieValue(value, matcher, key)
cookie(key, cookieValue)
}
}
}
if (yamlContract.response.body != null) {
YamlContract.BodyTestMatcher bodyTestMatcher = yamlContract.response?.matchers?.body?.find {
it.path == null && (it.type == YamlContract.TestMatcherType.by_regex ||
it.type == YamlContract.TestMatcherType.by_command)
}
if (bodyTestMatcher) {
body(new DslProperty(yamlContract.response.body,
bodyTestMatcher.type == YamlContract.TestMatcherType.by_regex ?
Pattern.
compile(bodyTestMatcher.value) : new ExecutionProperty(bodyTestMatcher.value)))
}
else {
body(yamlContract.response.body)
}
}
if (yamlContract.response.bodyFromFile) {
body(file(yamlContract.response.bodyFromFile))
}
if (yamlContract.response.bodyFromFileAsBytes) {
body(fileAsBytes(yamlContract.response.bodyFromFileAsBytes))
}
if (yamlContract.response.async) {
async()
}
if (yamlContract.response.fixedDelayMilliseconds) {
async()
fixedDelayMilliseconds(yamlContract.response.fixedDelayMilliseconds)
}
bodyMatchers {
yamlContract.response?.matchers?.body?.
each { YamlContract.BodyTestMatcher testMatcher ->
ContentType contentType =
evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContract.response?.headers),
yamlContract.response?.body)
MatchingTypeValue value = null
switch (testMatcher.type) {
case YamlContract.TestMatcherType.by_date:
value = byDate()
break
case YamlContract.TestMatcherType.by_time:
value = byTime()
break
case YamlContract.TestMatcherType.by_timestamp:
value = byTimestamp()
break
case YamlContract.TestMatcherType.by_regex:
String regex = testMatcher.value
if (testMatcher.predefined) {
regex =
predefinedToPattern(testMatcher.predefined).
pattern()
}
value = byRegex(regex)
break
case YamlContract.TestMatcherType.by_equality:
value = byEquality()
break
case YamlContract.TestMatcherType.by_type:
value = byType() {
if (testMatcher.minOccurrence != null) {
minOccurrence(testMatcher.minOccurrence)
}
if (testMatcher.maxOccurrence != null) {
maxOccurrence(testMatcher.maxOccurrence)
}
}
break
case YamlContract.TestMatcherType.by_command:
value = byCommand(testMatcher.value)
break
case YamlContract.TestMatcherType.by_null:
value = byNull()
break
default:
throw new UnsupportedOperationException("The type [" + testMatcher.type + "] is unsupported. Hint: If you're using <predefined> remember to pass <type: by_regex>")
}
if (testMatcher.path) {
if (XML == contentType) {
xPath(testMatcher.path, value)
}
else {
jsonPath(testMatcher.path, value)
}
}
}
}
}
}
if (yamlContract.input) {
input {
if (yamlContract.input.messageFrom) {
messageFrom(yamlContract.input.messageFrom)
}
if (yamlContract.input.assertThat) {
assertThat(yamlContract.input.assertThat)
}
if (yamlContract.input.triggeredBy) {
triggeredBy(yamlContract.input.triggeredBy)
}
messageHeaders {
yamlContract.input?.messageHeaders?.
each { String key, Object value ->
YamlContract.KeyValueMatcher matcher = yamlContract.input.matchers?.headers?.
find { it.key == key }
header(key, clientValue(value, matcher, key))
}
}
if (yamlContract.input.messageBody) {
messageBody(yamlContract.input.messageBody)
}
if (yamlContract.input.messageBodyFromFile) {
messageBody(file(yamlContract.input.messageBodyFromFile))
}
if (yamlContract.input.messageBodyFromFileAsBytes) {
messageBody(
fileAsBytes(yamlContract.input.messageBodyFromFileAsBytes))
}
bodyMatchers {
yamlContract.input.matchers.body?.
each { YamlContract.BodyStubMatcher matcher ->
ContentType contentType =
evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContract.input?.messageHeaders),
yamlContract.input?.messageBody)
MatchingTypeValue value = null
switch (matcher.type) {
case YamlContract.StubMatcherType.by_date:
value = byDate()
break
case YamlContract.StubMatcherType.by_time:
value = byTime()
break
case YamlContract.StubMatcherType.by_timestamp:
value = byTimestamp()
break
case YamlContract.StubMatcherType.by_regex:
String regex = matcher.value
if (matcher.predefined) {
regex =
predefinedToPattern(matcher.predefined).
pattern()
}
value = byRegex(regex)
break
case YamlContract.StubMatcherType.by_equality:
value = byEquality()
break
default:
throw new UnsupportedOperationException("The type [" + matcher.type + "] is unsupported. Hint: If you're using <predefined> remember to pass <type: by_regex>")
}
if (XML == contentType) {
xPath(matcher.path, value)
}
else {
jsonPath(matcher.path, value)
}
}
}
}
}
YamlContract.OutputMessage outputMsg = yamlContract.outputMessage
if (outputMsg) {
outputMessage {
if (outputMsg.assertThat) {
assertThat(outputMsg.assertThat)
}
if (outputMsg.sentTo) {
sentTo(outputMsg.sentTo)
}
headers {
outputMsg.headers?.each { String key, Object value ->
YamlContract.TestHeaderMatcher matcher = outputMsg.matchers?.headers?.
find { it.key == key }
Object serverValue = serverValue(value, matcher, key)
header(key, new DslProperty(value, serverValue))
}
}
if (outputMsg.body) {
body(outputMsg.body)
}
if (outputMsg.bodyFromFile) {
body(file(outputMsg.bodyFromFile))
}
if (outputMsg.bodyFromFileAsBytes) {
body(fileAsBytes(outputMsg.bodyFromFileAsBytes))
}
if (outputMsg.matchers) {
bodyMatchers {
yamlContract.outputMessage?.matchers?.body?.
each { YamlContract.BodyTestMatcher testMatcher ->
ContentType contentType =
evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContract.outputMessage?.headers),
yamlContract.outputMessage?.body)
MatchingTypeValue value = null
switch (testMatcher.type) {
case YamlContract.TestMatcherType.by_date:
value = byDate()
break
case YamlContract.TestMatcherType.by_time:
value = byTime()
break
case YamlContract.TestMatcherType.by_timestamp:
value = byTimestamp()
break
case YamlContract.TestMatcherType.by_regex:
String regex = testMatcher.value
if (testMatcher.predefined) {
regex =
predefinedToPattern(testMatcher.predefined).
pattern()
}
value = byRegex(regex)
break
case YamlContract.TestMatcherType.by_equality:
value = byEquality()
break
case YamlContract.TestMatcherType.by_type:
value = byType() {
if (testMatcher.minOccurrence != null) {
minOccurrence(testMatcher.minOccurrence)
}
if (testMatcher.maxOccurrence != null) {
maxOccurrence(testMatcher.maxOccurrence)
}
}
break
case YamlContract.TestMatcherType.by_command:
value = byCommand(testMatcher.value)
break
case YamlContract.TestMatcherType.by_null:
value = byNull()
break
default:
throw new UnsupportedOperationException("The type [" + testMatcher.type + "] is unsupported. Hint: If you're using <predefined> remember to pass <type: by_regex>")
}
if (XML == contentType) {
xPath(testMatcher.path, value)
}
else {
jsonPath(testMatcher.path, value)
}
}
}
}
}
}
}
contracts.add(contract)
}
return contracts
}
private Headers yamlHeadersToContractHeaders(Map<String, Object> headers) {
Set<Header> convertedHeaders = headers.keySet().stream()
.map({ Header.build(it, headers.get(it)) })
.collect(toSet())
Headers contractHeaders = new Headers()
contractHeaders.headers(convertedHeaders)
return contractHeaders
}
protected DslProperty urlValue(String url, YamlContract.KeyValueMatcher urlMatcher) {
if (urlMatcher) {
if (urlMatcher.command) {
return new DslProperty<Object>(url, new ExecutionProperty(urlMatcher.command))
}
return new DslProperty(urlMatcher.regex ? Pattern.compile(urlMatcher.regex) :
urlMatcher.predefined ?
predefinedToPattern(urlMatcher.predefined) : url, url)
}
return new DslProperty(url)
}
protected List<YamlContract> convert(ObjectMapper mapper, Object o) {
try {
return Arrays.asList(mapper.convertValue(o, YamlContract[].class))
}
catch (IllegalArgumentException e) {
return Collections.singletonList(mapper.convertValue(o, YamlContract.class))
}
}
protected Object serverValue(Object value, YamlContract.TestHeaderMatcher matcher, String key) {
Object serverValue = value
if (matcher?.regex) {
serverValue = Pattern.compile(matcher.regex)
Pattern pattern = (Pattern) serverValue
assertPatternMatched(pattern, value, key)
}
else if (matcher?.predefined) {
Pattern pattern = predefinedToPattern(matcher.predefined)
serverValue = pattern
assertPatternMatched(pattern, value, key)
}
else if (matcher?.command) {
serverValue = new ExecutionProperty(matcher.command)
}
return serverValue
}
protected DslProperty serverCookieValue(Object value, YamlContract.TestCookieMatcher matcher, String key) {
Object serverValue = value
if (matcher?.regex) {
serverValue = Pattern.compile(matcher.regex)
Pattern pattern = (Pattern) serverValue
assertPatternMatched(pattern, value, key)
}
else if (matcher?.predefined) {
Pattern pattern = predefinedToPattern(matcher.predefined)
serverValue = pattern
assertPatternMatched(pattern, value, key)
}
else if (matcher?.command) {
return new DslProperty(new ExecutionProperty(matcher.command), value)
}
return new DslProperty(value, serverValue)
}
protected DslProperty clientValue(Object value, YamlContract.KeyValueMatcher matcher, String key) {
Object clientValue = value instanceof DslProperty ? value.clientValue : value
if (matcher?.regex) {
clientValue = Pattern.compile(matcher.regex)
Pattern pattern = (Pattern) clientValue
assertPatternMatched(pattern, value, key)
}
else if (matcher?.predefined) {
Pattern pattern = predefinedToPattern(matcher.predefined)
clientValue = pattern
assertPatternMatched(pattern, value, key)
}
else if (matcher?.command) {
return new DslProperty(value, new ExecutionProperty(matcher.command))
}
return new DslProperty(clientValue, value)
}
protected Object queryParamValue(YamlContract yamlContract, String key, Object value) {
Request request = new Request()
YamlContract.QueryParameterMatcher matcher = yamlContract.request.
matchers.queryParameters.find { it.key == key }
if (!matcher) {
return value
}
switch (matcher.type) {
case YamlContract.MatchingType.equal_to:
return new DslProperty(request.equalTo(matcher.value) as Object, value)
case YamlContract.MatchingType.containing:
return new DslProperty(request.containing(matcher.value) as Object, value)
case YamlContract.MatchingType.matching:
return new DslProperty(request.matching(matcher.value) as Object, value)
case YamlContract.MatchingType.not_matching:
return new DslProperty(request.notMatching(matcher.value) as Object, value)
case YamlContract.MatchingType.equal_to_json:
return new DslProperty(request.equalToJson(matcher.value) as Object, value)
case YamlContract.MatchingType.equal_to_xml:
return new DslProperty(request.equalToXml(matcher.value) as Object, value)
case YamlContract.MatchingType.absent:
return new DslProperty(request.absent() as Object, null)
default:
throw new UnsupportedOperationException("The provided matching type [" + matcher + "] is unsupported. Use on of "
+ YamlContract.MatchingType.
values())
}
}
protected Object serverValue(Object value, YamlContract.KeyValueMatcher matcher) {
Object serverValue = value
if (matcher?.command) {
return new ExecutionProperty(matcher.command)
}
return serverValue instanceof DslProperty ?
((DslProperty) serverValue).serverValue : serverValue
}
private void assertPatternMatched(Pattern pattern, value, String key) {
boolean matches = pattern.matcher(value.toString()).matches()
if (!matches) {
throw new IllegalStateException("Broken headers! A header with "
+
"key [${key}] with value [${value}] is not matched by regex [${pattern.pattern()}]")
}
}
protected Pattern predefinedToPattern(YamlContract.PredefinedRegex predefinedRegex) {
switch (predefinedRegex) {
case YamlContract.PredefinedRegex.only_alpha_unicode:
return RegexPatterns.onlyAlphaUnicode().pattern
case YamlContract.PredefinedRegex.number:
return RegexPatterns.number().pattern
case YamlContract.PredefinedRegex.any_double:
return RegexPatterns.aDouble().pattern
case YamlContract.PredefinedRegex.any_boolean:
return RegexPatterns.anyBoolean().pattern
case YamlContract.PredefinedRegex.ip_address:
return RegexPatterns.ipAddress().pattern
case YamlContract.PredefinedRegex.hostname:
return RegexPatterns.hostname().pattern
case YamlContract.PredefinedRegex.email:
return RegexPatterns.email().pattern
case YamlContract.PredefinedRegex.url:
return RegexPatterns.url().pattern
case YamlContract.PredefinedRegex.uuid:
return RegexPatterns.uuid().pattern
case YamlContract.PredefinedRegex.iso_date:
return RegexPatterns.isoDate().pattern
case YamlContract.PredefinedRegex.iso_date_time:
return RegexPatterns.isoDateTime().pattern
case YamlContract.PredefinedRegex.iso_time:
return RegexPatterns.isoTime().pattern
case YamlContract.PredefinedRegex.iso_8601_with_offset:
return RegexPatterns.iso8601WithOffset().pattern
case YamlContract.PredefinedRegex.non_empty:
return RegexPatterns.nonEmpty().pattern
case YamlContract.PredefinedRegex.non_blank:
return RegexPatterns.nonBlank().pattern
default:
throw new UnsupportedOperationException("The predefined regex [" + predefinedRegex + "] is unsupported. Use on of "
+ YamlContract.PredefinedRegex.
values())
}
}
protected String file(String relativePath) {
URL resource = Thread.currentThread().getContextClassLoader().
getResource(relativePath)
if (resource == null) {
throw new IllegalStateException("File [${relativePath}] is not present")
}
return new File(resource.toURI()).text
}
protected static ClassLoader updatedClassLoader(File rootFolder, ClassLoader classLoader) {
ClassLoader urlCl = URLClassLoader
.newInstance([rootFolder.toURI().toURL()] as URL[], classLoader)
Thread.currentThread().setContextClassLoader(urlCl)
return urlCl
}
}

View File

@@ -1,210 +0,0 @@
/*
* Copyright 2013-2020 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.dsl.wiremock
import com.fasterxml.jackson.databind.ObjectMapper
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.ContractTemplate
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.spec.internal.Headers
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor
import org.springframework.cloud.contract.verifier.template.TemplateProcessor
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.verifier.util.MapConverter
import static org.springframework.cloud.contract.verifier.util.ContentType.UNKNOWN
import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getClientContentType
import static org.springframework.cloud.contract.verifier.util.MapConverter.transformValues
/**
* Common abstraction over WireMock Request / Response conversion implementations
*
* Do not change to {@code @CompileStatic} since it's using double dispatch.
*
* @since 1.0.0
*/
@TypeChecked
@PackageScope
abstract class BaseWireMockStubStrategy {
private static final String WRAPPER = "UNQUOTE_ME"
protected final TemplateProcessor processor
protected final ContractTemplate template
protected final Contract contract
protected BaseWireMockStubStrategy(Contract contract) {
this.processor = templateProcessor()
this.template = contractTemplate()
this.contract = contract
}
private TemplateProcessor templateProcessor() {
return new HandlebarsTemplateProcessor()
}
private ContractTemplate contractTemplate() {
return new HandlebarsTemplateProcessor()
}
/**
* @return the stub side values from the object
*/
protected Object getStubSideValue(Object object) {
return MapConverter.getStubSideValues(object)
}
private static Closure transform = {
it instanceof DslProperty ? transformValues(it.clientValue, transform) : it
}
/**
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(Object value, ContentType contentType) {
return parseBody(value.toString(), contentType)
}
/**
* Return body as String from file
*/
String parseBody(FromFileProperty value, ContentType contentType) {
return value.asString()
}
/**
* For the given {@link ContentType} returns the Boolean version of the body
*/
String parseBody(Boolean value, ContentType contentType) {
return value
}
/**
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(Map map, ContentType contentType) {
def transformedMap = MapConverter.getStubSideValues(map)
transformedMap = transformMapIfRequestPresent(transformedMap)
String json = toJson(transformedMap)
// the space is important cause at the end of the json body you also have a }
// you can't have 4 } next to each other
String unquotedJson = json.replace('"' + WRAPPER, '').replace(WRAPPER + '"', ' ')
String unescapedJson = unquotedJson.replace("\\/", "/")
return parseBody(unescapedJson, contentType)
}
private Object transformMapIfRequestPresent(Object transformedMap) {
def requestBody = contract.request.body
if (requestBody == null) {
return transformedMap
}
String testSideBody = toJson(
MapConverter.getTestSideValues(requestBody))
DocumentContext context = JsonPath.parse(testSideBody)
return processEntriesForTemplating(transformedMap, context)
}
private Object processEntriesForTemplating(Object transformedMap, DocumentContext context) {
return transformValues(transformedMap, {
if (it instanceof String && processor.containsJsonPathTemplateEntry(it)) {
String jsonPath = processor.jsonPathFromTemplateEntry(it)
if (!jsonPath) {
return it
}
Object value = context.read(jsonPath)
if (value instanceof String) {
return it
}
return "${WRAPPER}${it}${WRAPPER}"
}
else if (it instanceof String && processor.containsTemplateEntry(it)
&&
template.escapedBody() == it) {
return template.escapedBody()
}
return it
})
}
/**
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(List list, ContentType contentType) {
List result = []
list.each {
if (it instanceof Map) {
result += MapConverter.getStubSideValues(it)
}
else {
result += parseBody(it, contentType)
}
}
return parseBody(toJson(result), contentType)
}
/**
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(GString value, ContentType contentType) {
Object processedValue =
extractValue(value, contentType, { Object o -> o instanceof DslProperty ? o.clientValue : o })
if (processedValue instanceof GString) {
return parseBody(processedValue.toString(), contentType)
}
return parseBody(processedValue, contentType)
}
/**
* For the given {@link ContentType} returns the String version of the body
*/
String parseBody(String value, ContentType contentType) {
return value
}
private static String toJson(Object value) {
if (value instanceof Map) {
Map convertedMap = MapConverter.transformValues(value) {
it instanceof GString ? it.toString() : it
} as Map
String jsonOutput = new ObjectMapper().writeValueAsString(convertedMap)
return jsonOutput.replaceAll("\\\\\\\\\\\\", "\\\\")
}
return new ObjectMapper().writeValueAsString(value)
}
/**
* Attempts to guess the {@link ContentType} from body and headers. Returns
* {@link ContentType#UNKNOWN} if it fails to guess.
*/
protected ContentType tryToGetContentType(Object body, Headers headers) {
ContentType contentType = ContentUtils.recognizeContentTypeFromHeader(headers)
if (UNKNOWN == contentType) {
if (!body) {
return UNKNOWN
}
return getClientContentType(body)
}
return contentType
}
}

View File

@@ -1,520 +0,0 @@
/*
* Copyright 2013-2020 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.dsl.wiremock
import java.util.regex.Pattern
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.extension.Parameters
import com.github.tomakehurst.wiremock.http.RequestMethod
import com.github.tomakehurst.wiremock.matching.ContentPattern
import com.github.tomakehurst.wiremock.matching.RequestPattern
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder
import com.github.tomakehurst.wiremock.matching.StringValuePattern
import com.github.tomakehurst.wiremock.matching.UrlPattern
import groovy.json.JsonOutput
import groovy.json.StringEscapeUtils
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import groovy.util.logging.Commons
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.Body
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.spec.internal.MatchingStrategy
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.spec.internal.PathBodyMatcher
import org.springframework.cloud.contract.spec.internal.QueryParameters
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import org.springframework.cloud.contract.spec.internal.RegexProperty
import org.springframework.cloud.contract.spec.internal.Request
import org.springframework.cloud.contract.verifier.converter.YamlContract
import org.springframework.cloud.contract.verifier.converter.YamlContractConverter
import org.springframework.cloud.contract.verifier.dsl.ContractVerifierMetadata
import org.springframework.cloud.contract.verifier.file.SingleContractMetadata
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
import org.springframework.cloud.contract.verifier.util.MapConverter
import org.springframework.cloud.contract.verifier.util.xml.XmlToXPathsConverter
import org.springframework.util.StringUtils
import static org.springframework.cloud.contract.spec.internal.MatchingStrategy.Type.BINARY_EQUAL_TO
import static org.springframework.cloud.contract.spec.internal.MatchingType.COMMAND
import static org.springframework.cloud.contract.spec.internal.MatchingType.EQUALITY
import static org.springframework.cloud.contract.spec.internal.MatchingType.NULL
import static org.springframework.cloud.contract.spec.internal.MatchingType.TYPE
import static org.springframework.cloud.contract.verifier.util.ContentType.FORM
import static org.springframework.cloud.contract.verifier.util.ContentType.JSON
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getEqualsTypeFromContentType
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildGStringRegexpForStubSide
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildJSONRegexpMatch
import static org.springframework.cloud.contract.verifier.util.xml.XmlToXPathsConverter.retrieveValue
/**
* Converts a {@link Request} into {@link RequestPattern}
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
@TypeChecked
@PackageScope
@Commons
class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
private final Request request
private final ContentType contentType
WireMockRequestStubStrategy(Contract groovyDsl, SingleContractMetadata singleContractMetadata) {
super(groovyDsl)
this.request = groovyDsl.request
this.contentType = contentType(singleContractMetadata)
}
protected ContentType contentType(SingleContractMetadata singleContractMetadata) {
return singleContractMetadata.evaluatedInputStubContentType
}
@PackageScope
RequestPattern buildClientRequestContent() {
if (!request) {
return null
}
RequestPatternBuilder requestPatternBuilder = appendMethodAndUrl()
appendCookies(requestPatternBuilder)
appendHeaders(requestPatternBuilder)
appendQueryParameters(requestPatternBuilder)
appendBody(requestPatternBuilder)
appendMultipart(requestPatternBuilder)
return requestPatternBuilder.build()
}
private void appendBody(RequestPatternBuilder requestPatternBuilder) {
if (contract.metadata.containsKey(ContractVerifierMetadata.METADATA_KEY)) {
ContractVerifierMetadata metadata = ContractVerifierMetadata.fromMetadata(contract.getMetadata())
appendSpringCloudContractMatcher(metadata, requestPatternBuilder)
if (StringUtils.isEmpty(metadata.getTool())) {
doAppendBody(requestPatternBuilder)
}
}
else {
doAppendBody(requestPatternBuilder)
}
}
private void appendSpringCloudContractMatcher(ContractVerifierMetadata metadata, RequestPatternBuilder requestPatternBuilder) {
Parameters parameters = Parameters.one("tool", metadata.getTool() ?: "unknown");
YamlContractConverter converter = new YamlContractConverter();
List<YamlContract> contracts = converter.convertTo(Collections.singleton(contract));
Map<String, byte[]> store = converter.store(contracts);
parameters.put("contract", new String(store.entrySet().iterator().next().value))
requestPatternBuilder.andMatching(SpringCloudContractRequestMatcher.NAME, parameters)
}
private RequestPatternBuilder appendMethodAndUrl() {
if (!request.method) {
return null
}
RequestMethod requestMethod = RequestMethod.
fromString(request.method.clientValue?.toString())
UrlPattern urlPattern = urlPattern()
return RequestPatternBuilder.newRequestPattern(requestMethod, urlPattern)
}
private void doAppendBody(RequestPatternBuilder requestPattern) {
if (!request.body) {
return
}
boolean bodyHasMatchingStrategy = request.body.clientValue instanceof MatchingStrategy
MatchingStrategy matchingStrategy = getMatchingStrategyFromBody(request.body)
Object clientSideBody = MapConverter.transformToClientValues(request.body)
if (contentType == ContentType.JSON) {
def originalBody = matchingStrategy?.clientValue
if (bodyHasMatchingStrategy) {
requestPattern.withRequestBody(
convertToValuePattern(matchingStrategy))
} else if (clientSideBody instanceof Pattern || clientSideBody instanceof RegexProperty) {
requestPattern.withRequestBody(
convertToValuePattern(appendBodyRegexpMatchPattern(request.body, contentType)))
}
else {
def body = JsonToJsonPathsConverter.
removeMatchingJsonPaths(originalBody, request.bodyMatchers)
JsonPaths values = JsonToJsonPathsConverter.
transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(body)
if ((values.empty && !request.bodyMatchers?.hasMatchers())
||
onlySizeAssertionsArePresent(values)) {
requestPattern.withRequestBody(WireMock.equalToJson(JsonOutput.toJson(
getMatchingStrategy(request.body.clientValue).clientValue),
false, false))
}
else {
values.findAll { !it.assertsSize() }.each {
requestPattern.withRequestBody(WireMock.
matchingJsonPath(it.jsonPath().replace("\\\\", "\\")))
}
}
}
request.bodyMatchers?.matchers()?.each {
String newPath = JsonToJsonPathsConverter.
convertJsonPathAndRegexToAJsonPath(it, originalBody)
requestPattern.withRequestBody(WireMock.
matchingJsonPath(newPath.replace("\\\\", "\\")))
}
}
else if (contentType == ContentType.XML) {
Object originalBody = matchingStrategy?.clientValue
if (bodyHasMatchingStrategy) {
requestPattern.withRequestBody(
convertToValuePattern(matchingStrategy))
}
else {
Object body = XmlToXPathsConverter
.removeMatchingXPaths(originalBody, request.bodyMatchers)
List<BodyMatcher> byEqualityMatchersFromXml = new XmlToXPathsConverter()
.mapToMatchers(body)
byEqualityMatchersFromXml.each {
addWireMockStubMatchingSection(it, requestPattern, originalBody)
}
}
request.bodyMatchers?.matchers()?.each {
addWireMockStubMatchingSection(it, requestPattern, originalBody)
}
}
else if (containsPattern(request?.body)) {
requestPattern.withRequestBody(
convertToValuePattern(appendBodyRegexpMatchPattern(request.body)))
}
else {
requestBodyGuessedFromMatchingStrategy(requestPattern)
}
}
private Object generateConcreteValue(Object originalBody) {
if (originalBody instanceof Pattern || originalBody instanceof RegexProperty) {
return new RegexProperty(originalBody).generate()
}
return originalBody
}
private RequestPatternBuilder requestBodyGuessedFromMatchingStrategy(RequestPatternBuilder requestPattern) {
return requestPattern.withRequestBody(convertToValuePattern(
getMatchingStrategy(request.body.clientValue)))
}
private static void addWireMockStubMatchingSection(BodyMatcher matcher,
RequestPatternBuilder requestPattern,
Object body) {
Set<MatchingType> matchingTypesUnsupportedForRequest = [NULL, COMMAND, TYPE] as Set
if (!matcher instanceof PathBodyMatcher) {
throw new IllegalArgumentException("Only jsonPath and XPath matchers can be processed.")
}
String retrievedValue = Optional.ofNullable(matcher.value()).orElseGet({
if (matchingTypesUnsupportedForRequest.contains(matcher.matchingType())) {
throw new IllegalArgumentException("Null, Command and Type matchers are not supported in requests.")
}
if (EQUALITY == matcher.matchingType()) {
return retrieveValue(matcher, body)
}
else {
return ''
}
})
PathBodyMatcher pathMatcher = matcher as PathBodyMatcher
requestPattern.withRequestBody(WireMock.matchingXPath(pathMatcher.path(),
XPathBodyMatcherToWireMockValuePatternConverter
.mapToPattern(pathMatcher.matchingType(),
String.valueOf(retrievedValue))))
}
private boolean onlySizeAssertionsArePresent(JsonPaths values) {
return !values.empty && !request.bodyMatchers?.hasMatchers() && values.
every { it.assertsSize() }
}
private void appendMultipart(RequestPatternBuilder requestPattern) {
if (!request.multipart) {
return
}
if (request.multipart.clientValue instanceof Map) {
List<StringValuePattern> multipartPatterns = (request.multipart.clientValue as Map).
collect {
(it.value instanceof NamedProperty
? WireMock.matching(RegexPatterns.
multipartFile(it.key, (it.value as NamedProperty).name.clientValue,
(it.value as NamedProperty).value.clientValue,
(it.value as NamedProperty).contentType?.clientValue))
: WireMock.
matching(RegexPatterns.multipartParam(it.key, MapConverter.getStubSideValuesForNonBody(it.value))))
}
multipartPatterns.each {
requestPattern.withRequestBody(it)
}
}
}
private void appendHeaders(RequestPatternBuilder requestPattern) {
if (!request.headers) {
return
}
request.headers.entries.each {
requestPattern.withHeader(it.name, (StringValuePattern)
convertToValuePattern(it.clientValue))
}
}
private void appendCookies(RequestPatternBuilder requestPattern) {
if (!request.cookies) {
return
}
request.cookies.entries.each {
requestPattern.withCookie(it.key, (StringValuePattern)
convertToValuePattern(it.clientValue))
}
}
private UrlPattern urlPattern() {
Object urlPath = urlPathOrUrlIfQueryPresent()
if (urlPath) {
if (urlPath instanceof Pattern || urlPath instanceof RegexProperty) {
return WireMock.urlPathMatching(
getStubSideValue(new RegexProperty(urlPath).pattern()) as String)
}
else {
return WireMock.
urlPathEqualTo(getStubSideValue(urlPath.toString()) as String)
}
}
if (!request.url) {
throw new IllegalStateException("URL is required!")
}
Object url = getUrlIfGstring(request?.url?.clientValue)
if (url instanceof Pattern || url instanceof RegexProperty) {
return WireMock.urlMatching(new RegexProperty(url).pattern())
}
return WireMock.urlEqualTo(url.toString())
}
private Object urlPathOrUrlIfQueryPresent() {
Object urlPath = request?.urlPath?.clientValue
Object queryParamsFromUrl = request?.url?.queryParameters?.parameters
if (urlPath) {
return urlPath
}
if (queryParamsFromUrl) {
return request?.url?.clientValue
}
return null
}
private Object getUrlIfGstring(Object clientSide) {
if (clientSide instanceof GString) {
if (clientSide.values.any {
def value = getStubSideValue(it)
return value instanceof Pattern || value instanceof RegexProperty
}) {
String string = getStubSideValue(clientSide).toString()
return new RegexProperty(Pattern.compile(string))
}
else {
return getStubSideValue(clientSide).toString()
}
}
return clientSide
}
private void appendQueryParameters(RequestPatternBuilder requestPattern) {
QueryParameters queryParameters = request?.urlPath?.queryParameters ?: request?.url?.queryParameters
queryParameters?.parameters?.each {
requestPattern.withQueryParam(it.name, (StringValuePattern)
convertToValuePattern(it.clientValue))
}
}
protected ContentPattern convertToValuePattern(Object object) {
switch (object) {
case Pattern:
case RegexProperty:
return WireMock.matching(new RegexProperty(object).pattern())
case OptionalProperty:
OptionalProperty value = object as OptionalProperty
return WireMock.matching(value.optionalPattern())
case MatchingStrategy:
MatchingStrategy value = object as MatchingStrategy
switch (value.type) {
case MatchingStrategy.Type.NOT_MATCHING:
return WireMock.notMatching(value.clientValue.toString())
case MatchingStrategy.Type.ABSENT:
return WireMock.absent()
case MatchingStrategy.Type.EQUAL_TO:
return WireMock.equalTo(clientBody(value.clientValue, contentType).toString())
case MatchingStrategy.Type.CONTAINS:
return WireMock.containing(clientBody(value.clientValue, contentType).toString())
case MatchingStrategy.Type.MATCHING:
return WireMock.matching(clientBody(value.clientValue, contentType).toString())
case MatchingStrategy.Type.EQUAL_TO_JSON:
return WireMock.equalToJson(clientBody(value.clientValue, contentType).toString())
case MatchingStrategy.Type.EQUAL_TO_XML:
return WireMock.equalToXml(clientBody(value.clientValue, contentType).toString())
case MatchingStrategy.Type.BINARY_EQUAL_TO:
return WireMock.binaryEqualTo(clientBody(value.clientValue, contentType) as byte[])
default:
throw new UnsupportedOperationException("Unknown matching strategy " + value.type)
}
default:
return WireMock.equalTo(clientBody(object, contentType).toString())
}
}
protected Object clientBody(Object bodyValue, ContentType contentType) {
if (FORM == contentType) {
if (bodyValue instanceof Map) {
// [a:3, b:4] == "a=3&b=4"
return ((Map) bodyValue).collect {
StringEscapeUtils.
unescapeJavaScript(it.key.toString() + "=" + it.value)
}.join("&")
}
else if (bodyValue instanceof List) {
// ["a=3", "b=4"] == "a=3&b=4"
return ((List) bodyValue).collect {
StringEscapeUtils.unescapeJavaScript(it.toString())
}.join("&")
}
}
else if (bodyValue instanceof FromFileProperty) {
return bodyValue.isByte() ? bodyValue.asBytes() : bodyValue.asString()
}
else if (JSON == contentType) {
return parseBody(bodyValue, contentType)
}
return bodyValue
}
private MatchingStrategy getMatchingStrategyFromBody(Body body) {
if (!body) {
return null
}
return getMatchingStrategy(body.clientValue)
}
private MatchingStrategy getMatchingStrategy(MatchingStrategy matchingStrategy) {
return getMatchingStrategyIncludingContentType(matchingStrategy)
}
private MatchingStrategy getMatchingStrategy(GString gString) {
if (!gString) {
return new MatchingStrategy("", MatchingStrategy.Type.EQUAL_TO)
}
def extractedValue = ContentUtils.extractValue(gString) {
it instanceof DslProperty ? it.clientValue : getStringFromGString(it)
}
def value = getStringFromGString(extractedValue)
return getMatchingStrategy(value)
}
private def getStringFromGString(Object object) {
return object instanceof GString ? object.toString() : object
}
private MatchingStrategy getMatchingStrategy(Object bodyValue) {
return tryToFindMachingStrategy(bodyValue)
}
private MatchingStrategy getMatchingStrategy(FromFileProperty bodyValue) {
return new MatchingStrategy(bodyValue, BINARY_EQUAL_TO)
}
private MatchingStrategy tryToFindMachingStrategy(Object bodyValue) {
return new MatchingStrategy(MapConverter.transformToClientValues(bodyValue),
getEqualsTypeFromContentType(contentType))
}
private MatchingStrategy getMatchingStrategyIncludingContentType(MatchingStrategy matchingStrategy) {
MatchingStrategy.Type type = matchingStrategy.type
Object value = matchingStrategy.clientValue
ContentType contentType = ContentUtils.
recognizeContentTypeFromMatchingStrategy(type)
if (contentType == ContentType.UNKNOWN && type == MatchingStrategy.Type.EQUAL_TO) {
contentType = ContentUtils.recognizeContentTypeFromContent(value)
type = getEqualsTypeFromContentType(contentType)
}
return new MatchingStrategy(parseBody(value, contentType), type)
}
private MatchingStrategy appendBodyRegexpMatchPattern(Object value, ContentType contentType) {
Object clientValue = MapConverter.transformToClientValues(value)
switch (contentType) {
case ContentType.JSON:
return new MatchingStrategy(
buildJSONRegexpMatch(clientValue), MatchingStrategy.Type.MATCHING)
case ContentType.UNKNOWN:
return new MatchingStrategy(
buildGStringRegexpForStubSide(clientValue), MatchingStrategy.Type.MATCHING)
case ContentType.XML:
throw new IllegalStateException("XML pattern matching is not implemented yet")
}
}
private MatchingStrategy appendBodyRegexpMatchPattern(Object value) {
return appendBodyRegexpMatchPattern(value, ContentType.UNKNOWN)
}
private boolean containsPattern(GString bodyAsValue) {
return containsPattern(bodyAsValue.values)
}
private boolean containsPattern(Map map) {
return containsPattern(map.entrySet())
}
private boolean containsPattern(Collection collection) {
return collection.collect(this.&containsPattern).inject(false) { a, b -> a || b }
}
private boolean containsPattern(Object[] objects) {
return containsPattern(objects.toList())
}
private boolean containsPattern(Map.Entry entry) {
return containsPattern(entry.value)
}
private boolean containsPattern(DslProperty dslProperty) {
return containsPattern(dslProperty.clientValue)
}
private boolean containsPattern(Pattern pattern) {
return true
}
private boolean containsPattern(RegexProperty pattern) {
return true
}
private boolean containsPattern(Object o) {
return false
}
}

View File

@@ -1,376 +0,0 @@
/*
* Copyright 2013-2020 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.file
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.PathMatcher
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import wiremock.com.google.common.collect.HashMultiset
import wiremock.com.google.common.collect.ListMultimap
import wiremock.com.google.common.collect.Multimap
import wiremock.com.google.common.collect.Multiset
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.ContractConverter
import org.springframework.cloud.contract.verifier.converter.YamlContractConverter
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
import org.springframework.core.io.support.SpringFactoriesLoader
import org.springframework.util.CollectionUtils
import org.springframework.util.MultiValueMap
/**
* Scans the provided file path for the DSLs. There's a possibility to provide
* inclusion and exclusion filters.
*
* @author Jakub Kubrynski, codearte.io
*
* @since 1.0.0
*/
@CompileStatic
@Commons
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
private final Set<PathMatcher> excludeMatchers
private final Set<PathMatcher> ignoreMatchers
private final Set<PathMatcher> includeMatchers
private final String includeMatcher
ContractFileScanner(File baseDir, Set<String> excluded, Set<String> ignored,
Set<String> included = [],
String includeMatcher = "") {
this.baseDir = baseDir
this.excludeMatchers = processPatterns(excluded ?: [] as Set<String>)
this.ignoreMatchers = processPatterns(ignored ?: [] as Set<String>)
this.includeMatchers = processPatterns(included ?: [] as Set<String>)
this.includeMatcher = includeMatcher
}
private Set<PathMatcher> processPatterns(Set<String> patterns) {
FileSystem fileSystem = FileSystems.getDefault()
Set<PathMatcher> pathMatchers = new HashSet<PathMatcher>()
for (String pattern : patterns) {
String syntaxAndPattern = MATCH_PREFIX + '**' + File.separator + pattern
// FIXME: This looks strange, need to be checked on windows
if (IS_OS_WINDOWS) {
syntaxAndPattern = syntaxAndPattern.replace("\\", "\\\\")
}
pathMatchers.add(fileSystem.getPathMatcher(syntaxAndPattern))
}
return pathMatchers
}
/**
* @return for a map of paths for which a list of matching contracts has been found
* @deprecated use the {@link ContractFileScanner#findContractsRecursively} version
*/
@Deprecated
ListMultimap<Path, ContractMetadata> findContracts() {
MultiValueMap<Path, ContractMetadata> contracts = findContractsRecursively();
return new ListMultimap<Path, ContractMetadata>() {
@Override
List<ContractMetadata> get(Path key) {
return contracts.get(key)
}
@Override
List<ContractMetadata> removeAll(Object key) {
return contracts.remove(key)
}
@Override
List<ContractMetadata> replaceValues(Path key, Iterable<? extends ContractMetadata> values) {
return contracts.put(key, asList(values))
}
private static List<? extends ContractMetadata> asList(Iterable<? extends ContractMetadata> self) {
if (self instanceof List) {
return (List<? extends ContractMetadata>) self;
} else {
return toList(self.iterator());
}
}
private static List<? extends ContractMetadata> toList(Iterator<? extends ContractMetadata> self) {
List<? extends ContractMetadata> answer = new ArrayList<>();
while (self.hasNext()) {
answer.add(self.next());
}
return answer;
}
@Override
Map<Path, Collection<ContractMetadata>> asMap() {
return contracts.collectEntries {
[(it.key): (Collection<ContractMetadata>) it.value]
} as Map<Path, Collection<ContractMetadata>>
}
@Override
int size() {
return contracts.size()
}
@Override
boolean isEmpty() {
return contracts.isEmpty()
}
@Override
boolean containsKey(Object key) {
return contracts.containsKey(key)
}
@Override
boolean containsValue(Object value) {
return contracts.findResult { it.value.contains(value) }
}
@Override
boolean containsEntry(Object key, Object value) {
return contracts.findResult { it.key == key && it.value.contains(value) }
}
@Override
boolean put(Path key, ContractMetadata value) {
return contracts.add(key, value)
}
@Override
boolean remove(Object key, Object value) {
return contracts.getOrDefault(key, new ArrayList<ContractMetadata>()).remove(value)
}
@Override
boolean putAll(Path key, Iterable<? extends ContractMetadata> values) {
return contracts.getOrDefault(key, new ArrayList<ContractMetadata>()).addAll(values)
}
@Override
boolean putAll(Multimap<? extends Path, ? extends ContractMetadata> multimap) {
multimap.entries().each {
contracts.add(it.key, it.value)
}
return true
}
@Override
void clear() {
contracts.clear()
}
@Override
Set<Path> keySet() {
return contracts.keySet()
}
@Override
Multiset<Path> keys() {
return HashMultiset.create(contracts.keySet())
}
@Override
Collection<ContractMetadata> values() {
return (Collection<ContractMetadata>) contracts.values().flatten()
}
@Override
Collection<Map.Entry<Path, ContractMetadata>> entries() {
Collection<Map.Entry<Path, ContractMetadata>> entries = new LinkedList<>()
contracts.each {
Path path = it.key
List<ContractMetadata> list = it.value
list.each {
entries.add(new AbstractMap.SimpleEntry<Path, ContractMetadata>(path, it))
}
}
return entries
}
}
}
MultiValueMap<Path, ContractMetadata> findContractsRecursively() {
MultiValueMap<Path, ContractMetadata> result = CollectionUtils.toMultiValueMap(new LinkedHashMap<>());
appendRecursively(baseDir, result)
return result
}
/**
* We iterate over found contracts, filter out those that should be excluded
* and try to convert via pluggable Contract Converters any possible contracts
*/
private void appendRecursively(File baseDir, MultiValueMap<Path, ContractMetadata> result) {
List<ContractConverter> converters = convertersWithYml()
if (log.isTraceEnabled()) {
log.trace("Found the following contract converters ${converters}")
}
File[] files = baseDir.listFiles()
if (!files) {
return
}
File[] sortedFiles = files.sort() as File[]
for (int i = 0; i < sortedFiles.length; i++) {
File file = sortedFiles[i]
boolean excluded = matchesPattern(file, excludeMatchers)
if (!excluded) {
boolean contractFile = isContractFile(file)
boolean included = includeMatcher ? file.absolutePath.
matches(includeMatcher) : true
included = includeMatchers ?
matchesPattern(file, includeMatchers) : included
if (contractFile && included) {
addContractToTestGeneration(result, files, file, i, ContractVerifierDslConverter.
convertAsCollection(baseDir, file))
}
if (!contractFile && included) {
addContractToTestGeneration(converters, result, files, file, i)
}
else {
appendRecursively(file, result)
if (log.isDebugEnabled()) {
log.debug("File [$file] is ignored. Is a contract file? [$contractFile]. Should be included by pattern? [$included]")
}
}
}
else {
if (log.isDebugEnabled()) {
log.debug("File [$file] is ignored. Should be excluded? [$excluded]")
}
}
}
}
protected List<ContractConverter> convertersWithYml() {
List<ContractConverter> converters = converters()
converters.add(ContractVerifierDslConverter.INSTANCE)
converters.add(YamlContractConverter.INSTANCE)
return converters
}
protected List<ContractConverter> converters() {
return SpringFactoriesLoader.loadFactories(ContractConverter, null)
}
private void addContractToTestGeneration(List<ContractConverter> converters, MultiValueMap<Path, ContractMetadata> result,
File[] files, File file, int index) {
boolean converted = false
if (!file.isDirectory()) {
for (ContractConverter converter : converters) {
Collection<Contract> contracts = tryConvert(converter, file)
if (contracts) {
addContractToTestGeneration(result, files, file, index, contracts)
converted = true
break
}
}
}
if (!converted) {
appendRecursively(file, result)
if (log.isDebugEnabled()) {
log.debug("File [$file] wasn't ignored but no converter was applicable. The file is a directory [${file.isDirectory()}]")
}
}
}
private Collection<Contract> tryConvert(ContractConverter converter, File file) {
boolean accepted = converter.isAccepted(file)
if (!accepted) {
return null
}
try {
return converter.convertFrom(file)
}
catch (Exception e) {
throw new IllegalStateException("Failed to convert file [" + file + "]", e)
}
}
private void addContractToTestGeneration(MultiValueMap<Path, ContractMetadata> result, File[] files, File file,
int index, Collection<Contract> convertedContract) {
Path path = file.toPath()
Integer order = null
if (hasScenarioFilenamePattern(path)) {
order = index
}
Path parent = file.parentFile.toPath()
ContractMetadata metadata = new ContractMetadata(path,
matchesPattern(file, ignoreMatchers),
files.size(), order, convertedContract)
if (log.isDebugEnabled()) {
log.debug("Creating a contract entry for path [" + path + "] and metadata [" + metadata + "]")
}
result.add(parent, metadata)
}
private boolean hasScenarioFilenamePattern(Path path) {
return SCENARIO_STEP_FILENAME_PATTERN.matcher(path.fileName.toString()).matches()
}
private boolean matchesPattern(File file, Set<PathMatcher> matchers) {
for (PathMatcher matcher : matchers) {
if (matcher.matches(file.toPath())) {
return true
}
log.debug("Path [${file.toPath()}] doesn't match the pattern [${matcher}]")
}
return false
}
private boolean isContractFile(File file) {
return file.isFile() && ContractVerifierDslConverter.INSTANCE.isAccepted(file)
}
/**
* 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,72 +0,0 @@
/*
* Copyright 2013-2020 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.file
import groovy.transform.CompileStatic
/**
* Scans the provided file path for the DSLs. There's a possibility to provide
* inclusion and exclusion filters.
*
* @author Jakub Kubrynski, codearte.io
*
* @since 2.1.0
*/
@CompileStatic
class ContractFileScannerBuilder {
private File baseDir
private Set<String> excluded
private Set<String> ignored
private Set<String> included = []
private String includeMatcher = ""
ContractFileScannerBuilder baseDir(File baseDir) {
this.baseDir = baseDir
return this
}
ContractFileScannerBuilder excluded(Set<String> excluded) {
this.excluded = excluded
return this
}
ContractFileScannerBuilder ignored(Set<String> ignored) {
this.ignored = ignored
return this
}
ContractFileScannerBuilder included(Set<String> included) {
this.included = included
return this
}
ContractFileScannerBuilder includeMatcher(String includeMatcher) {
this.includeMatcher = includeMatcher
return this
}
ContractFileScanner build() {
return new ContractFileScanner(this.baseDir,
this.excluded,
this.ignored,
this.included,
this.includeMatcher)
}
}

View File

@@ -1,253 +0,0 @@
/*
* Copyright 2013-2020 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.util
import java.lang.reflect.Constructor
import java.util.function.Supplier
import java.util.regex.Matcher
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.codehaus.groovy.control.CompilerConfiguration
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.ContractConverter
import org.springframework.cloud.function.compiler.java.CompilationResult
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler
import org.springframework.util.StringUtils
/**
* Converts a String or a Groovy or Java file into a {@link Contract}.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
*
* @since 1.0.0
*/
@CompileStatic
@Commons
class ContractVerifierDslConverter implements ContractConverter<Collection<Contract>> {
public static final ContractVerifierDslConverter INSTANCE = new ContractVerifierDslConverter()
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".*package (.+?);.+?", Pattern.DOTALL)
private static final Pattern CLASS_PATTERN = Pattern.compile(".+?class (.+?)( |\\{).+?", Pattern.DOTALL)
private static final RuntimeJavaCompiler COMPILER = new RuntimeJavaCompiler()
/**
* @deprecated - use {@link ContractVerifierDslConverter#convertAsCollection(java.io.File, java.lang.String)}
*/
@Deprecated
static Collection<Contract> convertAsCollection(String dsl) {
try {
Object object = groovyShell().evaluate(dsl)
return listOfContracts(object)
}
catch (DslParseException e) {
throw e
}
catch (Exception e) {
log.error("Exception occurred while trying to evaluate the contract", e)
throw new DslParseException(e)
}
}
static Collection<Contract> convertAsCollection(File rootFolder, String dsl) {
ClassLoader classLoader = ContractVerifierDslConverter.getClassLoader()
try {
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader)
Object object = groovyShell(urlCl, rootFolder).evaluate(dsl)
return listOfContracts(object)
}
catch (DslParseException e) {
throw e
}
catch (Exception e) {
log.error("Exception occurred while trying to evaluate the contract", e)
throw new DslParseException(e)
}
finally {
Thread.currentThread().setContextClassLoader(classLoader)
}
}
static Collection<Contract> convertAsCollection(File dsl) {
return convertAsCollection(dsl.parentFile, dsl)
}
static Collection<Contract> convertAsCollection(File rootFolder, File dsl) {
ClassLoader classLoader = ContractVerifierDslConverter.getClassLoader()
try {
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader)
Object object = toObject(urlCl, rootFolder, dsl)
return listOfContracts(dsl, object)
}
catch (DslParseException e) {
throw e
}
catch (Exception e) {
log.error("Exception occurred while trying to evaluate the contract at path [${dsl.path}]", e)
throw new DslParseException(e)
}
finally {
Thread.currentThread().setContextClassLoader(classLoader)
}
}
private static ClassLoader updatedClassLoader(File rootFolder, ClassLoader classLoader) {
ClassLoader urlCl = URLClassLoader
.newInstance([rootFolder.toURI().toURL()] as URL[], classLoader)
updateTheThreadClassLoader(urlCl)
return urlCl
}
private static void updateTheThreadClassLoader(ClassLoader urlCl) {
Thread.currentThread().setContextClassLoader(urlCl)
}
private static GroovyShell groovyShell() {
return new GroovyShell(ContractVerifierDslConverter.classLoader, new CompilerConfiguration(sourceEncoding: 'UTF-8'))
}
private static Object toObject(ClassLoader cl, File rootFolder, File dsl) {
if (isJava(dsl)) {
try {
return parseJavaFile(cl, dsl)
}
catch (Exception ex) {
if (log.isWarnEnabled()) {
log.warn("Exception occurred while trying to parse the file [" + dsl + "] as a contract. Will not parse it.", ex)
}
return null
}
}
return groovyShell(cl, rootFolder).evaluate(dsl)
}
private static Object parseJavaFile(ClassLoader cl, File dsl) {
Constructor<?> constructor = classConstructor(dsl)
Object newInstance = constructor.newInstance()
if (!newInstance instanceof Supplier) {
if (log.isDebugEnabled()) {
log.debug("The class [" + dsl + "] is not instance of Supplier. Will not parse it as a contract")
}
return null
}
Supplier supplier = (Supplier) newInstance
return supplier.get()
}
private static Constructor<?> classConstructor(File dsl) {
String classText = dsl.text
String fqn = fqn(classText)
CompilationResult compilationResult = COMPILER
.compile(fqn, classText)
if (!compilationResult.wasSuccessful()) {
throw new IllegalStateException("Exceptions occurred while trying to compile the file " + compilationResult.compilationMessages)
}
Class<?> clazz = compilationResult.compiledClasses.find { it.name == fqn}
if (clazz == null) {
throw new IllegalStateException("Class with name [" + fqn + "] not found")
}
Constructor<?> constructor = clazz.getDeclaredConstructor()
constructor.setAccessible(true)
return constructor
}
private static boolean isJava(File dsl) {
return dsl.name.endsWith(".java")
}
private static String fqn(String classText) {
Matcher packageMatcher = PACKAGE_PATTERN.matcher(classText)
String fqn = "";
if (packageMatcher.matches()) {
fqn = packageMatcher.group(1) + "."
}
Matcher classMatcher = CLASS_PATTERN.matcher(classText)
if (!classMatcher.matches()) {
throw new IllegalAccessException("Can't parse the class name")
}
return fqn + classMatcher.group(1)
}
private static GroovyShell groovyShell(ClassLoader cl, File rootFolder) {
return new GroovyShell(cl,
new CompilerConfiguration(sourceEncoding: 'UTF-8',
classpathList: [rootFolder.absolutePath]))
}
private static Collection<Contract> listOfContracts(object) {
if (object instanceof Collection) {
return object as Collection<Contract>
}
else if (!object instanceof Contract) {
throw new DslParseException("Contract is not returning a Contract or list of Contracts")
}
return [object] as Collection<Contract>
}
private static Collection<Contract> listOfContracts(File file, Object object) {
if (object == null) {
return Collections.emptyList()
}
else if (isACollectionOfContracts(object)) {
return withName(file, object as Collection<Contract>)
}
else if (!object instanceof Contract) {
throw new DslParseException("Contract is not returning a Contract or list of Contracts")
}
return withName(file, [object] as Collection<Contract>)
}
private static boolean isACollectionOfContracts(object) {
return object instanceof Collection && ((Collection) object).every { it instanceof Contract }
}
private static Collection<Contract> withName(File file, Collection<Contract> contracts) {
int counter = 0
return contracts.collect {
if (contractNameEmpty(it)) {
it.name(NamesUtil.defaultContractName(file, contracts, counter))
}
counter++
return it
}
}
private static boolean contractNameEmpty(Contract it) {
return it != null && StringUtils.isEmpty(it.name)
}
@Override
boolean isAccepted(File file) {
return file.name.endsWith(".groovy") || file.name.endsWith(".gvy") || file.name.endsWith(".java")
}
@Override
Collection<Contract> convertFrom(File file) {
return convertAsCollection(file)
}
@Override
Collection<Contract> convertTo(Collection<Contract> contract) {
return contract
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.contract.verifier.util
import java.util.function.Function
import java.util.regex.Matcher
import java.util.regex.Pattern
@@ -208,7 +209,9 @@ class JsonToJsonPathsConverter {
while ({
matches << matcher.group()
matcher.find()
}()) continue
}()) {
continue
}
return matches[matches.size() - 1]
}
@@ -335,6 +338,10 @@ class JsonToJsonPathsConverter {
}
}
JsonPaths transformToJsonPathWithTestsSideValues(def json, Function parsingClosure, boolean includeEmptyCheck) {
return transformToJsonPathWithValues(json, SERVER_SIDE, { parsingClosure.apply(it) }, includeEmptyCheck)
}
JsonPaths transformToJsonPathWithTestsSideValues(def json,
Closure parsingClosure = MapConverter.JSON_PARSING_CLOSURE,
boolean includeEmptyCheck = false) {

View File

@@ -1,194 +0,0 @@
/*
* Copyright 2013-2020 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.util
import java.util.function.Function
import groovy.json.JsonSlurper
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.FromFileProperty
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor
import org.springframework.cloud.contract.verifier.template.TemplateProcessor
/**
* Converts an object into either client or server side representation.
* Iterates over the structure of an object (depending on whether it's an
* iterable or a primitive type etc.), converts the {@link DslProperty} into their
* client / server representation and returns the result
*
* @author Marcin Grzejszczak
*
* @since 1.1.0
*/
class MapConverter {
public static final boolean STUB_SIDE = true
public static final boolean TEST_SIDE = false
public static final Closure JSON_PARSING_CLOSURE = { String value ->
new JsonSlurper().parseText(value)
}
public static final Function<String, Object> JSON_PARSING_FUNCTION = { String value ->
new JsonSlurper().parseText(value)
} as Function
private final TemplateProcessor templateProcessor
MapConverter() {
this.templateProcessor = processor()
}
private TemplateProcessor processor() {
return new HandlebarsTemplateProcessor()
}
/**
* @return the object with client side values of {@link org.springframework.cloud.contract.spec.internal.DslProperty}
*/
static def transformToClientValues(def value) {
return transformValues(value) {
it instanceof DslProperty ? it.clientValue : it
}
}
static Closure fromFunction(Function function) {
return {
function.apply(it)
}
}
/**
* Iterates over the structure of the object and executes the closure
* on each element of that structure.
*
* @return the transformed structure
*/
static def transformValues(def value, Closure closure,
Closure parsingClosure = JSON_PARSING_CLOSURE) {
if (value instanceof String && value) {
try {
def parsed = parsingClosure(value)
if (parsed instanceof Map) {
return convert(parsed, closure, parsingClosure)
}
else if (parsed instanceof List) {
return transformValues(parsed, closure, parsingClosure)
}
}
catch (Exception ignore) {
}
return extractValue(value, closure)
}
else if (value instanceof Map) {
return convert(value as Map, closure, parsingClosure)
}
else if (value instanceof List) {
return value.collect({ transformValues(it, closure, parsingClosure) })
}
return transformValue(closure, value, parsingClosure)
}
/**
* Transforms a value with the given closure. Needs to be protected, otherwise
* method access exception will occur at runtime.
*/
protected static Object transformValue(Closure closure, Object value, Closure parsingClosure) {
return extractValue(value, { Object val ->
Object newValue = closure(val)
if (newValue instanceof Map || newValue instanceof List || newValue instanceof String && value) {
return transformValues(newValue, closure, parsingClosure)
}
return newValue
})
}
private static extractValue(Object value, Closure closure) {
try {
return closure(value)
}
catch (Exception ignore) {
return value
}
}
private static Map convert(Map map, Closure closure, Closure parsingClosure) {
return map.collectEntries {
key, value ->
[key, transformValues(value, closure, parsingClosure)]
}
}
/**
* If {@code clientSide} is {@code true} returns the client side value for the
* provided object
*/
static Object getClientOrServerSideValues(json, boolean clientSide,
Closure parsingClosure = JSON_PARSING_CLOSURE) {
return transformValues(json, {
if (it instanceof DslProperty) {
DslProperty dslProperty = ((DslProperty) it)
return clientSide ?
getClientOrServerSideValues(dslProperty.clientValue, clientSide, parsingClosure) :
getClientOrServerSideValues(dslProperty.serverValue, clientSide, parsingClosure)
}
else if (it instanceof GString) {
ContentType type = new MapConverter().templateProcessor.
containsJsonPathTemplateEntry(
ContentUtils.
extractValueForGString(it, ContentUtils.GET_TEST_SIDE).
toString()
) ? ContentType.TEXT : null
return ContentUtils.extractValue(it, type, {
if (it instanceof DslProperty) {
return clientSide ?
getClientOrServerSideValues((it as DslProperty).clientValue, clientSide, parsingClosure) :
getClientOrServerSideValues((it as DslProperty).serverValue, clientSide, parsingClosure)
}
return it
})
}
else if (it instanceof FromFileProperty) {
return it.isByte() ? it.asBytes() : it.asString()
}
return it
}, parsingClosure)
}
static Object getStubSideValues(json, Closure parsingClosure = JSON_PARSING_CLOSURE) {
return getClientOrServerSideValues(json, STUB_SIDE, parsingClosure)
}
static Object getTestSideValues(json, Function function) {
return getClientOrServerSideValues(json, TEST_SIDE, { function.apply(it) })
}
static Object getTestSideValues(json, Closure parsingClosure = JSON_PARSING_CLOSURE) {
return getClientOrServerSideValues(json, TEST_SIDE, parsingClosure)
}
static Object getTestSideValuesForText(json) {
return getClientOrServerSideValues(json, TEST_SIDE, Closure.IDENTITY)
}
static Object getStubSideValuesForNonBody(object) {
return getClientOrServerSideValues(object, STUB_SIDE, Closure.IDENTITY)
}
static Object getTestSideValuesForNonBody(object) {
return getClientOrServerSideValues(object, TEST_SIDE, Closure.IDENTITY)
}
}

View File

@@ -1,167 +0,0 @@
/*
* Copyright 2013-2020 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.util
import java.util.regex.Pattern
import groovy.transform.TypeChecked
import org.codehaus.groovy.runtime.GStringImpl
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.util.RegexpUtils
import static ContentUtils.extractValue
import static org.apache.commons.text.StringEscapeUtils.escapeJson
import static org.springframework.cloud.contract.verifier.util.ContentType.JSON
/**
* Useful utility methods to work with regular expressions
*
* Do not change to {@code @CompileStatic} since it's using double dispatch.
*
* @since 1.0.0
*/
@TypeChecked
class RegexpBuilders {
/**
* Converts the {@link GString} passed values into their stub side String representations
*/
static String buildGStringRegexpForStubSide(GString gString) {
new GStringImpl(
gString.values.collect(this.&buildGStringRegexpForStubSide) as Object[],
gString.strings.collect(this.&escapeSpecialRegexChars) as String[]
)
}
/**
* Converts the {@link Pattern} passed values into their stub side String representations
*/
static String buildGStringRegexpForStubSide(Pattern pattern) {
return pattern.pattern()
}
/**
* Converts the {@link org.springframework.cloud.contract.spec.internal.DslProperty} passed values into their stub side String representations
*/
static String buildGStringRegexpForStubSide(DslProperty dslProperty) {
return buildGStringRegexpForStubSide(dslProperty.clientValue)
}
/**
* Converts the {@link Object} passed values into their stub side String representations
*/
static String buildGStringRegexpForStubSide(Object o) {
if (o instanceof DslProperty) {
return buildGStringRegexpForStubSide((DslProperty) o)
}
else if (o instanceof Pattern) {
return buildGStringRegexpForStubSide((Pattern) o)
}
else if (o instanceof GString) {
return buildGStringRegexpForStubSide((GString) o)
}
return escapeSpecialRegexChars(o.toString())
}
/**
* Converts the {@link GString} passed values into their test side String representations
*/
static String buildGStringRegexpForTestSide(GString gString) {
new GStringImpl(
gString.values.collect(this.&buildGStringRegexpForTestSide) as Object[],
gString.strings.collect(this.&escapeSpecialRegexChars) as String[]
)
}
/**
* Converts the {@link Pattern} passed values into their test side String representations
*/
static String buildGStringRegexpForTestSide(Pattern pattern) {
return pattern.pattern()
}
/**
* Converts the {@link DslProperty} passed values into their test side String representations
*/
static String buildGStringRegexpForTestSide(DslProperty dslProperty) {
return buildGStringRegexpForTestSide(dslProperty.clientValue)
}
/**
* Converts the {@link Object} passed values into their test side String representations
*/
static String buildGStringRegexpForTestSide(Object o) {
return o.toString().replaceAll('\\\\', '\\\\\\\\')
}
static String escapeSpecialRegexChars(String str) {
return RegexpUtils.escapeSpecialRegexChars(str)
}
private final static String WS = /\s*/
static String buildJSONRegexpMatch(GString gString) {
return buildJSONRegexpMatch(
extractValue(gString, JSON, { DslProperty dslProperty -> dslProperty.clientValue }))
}
static String buildJSONRegexpMatch(Map jsonMap) {
return WS + "\\{" + jsonMap.collect(this.&buildJSONRegexpMatch).join(",") + "\\}" + WS
}
static String buildJSONRegexpMatch(List jsonList) {
return WS + "\\[" + jsonList.collect(this.&buildJSONRegexpMatch).join(",") + "\\]" + WS
}
/**
* Converts the map into String representation of regular expressions
*/
static String buildJSONRegexpMatch(Map.Entry<String, Object> entry) {
return buildJSONRegexpMatchString(escapeJson(entry.key)) + ":" +
buildJSONRegexpMatch(entry.value)
}
/**
* Converts the object into String representation of regular expressions
*/
static String buildJSONRegexpMatch(Object value) {
return buildJSONRegexpMatchStringOptionalQuotes(escapeJson(value.toString()))
}
/**
* Converts the pattern into String representation of regular expressions
*/
static String buildJSONRegexpMatch(Pattern pattern) {
return buildJSONRegexpMatchStringOptionalQuotes(pattern.pattern())
}
/**
* Converts the String into String representation of regular expressions
*/
static String buildJSONRegexpMatchString(String value) {
return WS + '"' + value + '"' + WS
}
/**
* Converts the String into an optional String representation of regular expressions
*/
static String buildJSONRegexpMatchStringOptionalQuotes(String value) {
return WS + '"?' + value + '"?' + WS
}
}

View File

@@ -37,7 +37,6 @@ import org.springframework.cloud.contract.verifier.builder.JavaTestGenerator;
import org.springframework.cloud.contract.verifier.builder.SingleTestGenerator;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.file.ContractFileScanner;
import org.springframework.cloud.contract.verifier.file.ContractFileScannerBuilder;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.MultiValueMap;
@@ -85,7 +84,7 @@ public class TestGenerator {
public TestGenerator(ContractVerifierConfigProperties configProperties, SingleTestGenerator generator,
FileSaver saver) {
this(configProperties, generator, saver, new ContractFileScannerBuilder()
this(configProperties, generator, saver, ContractFileScanner.builder()
.baseDir(configProperties.getContractsDslDir()).excluded(toSet(configProperties.getExcludedFiles()))
.ignored(toSet(configProperties.getIgnoredFiles())).included(toSet(configProperties.getIncludedFiles()))
.includeMatcher(configProperties.getIncludedContracts()).build());

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2020 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.assertion;
import org.assertj.core.api.Assertions;
public class SpringCloudContractAssertions extends Assertions {
/**
* Creates a new instance of <code>{@link CollectionAssert}</code>.
* @param <ELEMENT> type to assert
* @param actual the actual value.
* @return the created assertion object.
*/
public static <ELEMENT> CollectionAssert<ELEMENT> assertThat(Iterable<? extends ELEMENT> actual) {
return new CollectionAssert<>(actual);
}
}

View File

@@ -18,10 +18,10 @@ package org.springframework.cloud.contract.verifier.builder;
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.Closure;
import groovy.lang.GString;
import org.apache.commons.text.StringEscapeUtils;
@@ -117,14 +117,15 @@ interface BodyParser extends BodyThen {
*/
default Object extractServerValueFromBody(ContentType contentType, Object bodyValue) {
if (bodyValue instanceof GString) {
return extractValue((GString) bodyValue, contentType, ContentUtils.GET_TEST_SIDE);
return extractValue((GString) bodyValue, contentType, ContentUtils.GET_TEST_SIDE_FUNCTION);
}
else if (bodyValue instanceof FromFileProperty) {
return MapConverter.transformValues(bodyValue, ContentUtils.GET_TEST_SIDE);
return MapConverter.transformValues(bodyValue, ContentUtils.GET_TEST_SIDE_FUNCTION);
}
else if (TEXT != contentType && FORM != contentType && DEFINED != contentType) {
boolean dontParseStrings = contentType == JSON && bodyValue instanceof Map;
Closure parsingClosure = dontParseStrings ? Closure.IDENTITY : MapConverter.JSON_PARSING_CLOSURE;
Function<String, Object> parsingClosure = dontParseStrings ? MapConverter.IDENTITY
: MapConverter.JSON_PARSING_FUNCTION;
return MapConverter.getTestSideValues(bodyValue, parsingClosure);
}
return bodyValue;

View File

@@ -26,7 +26,6 @@ import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import groovy.json.JsonOutput;
import groovy.lang.Closure;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.cloud.contract.spec.Contract;
@@ -86,10 +85,11 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
appendJsonPath(bb, responseString);
DocumentContext parsedRequestBody = null;
boolean dontParseStrings = convertedResponseBody instanceof Map;
Closure parsingClosure = dontParseStrings ? Closure.IDENTITY : MapConverter.JSON_PARSING_CLOSURE;
Function<String, Object> parsingFunction = dontParseStrings ? MapConverter.IDENTITY
: MapConverter.JSON_PARSING_FUNCTION;
if (hasRequestBody()) {
Object testSideRequestBody = MapConverter.getTestSideValues(contract.getRequest().getBody(),
parsingClosure);
parsingFunction);
parsedRequestBody = JsonPath.parse(testSideRequestBody);
if (convertedResponseBody instanceof String
&& !textContainsJsonPathTemplate(convertedResponseBody.toString())) {
@@ -108,9 +108,9 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
TestSideRequestTemplateModel templateModel = hasRequestBody()
? TestSideRequestTemplateModel.from(contract.getRequest()) : null;
convertedResponseBody = MapConverter.transformValues(convertedResponseBody,
returnReferencedEntries(templateModel), parsingClosure);
returnReferencedEntries(templateModel), parsingFunction);
JsonPaths jsonPaths = new JsonToJsonPathsConverter(assertJsonSize)
.transformToJsonPathWithTestsSideValues(convertedResponseBody, parsingClosure, includeEmptyCheck);
.transformToJsonPathWithTestsSideValues(convertedResponseBody, parsingFunction, includeEmptyCheck);
DocumentContext finalParsedRequestBody = parsedRequestBody;
jsonPaths.forEach(it -> {
@@ -280,8 +280,8 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
}
}
private Closure<Object> returnReferencedEntries(TestSideRequestTemplateModel templateModel) {
return MapConverter.fromFunction(entry -> {
private Function<Object, ?> returnReferencedEntries(TestSideRequestTemplateModel templateModel) {
return entry -> {
if (!(entry instanceof String) || templateModel == null) {
return entry;
}
@@ -313,7 +313,7 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
}
}
return entry;
});
};
}
private static String minus(CharSequence self, Object target) {

View File

@@ -193,7 +193,7 @@ public class TestSideRequestTemplateModel {
bodyValue = ContentUtils.extractValue((GString) bodyValue, ContentUtils.GET_TEST_SIDE_FUNCTION);
}
else {
bodyValue = MapConverter.transformValues(bodyValue, ContentUtils.GET_TEST_SIDE);
bodyValue = MapConverter.transformValues(bodyValue, ContentUtils.GET_TEST_SIDE_FUNCTION);
}
return bodyValue;
}

View File

@@ -0,0 +1,585 @@
/*
* Copyright 2013-2020 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.converter;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Cookies;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ExecutionProperty;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.Input;
import org.springframework.cloud.contract.spec.internal.MatchingStrategy;
import org.springframework.cloud.contract.spec.internal.MatchingType;
import org.springframework.cloud.contract.spec.internal.Multipart;
import org.springframework.cloud.contract.spec.internal.NamedProperty;
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.spec.internal.QueryParameter;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Response;
import org.springframework.cloud.contract.spec.internal.Url;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import static org.springframework.cloud.contract.verifier.util.ContentType.XML;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType;
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Stessy Delcroix
*/
class ContractsToYaml {
private static final Map<Class<?>, YamlContract.RegexType> PRIMITIVE_WRAPPER_TO_REGEX_TYPE = new HashMap<>();
private static final Map<MatchingType, YamlContract.TestMatcherType> TEST_MATCHER_TYPE = new HashMap<>();
private static final Map<MatchingType, YamlContract.StubMatcherType> STUB_MATCHER_TYPE = new HashMap<>();
static {
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Boolean.class, YamlContract.RegexType.as_boolean);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Long.class, YamlContract.RegexType.as_long);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Short.class, YamlContract.RegexType.as_short);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Integer.class, YamlContract.RegexType.as_integer);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Float.class, YamlContract.RegexType.as_float);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(Double.class, YamlContract.RegexType.as_double);
PRIMITIVE_WRAPPER_TO_REGEX_TYPE.put(String.class, YamlContract.RegexType.as_string);
TEST_MATCHER_TYPE.put(MatchingType.EQUALITY, YamlContract.TestMatcherType.by_equality);
TEST_MATCHER_TYPE.put(MatchingType.TYPE, YamlContract.TestMatcherType.by_type);
TEST_MATCHER_TYPE.put(MatchingType.COMMAND, YamlContract.TestMatcherType.by_command);
TEST_MATCHER_TYPE.put(MatchingType.DATE, YamlContract.TestMatcherType.by_date);
TEST_MATCHER_TYPE.put(MatchingType.TIME, YamlContract.TestMatcherType.by_time);
TEST_MATCHER_TYPE.put(MatchingType.TIMESTAMP, YamlContract.TestMatcherType.by_timestamp);
TEST_MATCHER_TYPE.put(MatchingType.REGEX, YamlContract.TestMatcherType.by_regex);
TEST_MATCHER_TYPE.put(MatchingType.NULL, YamlContract.TestMatcherType.by_null);
STUB_MATCHER_TYPE.put(MatchingType.EQUALITY, YamlContract.StubMatcherType.by_equality);
STUB_MATCHER_TYPE.put(MatchingType.DATE, YamlContract.StubMatcherType.by_date);
STUB_MATCHER_TYPE.put(MatchingType.TIME, YamlContract.StubMatcherType.by_time);
STUB_MATCHER_TYPE.put(MatchingType.TIMESTAMP, YamlContract.StubMatcherType.by_timestamp);
STUB_MATCHER_TYPE.put(MatchingType.REGEX, YamlContract.StubMatcherType.by_regex);
}
List<YamlContract> convertTo(Collection<Contract> contracts) {
return contracts.stream().map(contract -> {
YamlContract yamlContract = new YamlContract();
if (contract == null) {
return yamlContract;
}
yamlContract.name = contract.getName();
yamlContract.ignored = contract.getIgnored();
yamlContract.inProgress = contract.getInProgress();
yamlContract.description = contract.getDescription();
yamlContract.label = contract.getLabel();
yamlContract.metadata = contract.getMetadata();
request(contract, yamlContract);
response(yamlContract, contract);
input(contract, yamlContract);
output(contract, yamlContract);
return yamlContract;
}).collect(Collectors.toList());
}
protected void request(Contract contract, YamlContract yamlContract) {
Request request = contract.getRequest();
if (request != null) {
ContentType requestContentType = evaluateClientSideContentType(request.getHeaders(), request.getBody());
yamlContract.request = new YamlContract.Request();
mapRequestMethod(yamlContract.request, request);
mapRequestUrl(yamlContract.request, request);
mapRequestUrlPath(yamlContract.request, request);
mapRequestMatchers(yamlContract.request);
Url requestUrl = Optional.ofNullable(request.getUrl()).orElse(request.getUrlPath());
if (requestUrl.getQueryParameters() != null) {
mapRequestQueryParameters(yamlContract.request, requestUrl);
mapRequestMatchersQueryParameters(yamlContract.request, requestUrl);
}
mapRequestHeaders(yamlContract.request, request);
mapRequestCookies(yamlContract.request, request);
mapRequestBody(yamlContract.request, request);
mapRequestMultipart(yamlContract.request, request);
mapRequestMatchersBody(yamlContract.request, request);
mapRequestMatchersUrl(yamlContract.request, request);
mapRequestMatchersMultipart(yamlContract.request, request);
// TODO: Cookie matchers - including absent
if (XML != requestContentType) {
setInputBodyMatchers(request.getBody(), yamlContract.request.matchers.body);
}
setInputHeadersMatchers(request.getHeaders(), yamlContract.request.matchers.headers);
}
}
private void mapRequestMatchersMultipart(YamlContract.Request yamlContractRequest, Request request) {
Multipart multipart = request.getMultipart();
if (multipart != null) {
yamlContractRequest.matchers.multipart = new YamlContract.MultipartStubMatcher();
Map<String, Object> map = (Map<String, Object>) MapConverter.getStubSideValues(multipart);
map.forEach((key, value) -> {
if (value instanceof NamedProperty) {
Object fileName = Optional.ofNullable(((NamedProperty) value).getName())
.map(DslProperty::getClientValue).orElse(null);
Object fileContent = Optional.ofNullable(((NamedProperty) value).getValue())
.map(DslProperty::getClientValue).orElse(null);
Object contentType = Optional.ofNullable(((NamedProperty) value).getContentType())
.map(DslProperty::getClientValue).orElse(null);
if (fileName instanceof RegexProperty || fileContent instanceof RegexProperty
|| contentType instanceof RegexProperty) {
YamlContract.MultipartNamedStubMatcher multipartNamedStubMatcher = new YamlContract.MultipartNamedStubMatcher();
multipartNamedStubMatcher.paramName = key;
multipartNamedStubMatcher.fileName = valueMatcher(fileName);
multipartNamedStubMatcher.fileContent = valueMatcher(fileContent);
multipartNamedStubMatcher.contentType = valueMatcher(contentType);
yamlContractRequest.matchers.multipart.named.add(multipartNamedStubMatcher);
}
}
else if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value);
YamlContract.KeyValueMatcher keyValueMatcher = new YamlContract.KeyValueMatcher();
keyValueMatcher.key = key;
keyValueMatcher.regex = property.pattern();
keyValueMatcher.regexType = regexType(property.clazz());
yamlContractRequest.matchers.multipart.params.add(keyValueMatcher);
}
});
}
}
private void mapRequestMatchersUrl(YamlContract.Request yamlContractRequest, Request request) {
Object url = Optional.ofNullable(request.getUrl()).map(Url::getClientValue).orElse(null);
YamlContract.KeyValueMatcher keyValueMatcher = new YamlContract.KeyValueMatcher();
if (url instanceof RegexProperty) {
keyValueMatcher.regex = ((RegexProperty) url).pattern();
yamlContractRequest.matchers.url = keyValueMatcher;
}
else if (url instanceof ExecutionProperty) {
keyValueMatcher.command = url.toString();
yamlContractRequest.matchers.url = keyValueMatcher;
}
else {
yamlContractRequest.matchers.url = null;
}
Object urlPath = Optional.ofNullable(request.getUrlPath()).map(Url::getClientValue).orElse(null);
if (urlPath instanceof RegexProperty) {
keyValueMatcher.regex = ((RegexProperty) urlPath).pattern();
yamlContractRequest.matchers.url = keyValueMatcher;
}
else if (urlPath instanceof ExecutionProperty) {
keyValueMatcher.command = urlPath.toString();
yamlContractRequest.matchers.url = keyValueMatcher;
}
else {
yamlContractRequest.matchers.url = null;
}
}
private void mapRequestMatchersBody(YamlContract.Request yamlContractRequest, Request request) {
Optional.ofNullable(request.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach(bodyMatcher -> {
YamlContract.BodyStubMatcher bodyStubMatcher = new YamlContract.BodyStubMatcher();
bodyStubMatcher.path = bodyMatcher.path();
bodyStubMatcher.type = stubMatcherType(bodyMatcher.matchingType());
bodyStubMatcher.value = Optional.ofNullable(bodyMatcher.value()).map(Object::toString).orElse(null);
bodyStubMatcher.minOccurrence = bodyMatcher.minTypeOccurrence();
bodyStubMatcher.maxOccurrence = bodyMatcher.maxTypeOccurrence();
yamlContractRequest.matchers.body.add(bodyStubMatcher);
}));
}
private void mapRequestMultipart(YamlContract.Request yamlContractRequest, Request request) {
Multipart multipart = request.getMultipart();
if (multipart != null) {
yamlContractRequest.multipart = new YamlContract.Multipart();
Map<String, Object> map = (Map<String, Object>) MapConverter.getTestSideValues(multipart);
map.forEach((key, value) -> {
if (value instanceof NamedProperty) {
Object fileName = Optional.ofNullable(((NamedProperty) value).getName())
.map(DslProperty::getServerValue).orElse(null);
Object contentType = Optional.ofNullable(((NamedProperty) value).getContentType())
.map(DslProperty::getServerValue).orElse(null);
Object fileContent = Optional.ofNullable(((NamedProperty) value).getValue())
.map(DslProperty::getServerValue).orElse(null);
YamlContract.Named named = new YamlContract.Named();
named.paramName = key;
named.fileName = fileName instanceof String ? Optional.ofNullable(((NamedProperty) value).getName())
.map(DslProperty::getServerValue).map(Object::toString).orElse(null) : null;
named.fileContent = (String) Optional.ofNullable(fileContent).filter(f -> f instanceof String)
.orElse(null);
named.fileContentAsBytes = fileContent instanceof FromFileProperty
? new String(((FromFileProperty) fileContent).asBytes()) : null;
named.fileContentFromFileAsBytes = resolveFileNameAsBytes(fileContent);
named.contentType = (String) Optional.ofNullable(contentType).filter(f -> f instanceof String)
.orElse(null);
named.fileNameCommand = fileName instanceof ExecutionProperty ? fileName.toString() : null;
named.fileContentCommand = fileContent instanceof ExecutionProperty ? fileContent.toString() : null;
named.contentTypeCommand = contentType instanceof ExecutionProperty ? contentType.toString() : null;
yamlContractRequest.multipart.named.add(named);
}
else {
yamlContractRequest.multipart.params.put(key, value != null ? value.toString() : null);
}
});
}
}
private void mapRequestBody(YamlContract.Request yamlContractRequest, Request request) {
Object body = Optional.ofNullable(request.getBody()).map(DslProperty::getServerValue).orElse(null);
if (body instanceof FromFileProperty) {
FromFileProperty fromFileProperty = (FromFileProperty) body;
if (fromFileProperty.isByte()) {
yamlContractRequest.bodyFromFileAsBytes = fromFileProperty.fileName();
}
if (fromFileProperty.isString()) {
yamlContractRequest.bodyFromFile = fromFileProperty.fileName();
}
}
else {
yamlContractRequest.body = MapConverter.getTestSideValues(request.getBody());
}
}
private void mapRequestCookies(YamlContract.Request yamlContractRequest, Request request) {
yamlContractRequest.cookies = Optional.ofNullable(request.getCookies()).map(Cookies::asTestSideMap)
.orElse(null);
}
private void mapRequestHeaders(YamlContract.Request yamlContractRequest, Request request) {
yamlContractRequest.headers = request.getHeaders().asMap((headerName, prop) -> {
Object testSideValue = MapConverter.getTestSideValues(prop);
if (testSideValue instanceof ExecutionProperty) {
return MapConverter.getStubSideValuesForNonBody(prop).toString();
}
return testSideValue.toString();
});
}
private void mapRequestMatchersQueryParameters(YamlContract.Request yamlContractRequest, Url requestUrl) {
yamlContractRequest.matchers.queryParameters
.addAll(requestUrl.getQueryParameters().getParameters().stream().map(parameter -> {
Object stubSide = parameter.getClientValue();
if (stubSide instanceof RegexProperty || stubSide instanceof Pattern) {
YamlContract.QueryParameterMatcher queryParameterMatcher = new YamlContract.QueryParameterMatcher();
queryParameterMatcher.key = parameter.getName();
queryParameterMatcher.type = YamlContract.MatchingType.matching;
queryParameterMatcher.value = new RegexProperty(stubSide).pattern();
return queryParameterMatcher;
}
else if (stubSide instanceof MatchingStrategy) {
YamlContract.QueryParameterMatcher queryParameterMatcher = new YamlContract.QueryParameterMatcher();
queryParameterMatcher.key = parameter.getName();
queryParameterMatcher.type = YamlContract.MatchingType
.from(((MatchingStrategy) stubSide).getType().getName());
queryParameterMatcher.value = MapConverter.getStubSideValuesForNonBody(stubSide);
return queryParameterMatcher;
}
else {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList()));
}
private void mapRequestQueryParameters(YamlContract.Request yamlContractRequest, Url requestUrl) {
yamlContractRequest.queryParameters = requestUrl.getQueryParameters().getParameters().stream()
.collect(Collectors.toMap(QueryParameter::getName, MapConverter::getTestSideValuesForNonBody));
}
private void mapRequestMatchers(YamlContract.Request yamlContractRequest) {
yamlContractRequest.matchers = new YamlContract.StubMatchers();
}
private void mapRequestUrlPath(YamlContract.Request yamlContractRequest, Request request) {
yamlContractRequest.urlPath = Optional.ofNullable(request.getUrlPath()).map(m -> m.getServerValue().toString())
.orElse(null);
}
private void mapRequestUrl(YamlContract.Request yamlContractRequest, Request request) {
yamlContractRequest.url = Optional.ofNullable(request.getUrl()).map(m -> m.getServerValue().toString())
.orElse(null);
}
private void mapRequestMethod(YamlContract.Request yamlContractRequest, Request request) {
yamlContractRequest.method = Optional.ofNullable(request.getMethod()).map(m -> m.getServerValue().toString())
.orElse(null);
}
protected void output(Contract contract, YamlContract yamlContract) {
OutputMessage outputMessage = contract.getOutputMessage();
if (outputMessage != null) {
Optional<Response> optionalResponse = Optional.ofNullable(contract.getResponse());
ContentType contentType = evaluateClientSideContentType(
optionalResponse.map(Response::getHeaders).orElse(null),
optionalResponse.map(Response::getBody).orElse(null));
yamlContract.outputMessage = new YamlContract.OutputMessage();
yamlContract.outputMessage.sentTo = MapConverter.getStubSideValues(outputMessage.getSentTo()).toString();
yamlContract.outputMessage.headers = Optional.ofNullable(outputMessage.getHeaders())
.map(Headers::asStubSideMap).orElse(null);
yamlContract.outputMessage.body = MapConverter.getStubSideValues(outputMessage.getBody());
Optional.ofNullable(outputMessage.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach(bodyMatcher -> {
YamlContract.BodyTestMatcher bodyTestMatcher = new YamlContract.BodyTestMatcher();
bodyTestMatcher.path = bodyMatcher.path();
bodyTestMatcher.type = testMatcherType(bodyMatcher.matchingType());
bodyTestMatcher.value = Optional.ofNullable(bodyMatcher.value()).map(Object::toString)
.orElse(null);
bodyTestMatcher.minOccurrence = bodyMatcher.minTypeOccurrence();
bodyTestMatcher.maxOccurrence = bodyMatcher.maxTypeOccurrence();
yamlContract.outputMessage.matchers.body.add(bodyTestMatcher);
}));
if (XML != contentType) {
setOutputBodyMatchers(outputMessage.getBody(), yamlContract.outputMessage.matchers.body);
}
setOutputHeadersMatchers(outputMessage.getHeaders(), yamlContract.outputMessage.matchers.headers);
}
}
protected void input(Contract contract, YamlContract yamlContract) {
Input input = contract.getInput();
if (input != null) {
ContentType contentType = evaluateClientSideContentType(input.getMessageHeaders(), input.getMessageBody());
yamlContract.input = new YamlContract.Input();
yamlContract.input.assertThat = Optional.ofNullable(input.getAssertThat())
.map(assertThat -> MapConverter
.getTestSideValues(assertThat.toString(), MapConverter.JSON_PARSING_FUNCTION).toString())
.orElse(null);
yamlContract.input.triggeredBy = Optional.ofNullable(input.getTriggeredBy())
.map(triggeredBy -> MapConverter
.getTestSideValues(triggeredBy.toString(), MapConverter.JSON_PARSING_FUNCTION).toString())
.orElse(null);
yamlContract.input.messageHeaders = input.getMessageHeaders().asTestSideMap();
yamlContract.input.messageBody = MapConverter.getTestSideValues(input.getMessageBody(),
MapConverter.JSON_PARSING_FUNCTION);
yamlContract.input.messageFrom = Optional
.ofNullable(input.getMessageFrom()).map(messageFrom -> MapConverter
.getTestSideValues(messageFrom, MapConverter.JSON_PARSING_FUNCTION).toString())
.orElse(null);
Optional.ofNullable(input.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach(bodyMatcher -> {
YamlContract.BodyStubMatcher bodyStubMatcher = new YamlContract.BodyStubMatcher();
bodyStubMatcher.path = bodyMatcher.path();
bodyStubMatcher.type = stubMatcherType(bodyMatcher.matchingType());
bodyStubMatcher.value = Optional.ofNullable(bodyMatcher.value()).map(Object::toString)
.orElse(null);
yamlContract.input.matchers.body.add(bodyStubMatcher);
}));
if (XML != contentType) {
setInputBodyMatchers(input.getMessageBody(), yamlContract.input.matchers.body);
}
setInputHeadersMatchers(input.getMessageHeaders(), yamlContract.input.matchers.headers);
}
}
protected String resolveFileNameAsBytes(Object value) {
if (!(value instanceof FromFileProperty)) {
return null;
}
FromFileProperty property = (FromFileProperty) value;
return property.fileName();
}
protected YamlContract.ValueMatcher valueMatcher(Object o) {
return Optional.ofNullable(o).filter(object -> object instanceof RegexProperty)
.map(object -> (RegexProperty) object).map(regexProperty -> {
YamlContract.ValueMatcher valueMatcher = new YamlContract.ValueMatcher();
valueMatcher.regex = regexProperty.pattern();
return valueMatcher;
}).orElse(null);
}
protected void setInputBodyMatchers(DslProperty<?> body, List<YamlContract.BodyStubMatcher> bodyMatchers) {
Object testSideValues = MapConverter.getTestSideValues(body);
JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithStubsSideValues(body);
paths.stream().filter((path) -> path.valueBeforeChecking() instanceof Pattern).forEach((path) -> {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, path.keyBeforeChecking());
YamlContract.BodyStubMatcher bodyStubMatcher = new YamlContract.BodyStubMatcher();
bodyStubMatcher.path = path.keyBeforeChecking();
bodyStubMatcher.type = YamlContract.StubMatcherType.by_regex;
bodyStubMatcher.value = ((Pattern) path.valueBeforeChecking()).pattern();
bodyStubMatcher.regexType = regexType(element);
bodyMatchers.add(bodyStubMatcher);
});
}
protected YamlContract.RegexType regexType(Object from) {
return regexType(from.getClass());
}
protected YamlContract.RegexType regexType(Class<?> clazz) {
return PRIMITIVE_WRAPPER_TO_REGEX_TYPE.getOrDefault(clazz, PRIMITIVE_WRAPPER_TO_REGEX_TYPE.get(String.class));
}
protected void response(YamlContract yamlContract, Contract contract) {
if (contract.getResponse() != null) {
Response contractResponse = contract.getResponse();
ContentType contentType = evaluateClientSideContentType(contractResponse.getHeaders(),
contractResponse.getBody());
YamlContract.Response response = new YamlContract.Response();
yamlContract.response = response;
mapResponseAsync(contractResponse, response);
mapResponseFixedDelayMilliseconds(contractResponse, response);
mapResponseStatus(contractResponse, response);
mapResponseHeaders(contractResponse, response);
mapResponseCookies(contractResponse, response);
mapResponseBody(contractResponse, response);
mapResponseBodyMatchers(contractResponse, response);
if (XML != contentType) {
setOutputBodyMatchers(contractResponse.getBody(), yamlContract.response.matchers.body);
}
setOutputHeadersMatchers(contractResponse.getHeaders(), yamlContract.response.matchers.headers);
}
}
private void mapResponseBodyMatchers(Response contractResponse, YamlContract.Response response) {
Optional.ofNullable(contractResponse.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach((bodyMatcher) -> {
YamlContract.BodyTestMatcher bodyTestMatcher = new YamlContract.BodyTestMatcher();
bodyTestMatcher.path = bodyMatcher.path();
bodyTestMatcher.type = testMatcherType(bodyMatcher.matchingType());
bodyTestMatcher.value = Optional.ofNullable(bodyMatcher.value()).map(Object::toString).orElse(null);
bodyTestMatcher.minOccurrence = bodyMatcher.minTypeOccurrence();
bodyTestMatcher.maxOccurrence = bodyMatcher.maxTypeOccurrence();
response.matchers.body.add(bodyTestMatcher);
}));
}
private void mapResponseBody(Response contractResponse, YamlContract.Response response) {
Object body = Optional.ofNullable(contractResponse.getBody()).map(DslProperty::getClientValue).orElse(null);
if (body instanceof FromFileProperty) {
if (((FromFileProperty) body).isByte()) {
response.bodyFromFileAsBytes = ((FromFileProperty) body).fileName();
}
if (((FromFileProperty) body).isString()) {
response.bodyFromFile = ((FromFileProperty) body).fileName();
}
}
else {
response.body = MapConverter.getStubSideValues(contractResponse.getBody());
}
}
private void mapResponseCookies(Response contractResponse, YamlContract.Response response) {
response.cookies = Optional.ofNullable(contractResponse.getCookies()).map(Cookies::asStubSideMap).orElse(null);
}
private void mapResponseHeaders(Response contractResponse, YamlContract.Response response) {
response.headers = Optional.ofNullable(contractResponse.getHeaders())
.map(headers -> headers.asMap((headerName, dslProperty) -> MapConverter.getStubSideValues(dslProperty)))
.orElse(null);
}
private void mapResponseStatus(Response contractResponse, YamlContract.Response response) {
response.status = Optional.ofNullable(contractResponse.getStatus()).map(DslProperty::getClientValue)
.map(clientValue -> (Integer) clientValue).orElse(null);
}
private void mapResponseFixedDelayMilliseconds(Response contractResponse, YamlContract.Response response) {
response.fixedDelayMilliseconds = (Integer) Optional.ofNullable(contractResponse.getDelay())
.map(DslProperty::getClientValue).orElse(null);
}
private void mapResponseAsync(Response contractResponse, YamlContract.Response response) {
response.async = contractResponse.getAsync();
}
protected void setOutputBodyMatchers(DslProperty<?> body, List<YamlContract.BodyTestMatcher> bodyMatchers) {
Object testSideValues = MapConverter.getTestSideValues(body);
JsonPaths paths = new JsonToJsonPathsConverter().transformToJsonPathWithTestsSideValues(body);
paths.stream().filter(m -> m.valueBeforeChecking() instanceof Pattern).forEach((m) -> {
Object element = JsonToJsonPathsConverter.readElement(testSideValues, m.keyBeforeChecking());
YamlContract.BodyTestMatcher bodyTestMatcher = new YamlContract.BodyTestMatcher();
bodyTestMatcher.path = m.keyBeforeChecking();
bodyTestMatcher.type = YamlContract.TestMatcherType.by_regex;
bodyTestMatcher.value = ((Pattern) m.valueBeforeChecking()).pattern();
bodyTestMatcher.regexType = regexType(element);
bodyMatchers.add(bodyTestMatcher);
});
Optional.ofNullable(body).filter(b -> b.getServerValue() instanceof Pattern).ifPresent((b) -> {
YamlContract.BodyTestMatcher bodyTestMatcher = new YamlContract.BodyTestMatcher();
bodyTestMatcher.type = YamlContract.TestMatcherType.by_regex;
bodyTestMatcher.value = ((Pattern) b.getServerValue()).pattern();
bodyMatchers.add(bodyTestMatcher);
});
}
protected void setInputHeadersMatchers(Headers headers, List<YamlContract.KeyValueMatcher> headerMatchers) {
Optional.ofNullable(headers).map(Headers::asStubSideMap)
.ifPresent(stubSideMap -> stubSideMap.forEach((key, value) -> {
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value);
YamlContract.KeyValueMatcher keyValueMatcher = new YamlContract.KeyValueMatcher();
keyValueMatcher.key = key;
keyValueMatcher.regex = property.pattern();
keyValueMatcher.regexType = regexType(property.clazz());
headerMatchers.add(keyValueMatcher);
}
}));
}
protected void setOutputHeadersMatchers(Headers headers, List<YamlContract.TestHeaderMatcher> headerMatchers) {
Optional.ofNullable(headers).map(Headers::asTestSideMap)
.ifPresent(testSideMap -> testSideMap.forEach((key, value) -> {
if (value instanceof RegexProperty || value instanceof Pattern) {
RegexProperty property = new RegexProperty(value);
YamlContract.TestHeaderMatcher testHeaderMatcher = new YamlContract.TestHeaderMatcher();
testHeaderMatcher.key = key;
testHeaderMatcher.regex = property.pattern();
testHeaderMatcher.regexType = regexType(property.clazz());
headerMatchers.add(testHeaderMatcher);
}
else if (value instanceof ExecutionProperty) {
YamlContract.TestHeaderMatcher testHeaderMatcher = new YamlContract.TestHeaderMatcher();
testHeaderMatcher.key = key;
testHeaderMatcher.command = ((ExecutionProperty) value).getExecutionCommand();
headerMatchers.add(testHeaderMatcher);
}
else if (value instanceof NotToEscapePattern) {
YamlContract.TestHeaderMatcher testHeaderMatcher = new YamlContract.TestHeaderMatcher();
testHeaderMatcher.key = key;
testHeaderMatcher.regex = (((NotToEscapePattern) value).getServerValue()).pattern();
headerMatchers.add(testHeaderMatcher);
}
}));
}
protected YamlContract.TestMatcherType testMatcherType(MatchingType matchingType) {
return TEST_MATCHER_TYPE.getOrDefault(matchingType, null);
}
protected YamlContract.StubMatcherType stubMatcherType(MatchingType matchingType) {
if (matchingType == MatchingType.COMMAND || matchingType == MatchingType.TYPE) {
throw new UnsupportedOperationException("No type or command for client side");
}
return STUB_MATCHER_TYPE.getOrDefault(matchingType, null);
}
}

View File

@@ -0,0 +1,961 @@
/*
* Copyright 2013-2020 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.converter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import org.apache.commons.collections.MapUtils;
import org.yaml.snakeyaml.Yaml;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.ExecutionProperty;
import org.springframework.cloud.contract.spec.internal.Header;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.spec.internal.Input;
import org.springframework.cloud.contract.spec.internal.MatchingTypeValue;
import org.springframework.cloud.contract.spec.internal.NamedProperty;
import org.springframework.cloud.contract.spec.internal.OutputMessage;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
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.NamesUtil;
import org.springframework.util.StringUtils;
import static java.util.stream.Collectors.toSet;
import static org.springframework.cloud.contract.verifier.util.ContentType.XML;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.evaluateClientSideContentType;
/**
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Tim Ysewyn
* @author Stessy Delcroix
*/
class YamlToContracts {
Collection<Contract> convertFrom(File contractFile) {
ClassLoader classLoader = YamlContractConverter.class.getClassLoader();
YAMLMapper mapper = new YAMLMapper();
try {
Iterable<Object> iterables = new Yaml().loadAll(Files.newInputStream(contractFile.toPath()));
Collection<Contract> contracts = new ArrayList<>();
int counter = 0;
for (Object document : iterables) {
List<Contract> processedYaml = processYaml(counter, document, mapper, classLoader, contractFile);
contracts.addAll(processedYaml);
counter = counter + 1;
}
return contracts;
}
catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
catch (IllegalStateException ise) {
throw ise;
}
catch (Exception e1) {
throw new IllegalStateException("Exception occurred while processing the file [" + contractFile + "]", e1);
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
protected List<Contract> processYaml(int counter, Object document, ObjectMapper mapper, ClassLoader classLoader,
File contractFile) {
List<YamlContract> yamlContracts = convert(mapper, document);
Thread.currentThread().setContextClassLoader(updatedClassLoader(contractFile.getParentFile(), classLoader));
List<Contract> contracts = new ArrayList<>();
for (YamlContract yamlContract : yamlContracts) {
Contract contract = Contract.make((dslContract) -> {
mapDescription(yamlContract, dslContract);
mapLabel(yamlContract, dslContract);
mapName(counter, contractFile, yamlContracts, yamlContract, dslContract);
mapPriority(yamlContract, dslContract);
mapIgnored(yamlContract, dslContract);
mapInProgress(yamlContract, dslContract);
mapMetadata(yamlContract, dslContract);
mapRequest(yamlContract, dslContract);
mapResponse(yamlContract, dslContract);
mapInput(yamlContract, dslContract);
mapOutput(yamlContract, dslContract);
});
contracts.add(contract);
}
return contracts;
}
private void mapMetadata(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.metadata != null) {
dslContract.metadata(yamlContract.metadata);
}
}
private void mapInProgress(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.inProgress) {
dslContract.inProgress();
}
}
private void mapIgnored(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.ignored) {
dslContract.ignored();
}
}
private void mapPriority(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.priority != null) {
dslContract.priority(yamlContract.priority);
}
}
private void mapName(int counter, File contractFile, List<YamlContract> yamlContracts, YamlContract yamlContract,
Contract dslContract) {
dslContract.name(StringUtils.hasText(yamlContract.name) ? yamlContract.name
: NamesUtil.defaultContractName(contractFile, yamlContracts, counter));
}
private void mapLabel(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.label != null) {
dslContract.label(yamlContract.label);
}
}
private void mapDescription(YamlContract yamlContract, Contract dslContract) {
if (yamlContract.description != null) {
dslContract.description(yamlContract.description);
}
}
private void mapRequest(YamlContract yamlContract, Contract dslContract) {
YamlContract.Request yamlContractRequest = yamlContract.request;
if (yamlContractRequest != null) {
dslContract.request((dslContractRequest) -> {
mapRequestMethod(yamlContractRequest, dslContractRequest);
mapRequestUrl(yamlContractRequest, dslContractRequest);
mapRequestUrlPath(yamlContract, dslContractRequest);
mapRequestHeaders(yamlContractRequest, dslContractRequest);
mapRequestCookies(yamlContractRequest, dslContractRequest);
mapRequestBody(yamlContractRequest, dslContractRequest);
mapRequestMultiPart(yamlContractRequest, dslContractRequest);
mapRequestBodyMatchers(yamlContractRequest, dslContractRequest);
});
}
}
private void mapRequestMethod(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
if (yamlContractRequest.method != null) {
dslContractRequest.method(yamlContractRequest.method);
}
}
private void mapRequestUrl(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
String yamlContractRequestUrl = yamlContractRequest.url;
if (yamlContractRequestUrl != null) {
YamlContract.KeyValueMatcher yamlContractRequestMatchersUrl = Optional
.ofNullable(yamlContractRequest.matchers).map(matchers -> matchers.url).orElse(null);
dslContractRequest.url(urlValue(yamlContractRequestUrl, yamlContractRequestMatchersUrl), (url) -> {
if (yamlContractRequest.queryParameters != null) {
url.queryParameters(
(queryParameters -> yamlContractRequest.queryParameters.forEach((key, value) -> {
if (value instanceof List) {
((List<?>) value).forEach(v -> queryParameters.parameter(key, v));
}
else {
queryParameters.parameter(key, value);
}
})));
}
});
}
}
private void mapRequestUrlPath(YamlContract yamlContract, Request dslContractRequest) {
String yamlContractRequestUrlPath = yamlContract.request.urlPath;
if (yamlContractRequestUrlPath != null) {
YamlContract.KeyValueMatcher yamlContractRequestMatchersUrl = Optional
.ofNullable(yamlContract.request.matchers).map(matchers -> matchers.url).orElse(null);
dslContractRequest.urlPath(urlValue(yamlContractRequestUrlPath, yamlContractRequestMatchersUrl), (url) -> {
if (yamlContract.request.queryParameters != null) {
url.queryParameters(
(queryParameters -> yamlContract.request.queryParameters.forEach((key, value) -> {
if (value instanceof List) {
((List<?>) value).forEach(
v -> queryParameters.parameter(key, queryParamValue(yamlContract, key, v)));
}
else {
queryParameters.parameter(key, queryParamValue(yamlContract, key, value));
}
})));
}
});
}
}
private void mapRequestHeaders(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
Map<String, Object> yamlContractRequestHeaders = yamlContractRequest.headers;
if (MapUtils.isNotEmpty(yamlContractRequestHeaders)) {
dslContractRequest.headers((headers) -> yamlContractRequestHeaders.forEach((key, value) -> {
List<YamlContract.KeyValueMatcher> matchers = yamlContractRequest.matchers.headers.stream()
.filter((header) -> header.key.equals(key)).collect(Collectors.toList());
matchers.forEach(matcher -> {
if (value instanceof List) {
((List<?>) value)
.forEach(v -> headers.header(key, clientValue(v, matcher, key).getClientValue()));
}
else {
headers.header(key, new DslProperty<>(clientValue(value, matcher, key).getClientValue(),
serverValue(value, matcher)));
}
});
if (matchers != null) {
headers.header(key, value);
}
}));
}
}
private void mapRequestCookies(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
Map<String, Object> yamlContractRequestCookies = yamlContractRequest.cookies;
if (MapUtils.isNotEmpty(yamlContractRequestCookies)) {
dslContractRequest.cookies((cookies) -> yamlContractRequestCookies.forEach((key, value) -> {
YamlContract.KeyValueMatcher matcher = yamlContractRequest.matchers.cookies.stream()
.filter(cookie -> cookie.key.equals(key)).findFirst().orElse(null);
cookies.cookie(key, clientValue(value, matcher, key));
}));
}
}
private void mapRequestBody(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
if (yamlContractRequest.body != null) {
dslContractRequest.body(yamlContractRequest.body);
}
if (yamlContractRequest.bodyFromFile != null) {
dslContractRequest.body(file(yamlContractRequest.bodyFromFile));
}
if (yamlContractRequest.bodyFromFileAsBytes != null) {
dslContractRequest.body(dslContractRequest.fileAsBytes(yamlContractRequest.bodyFromFileAsBytes));
}
}
private void mapRequestMultiPart(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
if (yamlContractRequest.multipart != null) {
Map<String, Object> multipartMap = new HashMap<>();
yamlContractRequest.multipart.params.forEach((paramKey, paramValue) -> {
YamlContract.KeyValueMatcher matcher = yamlContractRequest.matchers.multipart.params.stream()
.filter((param) -> param.key.equals(paramKey)).findFirst().orElse(null);
Object value = paramValue;
if (matcher != null) {
value = matcher.regex != null ? Pattern.compile(matcher.regex)
: predefinedToPattern(matcher.predefined);
}
multipartMap.put(paramKey, new DslProperty<>(value, paramValue));
});
yamlContractRequest.multipart.named.forEach(namedParam -> {
YamlContract.MultipartNamedStubMatcher matcher = yamlContractRequest.matchers.multipart.named.stream()
.filter((stubMatcher) -> stubMatcher.paramName.equals(namedParam.paramName)).findFirst()
.orElse(null);
Object fileNameValue = namedParam.fileName;
Object fileContentValue = namedParam.fileContent;
String fileContentAsBytes = namedParam.fileContentAsBytes;
String fileContentFromFileAsBytes = namedParam.fileContentFromFileAsBytes;
String contentTypeCommand = namedParam.contentTypeCommand;
String fileContentCommand = namedParam.fileContentCommand;
String fileNameCommand = namedParam.fileNameCommand;
Object contentTypeValue = namedParam.contentType;
if (matcher != null && matcher.fileName != null) {
fileNameValue = matcher.fileName.regex != null ? Pattern.compile(matcher.fileName.regex)
: predefinedToPattern(matcher.fileName.predefined);
}
if (matcher != null && matcher.fileContent != null) {
fileContentValue = matcher.fileContent.regex != null ? Pattern.compile(matcher.fileContent.regex)
: predefinedToPattern(matcher.fileContent.predefined);
}
if (matcher != null && matcher.contentType != null) {
contentTypeValue = matcher.contentType.regex != null ? Pattern.compile(matcher.contentType.regex)
: predefinedToPattern(matcher.contentType.predefined);
}
multipartMap.put(namedParam.paramName, new NamedProperty(
new DslProperty<>(fileNameValue,
fileNameCommand != null ? new ExecutionProperty(fileNameCommand) : namedParam.fileName),
new DslProperty<>(fileContentValue,
namedParam.fileContent != null ? namedParam.fileContent
: fileContentFromFileAsBytes != null
? dslContractRequest.fileAsBytes(namedParam.fileContentFromFileAsBytes)
: fileContentAsBytes != null ? fileContentAsBytes.getBytes()
: new ExecutionProperty(fileContentCommand)),
new DslProperty<>(contentTypeValue, contentTypeCommand != null
? new ExecutionProperty(contentTypeCommand) : namedParam.contentType)));
});
dslContractRequest.multipart(multipartMap);
}
}
private void mapRequestBodyMatchers(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
dslContractRequest.bodyMatchers((bodyMatchers) -> Optional.ofNullable(yamlContractRequest.matchers)
.map(stubMatchers -> stubMatchers.body).ifPresent(stubMatchers -> stubMatchers.forEach(stubMatcher -> {
ContentType contentType = evaluateClientSideContentType(
yamlHeadersToContractHeaders(
Optional.ofNullable(yamlContractRequest.headers).orElse(new HashMap<>())),
Optional.ofNullable(yamlContractRequest.body).orElse(null));
MatchingTypeValue value = null;
switch (stubMatcher.type) {
case by_date:
value = bodyMatchers.byDate();
break;
case by_time:
value = bodyMatchers.byTime();
break;
case by_timestamp:
value = bodyMatchers.byTimestamp();
break;
case by_regex:
String regex = stubMatcher.value;
if (stubMatcher.predefined != null) {
regex = predefinedToPattern(stubMatcher.predefined).pattern();
}
value = bodyMatchers.byRegex(regex);
break;
case by_equality:
value = bodyMatchers.byEquality();
break;
case by_type:
value = bodyMatchers.byType(matchingTypeValueHolder -> {
if (stubMatcher.minOccurrence != null) {
matchingTypeValueHolder.minOccurrence(stubMatcher.minOccurrence);
}
if (stubMatcher.maxOccurrence != null) {
matchingTypeValueHolder.maxOccurrence(stubMatcher.maxOccurrence);
}
});
break;
case by_null:
// do nothing
break;
default:
throw new UnsupportedOperationException("The type [" + stubMatcher.type + "] is"
+ " unsupported.Hint:If you 're using <predefined> remember to pass <type:by_regex > ");
}
if (value != null) {
if (XML == contentType) {
bodyMatchers.xPath(stubMatcher.path, value);
}
else {
bodyMatchers.jsonPath(stubMatcher.path, value);
}
}
})));
}
private void mapResponse(YamlContract yamlContract, Contract dslContract) {
YamlContract.Response yamlContractResponse = yamlContract.response;
if (yamlContractResponse != null) {
dslContract.response(dslContractResponse -> {
mapResponseStatus(yamlContractResponse, dslContractResponse);
mapResponseHeaders(yamlContractResponse, dslContractResponse);
mapResponseCookies(yamlContractResponse, dslContractResponse);
mapResponseBody(yamlContractResponse, dslContractResponse);
mapResponseAsync(yamlContractResponse, dslContractResponse);
mapResponseFixedDelayMilliseconds(yamlContractResponse, dslContractResponse);
mapResponseBodyMatchers(yamlContractResponse, dslContractResponse);
});
}
}
private void mapResponseStatus(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
dslContractResponse.status(yamlContractResponse.status);
}
private void mapResponseHeaders(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
dslContractResponse.headers(headers -> Optional.ofNullable(yamlContractResponse.headers)
.ifPresent(yamlContractResponseHeaders -> yamlContractResponseHeaders.forEach((key, value) -> {
YamlContract.TestHeaderMatcher matcher = yamlContractResponse.matchers.headers.stream()
.filter(h -> h.key.equals(key)).findFirst().orElse(null);
if (value instanceof List) {
((List<?>) value).forEach(v -> {
Object serverValue = serverValue(v, matcher, key);
headers.header(key, new DslProperty<>(v, serverValue));
});
}
else {
Object serverValue = serverValue(value, matcher, key);
headers.header(key, new DslProperty<>(value, serverValue));
}
})));
}
private void mapResponseCookies(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
if (yamlContractResponse.cookies != null) {
dslContractResponse.cookies(cookies -> yamlContractResponse.cookies.forEach((key, value) -> {
YamlContract.TestCookieMatcher matcher = yamlContractResponse.matchers.cookies.stream()
.filter(testCookieMatcher -> testCookieMatcher.key.equals(key)).findFirst().orElse(null);
DslProperty<?> cookieValue = serverCookieValue(value, matcher, key);
cookies.cookie(key, cookieValue);
}));
}
}
private void mapResponseBody(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
if (yamlContractResponse.body != null) {
YamlContract.BodyTestMatcher bodyTestMatcher = Optional.ofNullable(yamlContractResponse.matchers)
.map(testMatchers -> testMatchers.body)
.flatMap(
bodyTestMatchers -> bodyTestMatchers.stream()
.filter(m -> m.path == null && (m.type == YamlContract.TestMatcherType.by_regex
|| m.type == YamlContract.TestMatcherType.by_command))
.findFirst())
.orElse(null);
if (bodyTestMatcher != null) {
dslContractResponse.body(new DslProperty<>(yamlContractResponse.body,
bodyTestMatcher.type == YamlContract.TestMatcherType.by_regex
? Pattern.compile(bodyTestMatcher.value)
: new ExecutionProperty(bodyTestMatcher.value)));
}
else {
dslContractResponse.body(yamlContractResponse.body);
}
}
if (yamlContractResponse.bodyFromFile != null) {
dslContractResponse.body(file(yamlContractResponse.bodyFromFile));
}
if (yamlContractResponse.bodyFromFileAsBytes != null) {
dslContractResponse.body(dslContractResponse.fileAsBytes(yamlContractResponse.bodyFromFileAsBytes));
}
}
private void mapResponseAsync(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
if (yamlContractResponse.async != null && yamlContractResponse.async) {
dslContractResponse.async();
}
}
private void mapResponseFixedDelayMilliseconds(YamlContract.Response yamlContractResponse,
Response dslContractResponse) {
if (yamlContractResponse.fixedDelayMilliseconds != null) {
dslContractResponse.async();
dslContractResponse.fixedDelayMilliseconds(yamlContractResponse.fixedDelayMilliseconds);
}
}
private void mapResponseBodyMatchers(YamlContract.Response yamlContractResponse, Response dslContractResponse) {
dslContractResponse.bodyMatchers(bodyMatchers -> Optional.ofNullable(yamlContractResponse.matchers)
.map(yamlContractResponseTestMatchers -> yamlContractResponseTestMatchers.body)
.ifPresent(yamlContractBodyTestMatchers -> yamlContractBodyTestMatchers
.forEach(yamlContractBodyTestMatcher -> {
ContentType contentType = evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContractResponse.headers),
yamlContractResponse.body);
MatchingTypeValue value;
switch (yamlContractBodyTestMatcher.type) {
case by_date:
value = bodyMatchers.byDate();
break;
case by_time:
value = bodyMatchers.byTime();
break;
case by_timestamp:
value = bodyMatchers.byTimestamp();
break;
case by_regex:
String regex = yamlContractBodyTestMatcher.value;
if (yamlContractBodyTestMatcher.predefined != null) {
regex = predefinedToPattern(yamlContractBodyTestMatcher.predefined).pattern();
}
value = bodyMatchers.byRegex(regex);
break;
case by_equality:
value = bodyMatchers.byEquality();
break;
case by_type:
value = bodyMatchers.byType(v -> {
if (yamlContractBodyTestMatcher.minOccurrence != null) {
v.minOccurrence(yamlContractBodyTestMatcher.minOccurrence);
}
if (yamlContractBodyTestMatcher.maxOccurrence != null) {
v.maxOccurrence(yamlContractBodyTestMatcher.maxOccurrence);
}
});
break;
case by_command:
value = bodyMatchers.byCommand(yamlContractBodyTestMatcher.value);
break;
case by_null:
value = bodyMatchers.byNull();
break;
default:
throw new UnsupportedOperationException("The type [" + yamlContractBodyTestMatcher.type
+ "] is unsupported. "
+ "Hint: If you're using <predefined> remember to pass < type:by_regex > ");
}
if (yamlContractBodyTestMatcher.path != null) {
if (XML == contentType) {
bodyMatchers.xPath(yamlContractBodyTestMatcher.path, value);
}
else {
bodyMatchers.jsonPath(yamlContractBodyTestMatcher.path, value);
}
}
})));
}
private void mapOutput(YamlContract yamlContract, Contract dslContract) {
YamlContract.OutputMessage yamlContractOutputMessage = yamlContract.outputMessage;
if (yamlContract.outputMessage != null) {
dslContract.outputMessage((dslContractOutputMessage) -> {
mapOutputAssertThat(yamlContractOutputMessage, dslContractOutputMessage);
mapOutputSentTo(yamlContractOutputMessage, dslContractOutputMessage);
mapOutputMessageHeaders(yamlContractOutputMessage, dslContractOutputMessage);
mapOutputBody(yamlContractOutputMessage, dslContractOutputMessage);
mapOutputBodyMatchers(yamlContractOutputMessage, dslContractOutputMessage);
});
}
}
private void mapOutputBodyMatchers(YamlContract.OutputMessage yamlContractOutputMessage,
OutputMessage dslContractOutputMessage) {
if (yamlContractOutputMessage.matchers != null) {
dslContractOutputMessage.bodyMatchers(
dslContractOutputMessageBodyMatchers -> Optional.ofNullable(yamlContractOutputMessage.matchers.body)
.ifPresent(yamlContractBodyTestMatchers -> yamlContractBodyTestMatchers
.forEach(yamlContractBodyTestMatcher -> {
ContentType contentType = evaluateClientSideContentType(
yamlHeadersToContractHeaders(yamlContractOutputMessage.headers),
yamlContractOutputMessage.body);
MatchingTypeValue value;
switch (yamlContractBodyTestMatcher.type) {
case by_date:
value = dslContractOutputMessageBodyMatchers.byDate();
break;
case by_time:
value = dslContractOutputMessageBodyMatchers.byTime();
break;
case by_timestamp:
value = dslContractOutputMessageBodyMatchers.byTimestamp();
break;
case by_regex:
String regex = yamlContractBodyTestMatcher.value;
if (yamlContractBodyTestMatcher.predefined != null) {
regex = predefinedToPattern(yamlContractBodyTestMatcher.predefined)
.pattern();
}
value = dslContractOutputMessageBodyMatchers.byRegex(regex);
break;
case by_equality:
value = dslContractOutputMessageBodyMatchers.byEquality();
break;
case by_type:
value = dslContractOutputMessageBodyMatchers.byType(v -> {
if (yamlContractBodyTestMatcher.minOccurrence != null) {
v.minOccurrence(yamlContractBodyTestMatcher.minOccurrence);
}
if (yamlContractBodyTestMatcher.maxOccurrence != null) {
v.maxOccurrence(yamlContractBodyTestMatcher.maxOccurrence);
}
});
break;
case by_command:
value = dslContractOutputMessageBodyMatchers
.byCommand(yamlContractBodyTestMatcher.value);
break;
case by_null:
value = dslContractOutputMessageBodyMatchers.byNull();
break;
default:
throw new UnsupportedOperationException("The type " + "["
+ yamlContractBodyTestMatcher.type + "] is unsupported. Hint: If "
+ "you're using <predefined> remember to pass < type:by_regex > ");
}
if (XML == contentType) {
dslContractOutputMessageBodyMatchers.xPath(yamlContractBodyTestMatcher.path,
value);
}
else {
dslContractOutputMessageBodyMatchers
.jsonPath(yamlContractBodyTestMatcher.path, value);
}
})));
}
}
private void mapOutputMessageHeaders(YamlContract.OutputMessage yamlContractOutputMessage,
OutputMessage dslContractOutputMessage) {
dslContractOutputMessage.headers(dslContractOutputMessageHeaders -> Optional
.ofNullable(yamlContractOutputMessage).map(yamlContractOutput -> yamlContractOutput.headers).ifPresent(
yamlContractOutputMessageHeaders -> yamlContractOutputMessageHeaders.forEach((key, value) -> {
YamlContract.TestHeaderMatcher matcher = Optional
.ofNullable(yamlContractOutputMessage.matchers)
.map(yamlContractOutputMatchers -> yamlContractOutputMatchers.headers)
.flatMap(yamlContractOutputMatchersHeaders -> yamlContractOutputMatchersHeaders
.stream()
.filter(yamlContractOutputMatchersHeader -> yamlContractOutputMatchersHeader.key
.equals(key))
.findFirst())
.orElse(null);
Object serverValue = serverValue(value, matcher, key);
dslContractOutputMessageHeaders.header(key, new DslProperty<>(value, serverValue));
})));
}
private void mapOutputBody(YamlContract.OutputMessage yamlContractOutputMessage,
OutputMessage dslContractOutputMessage) {
if (yamlContractOutputMessage.body != null) {
dslContractOutputMessage.body(yamlContractOutputMessage.body);
}
if (yamlContractOutputMessage.bodyFromFile != null) {
dslContractOutputMessage.body(file(yamlContractOutputMessage.bodyFromFile));
}
if (yamlContractOutputMessage.bodyFromFileAsBytes != null) {
dslContractOutputMessage
.body(dslContractOutputMessage.fileAsBytes(yamlContractOutputMessage.bodyFromFileAsBytes));
}
}
private void mapOutputSentTo(YamlContract.OutputMessage yamlContractOutputMessage,
OutputMessage dslContractOutputMessage) {
if (yamlContractOutputMessage.sentTo != null) {
dslContractOutputMessage.sentTo(yamlContractOutputMessage.sentTo);
}
}
private void mapOutputAssertThat(YamlContract.OutputMessage yamlContractOutputMessage,
OutputMessage dslContractOutputMessage) {
if (yamlContractOutputMessage.assertThat != null) {
dslContractOutputMessage.assertThat(yamlContractOutputMessage.assertThat);
}
}
private void mapInput(YamlContract yamlContract, Contract dslContract) {
YamlContract.Input yamlContractInput = yamlContract.input;
if (yamlContractInput != null) {
dslContract.input(dslContractInput -> {
mapInputMessageFrom(yamlContractInput, dslContractInput);
mapInputAssertThat(yamlContractInput, dslContractInput);
mapInputTriggeredBy(yamlContractInput, dslContractInput);
mapInputMessageHeaders(yamlContractInput, dslContractInput);
mapInputMessageBody(yamlContractInput, dslContractInput);
mapInputBodyMatchers(yamlContractInput, dslContractInput);
});
}
}
private void mapInputMessageFrom(YamlContract.Input yamlContractInput, Input dslContractInput) {
if (yamlContractInput.messageFrom != null) {
dslContractInput.messageFrom(yamlContractInput.messageFrom);
}
}
private void mapInputAssertThat(YamlContract.Input yamlContractInput, Input dslContractInput) {
if (yamlContractInput.assertThat != null) {
dslContractInput.assertThat(yamlContractInput.assertThat);
}
}
private void mapInputTriggeredBy(YamlContract.Input yamlContractInput, Input dslContractInput) {
if (yamlContractInput.triggeredBy != null) {
dslContractInput.triggeredBy(yamlContractInput.triggeredBy);
}
}
private void mapInputMessageHeaders(YamlContract.Input yamlContractInput, Input dslContractInput) {
dslContractInput
.messageHeaders(dslContractMessageHeaders -> Optional.ofNullable(yamlContractInput.messageHeaders)
.ifPresent(yamlContractMessageHeaders -> yamlContractMessageHeaders.forEach((key, value) -> {
YamlContract.KeyValueMatcher matcher = Optional.ofNullable(yamlContractInput.matchers)
.map(yamlContractInputMatchers -> yamlContractInputMatchers.headers)
.flatMap(yamlContractInputMatchersHeaders -> yamlContractInputMatchersHeaders
.stream()
.filter(yamlContractInputMatchersHeader -> yamlContractInputMatchersHeader.key
.equals(key))
.findFirst())
.orElse(null);
dslContractMessageHeaders.header(key, clientValue(value, matcher, key));
})));
}
private void mapInputMessageBody(YamlContract.Input yamlContractInput, Input dslContractInput) {
if (yamlContractInput.messageBody != null) {
dslContractInput.messageBody(yamlContractInput.messageBody);
}
if (yamlContractInput.messageBodyFromFile != null) {
dslContractInput.messageBody(file(yamlContractInput.messageBodyFromFile));
}
if (yamlContractInput.messageBodyFromFileAsBytes != null) {
dslContractInput.messageBody(dslContractInput.fileAsBytes(yamlContractInput.messageBodyFromFileAsBytes));
}
}
private void mapInputBodyMatchers(YamlContract.Input yamlContractInput, Input dslContractInput) {
dslContractInput
.bodyMatchers(dslContractInputBodyMatchers -> Optional.ofNullable(yamlContractInput.matchers.body)
.ifPresent(yamlContractBodyStubMatchers -> yamlContractBodyStubMatchers
.forEach(yamlContractBodyStubMatcher -> {
ContentType contentType = evaluateClientSideContentType(
yamlHeadersToContractHeaders(
Optional.ofNullable(yamlContractInput.messageHeaders)
.orElse(new HashMap<>())),
Optional.ofNullable(yamlContractInput.messageBody).orElse(null));
MatchingTypeValue value;
switch (yamlContractBodyStubMatcher.type) {
case by_date:
value = dslContractInputBodyMatchers.byDate();
break;
case by_time:
value = dslContractInputBodyMatchers.byTime();
break;
case by_timestamp:
value = dslContractInputBodyMatchers.byTimestamp();
break;
case by_regex:
String regex = yamlContractBodyStubMatcher.value;
if (yamlContractBodyStubMatcher.predefined != null) {
regex = predefinedToPattern(yamlContractBodyStubMatcher.predefined)
.pattern();
}
value = dslContractInputBodyMatchers.byRegex(regex);
break;
case by_equality:
value = dslContractInputBodyMatchers.byEquality();
break;
default:
throw new UnsupportedOperationException("The type " + "["
+ yamlContractBodyStubMatcher.type + "] is unsupported. "
+ "Hint: If you're using <predefined> remember to pass < type:by_regex > ");
}
if (XML == contentType) {
dslContractInputBodyMatchers.xPath(yamlContractBodyStubMatcher.path, value);
}
else {
dslContractInputBodyMatchers.jsonPath(yamlContractBodyStubMatcher.path, value);
}
})));
}
private Headers yamlHeadersToContractHeaders(Map<String, Object> headers) {
Set<Header> convertedHeaders = headers.keySet().stream()
.map(header -> Header.build(header, headers.get(header))).collect(toSet());
Headers contractHeaders = new Headers();
contractHeaders.headers(convertedHeaders);
return contractHeaders;
}
protected DslProperty<?> urlValue(String url, YamlContract.KeyValueMatcher urlMatcher) {
if (urlMatcher != null) {
if (urlMatcher.command != null) {
return new DslProperty<Object>(url, new ExecutionProperty(urlMatcher.command));
}
return new DslProperty<>(urlMatcher.regex != null ? Pattern.compile(urlMatcher.regex)
: urlMatcher.predefined != null ? predefinedToPattern(urlMatcher.predefined) : url, url);
}
return new DslProperty<>(url);
}
protected List<YamlContract> convert(ObjectMapper mapper, Object o) {
try {
return Arrays.asList(mapper.convertValue(o, YamlContract[].class));
}
catch (IllegalArgumentException e) {
return Collections.singletonList(mapper.convertValue(o, YamlContract.class));
}
}
protected Object serverValue(Object value, YamlContract.TestHeaderMatcher matcher, String key) {
Object serverValue = value;
if (matcher != null && matcher.regex != null) {
serverValue = Pattern.compile(matcher.regex);
Pattern pattern = (Pattern) serverValue;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.predefined != null) {
Pattern pattern = predefinedToPattern(matcher.predefined);
serverValue = pattern;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.command != null) {
serverValue = new ExecutionProperty(matcher.command);
}
return serverValue;
}
protected DslProperty<?> serverCookieValue(Object value, YamlContract.TestCookieMatcher matcher, String key) {
Object serverValue = value;
if (matcher != null && matcher.regex != null) {
serverValue = Pattern.compile(matcher.regex);
Pattern pattern = (Pattern) serverValue;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.predefined != null) {
Pattern pattern = predefinedToPattern(matcher.predefined);
serverValue = pattern;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.command != null) {
return new DslProperty<>(new ExecutionProperty(matcher.command), value);
}
return new DslProperty<>(value, serverValue);
}
protected DslProperty<?> clientValue(Object value, YamlContract.KeyValueMatcher matcher, String key) {
Object clientValue = value instanceof DslProperty ? ((DslProperty<?>) value).getClientValue() : value;
if (matcher != null && matcher.regex != null) {
clientValue = Pattern.compile(matcher.regex);
Pattern pattern = (Pattern) clientValue;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.predefined != null) {
Pattern pattern = predefinedToPattern(matcher.predefined);
clientValue = pattern;
assertPatternMatched(pattern, value, key);
}
else if (matcher != null && matcher.command != null) {
return new DslProperty<>(value, new ExecutionProperty(matcher.command));
}
return new DslProperty<>(clientValue, value);
}
protected Object queryParamValue(YamlContract yamlContract, String key, Object value) {
Request request = new Request();
YamlContract.QueryParameterMatcher matcher = yamlContract.request.matchers.queryParameters.stream()
.filter(queryParameter -> queryParameter.key.equals(key)).findFirst().orElse(null);
if (matcher == null) {
return value;
}
switch (matcher.type) {
case equal_to:
return new DslProperty<>(request.equalTo(matcher.value), value);
case containing:
return new DslProperty<>(request.containing(matcher.value), value);
case matching:
return new DslProperty<>(request.matching(matcher.value), value);
case not_matching:
return new DslProperty<>(request.notMatching(matcher.value), value);
case equal_to_json:
return new DslProperty<>(request.equalToJson(matcher.value), value);
case equal_to_xml:
return new DslProperty<>(request.equalToXml(matcher.value), value);
case absent:
return new DslProperty<Object>(request.absent(), null);
default:
throw new UnsupportedOperationException("The provided matching type [" + matcher
+ "] is unsupported. Use on of " + Arrays.toString(YamlContract.MatchingType.values()));
}
}
protected Object serverValue(Object value, YamlContract.KeyValueMatcher matcher) {
if (matcher != null && matcher.command != null) {
return new ExecutionProperty(matcher.command);
}
return value instanceof DslProperty ? ((DslProperty<?>) value).getServerValue() : value;
}
private void assertPatternMatched(Pattern pattern, Object value, String key) {
boolean matches = pattern.matcher(value.toString()).matches();
if (!matches) {
throw new IllegalStateException("Broken headers! A header with " + "key [" + key + "] with value [" + value
+ "] is not matched by regex [" + pattern.pattern() + "]");
}
}
protected Pattern predefinedToPattern(YamlContract.PredefinedRegex predefinedRegex) {
switch (predefinedRegex) {
case only_alpha_unicode:
return RegexPatterns.onlyAlphaUnicode().getPattern();
case number:
return RegexPatterns.number().getPattern();
case any_double:
return RegexPatterns.aDouble().getPattern();
case any_boolean:
return RegexPatterns.anyBoolean().getPattern();
case ip_address:
return RegexPatterns.ipAddress().getPattern();
case hostname:
return RegexPatterns.hostname().getPattern();
case email:
return RegexPatterns.email().getPattern();
case url:
return RegexPatterns.url().getPattern();
case uuid:
return RegexPatterns.uuid().getPattern();
case iso_date:
return RegexPatterns.isoDate().getPattern();
case iso_date_time:
return RegexPatterns.isoDateTime().getPattern();
case iso_time:
return RegexPatterns.isoTime().getPattern();
case iso_8601_with_offset:
return RegexPatterns.iso8601WithOffset().getPattern();
case non_empty:
return RegexPatterns.nonEmpty().getPattern();
case non_blank:
return RegexPatterns.nonBlank().getPattern();
default:
throw new UnsupportedOperationException("The predefined regex [" + predefinedRegex
+ "] is unsupported. Use one of " + Arrays.toString(YamlContract.PredefinedRegex.values()));
}
}
protected String file(String relativePath) {
URL resource = Thread.currentThread().getContextClassLoader().getResource(relativePath);
if (resource == null) {
throw new IllegalStateException("File [\"+relativePath+\"] is not present");
}
try {
return String.join("\n", Files.readAllLines(Paths.get(resource.toURI())));
}
catch (URISyntaxException | IOException e) {
throw new IllegalStateException("File [" + relativePath + "] syntax is incorrect");
}
}
protected static ClassLoader updatedClassLoader(File rootFolder, ClassLoader classLoader) {
try {
ClassLoader urlCl = URLClassLoader.newInstance(new URL[] { rootFolder.toURI().toURL() }, classLoader);
Thread.currentThread().setContextClassLoader(urlCl);
return urlCl;
}
catch (MalformedURLException e) {
throw new IllegalStateException("Root folder [" + rootFolder + "] URL is incorrect");
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2013-2020 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.dsl.wiremock;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import groovy.lang.GString;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractTemplate;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.Headers;
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor;
import org.springframework.cloud.contract.verifier.template.TemplateProcessor;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import static org.springframework.cloud.contract.verifier.util.ContentType.UNKNOWN;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getClientContentType;
import static org.springframework.cloud.contract.verifier.util.MapConverter.transformValues;
/**
* Common abstraction over WireMock Request / Response conversion implementations.
*
* @since 1.0.0
*/
abstract class BaseWireMockStubStrategy {
private static final String WRAPPER = "UNQUOTE_ME";
protected final TemplateProcessor processor;
protected final ContractTemplate template;
protected final Contract contract;
protected BaseWireMockStubStrategy(Contract contract) {
this.processor = templateProcessor();
this.template = contractTemplate();
this.contract = contract;
}
private TemplateProcessor templateProcessor() {
return new HandlebarsTemplateProcessor();
}
private ContractTemplate contractTemplate() {
return new HandlebarsTemplateProcessor();
}
/**
* @return the stub side values from the object
*/
protected Object getStubSideValue(Object object) {
return MapConverter.getStubSideValues(object);
}
/**
* For the given {@link ContentType} returns the String version of the body.
*/
String parseBody(Object value, ContentType contentType) {
return parseBody(value.toString(), contentType);
}
/**
* Return body as String from file.
*/
String parseBody(FromFileProperty value, ContentType contentType) {
return value.asString();
}
/**
* For the given {@link ContentType} returns the Boolean version of the body.
*/
String parseBody(Boolean value, ContentType contentType) {
return value.toString();
}
/**
* For the given {@link ContentType} returns the String version of the body.
*/
String parseBody(Map<?, ?> map, ContentType contentType) {
Object transformedMap = MapConverter.getStubSideValues(map);
transformedMap = transformMapIfRequestPresent(transformedMap);
String json = toJson(transformedMap);
// the space is important cause at the end of the json body you also have a }
// you can't have 4 } next to each other
String unquotedJson = json.replace('"' + WRAPPER, "").replace(WRAPPER + '"', " ");
String unescapedJson = unquotedJson.replace("\\/", "/");
return parseBody(unescapedJson, contentType);
}
private Object transformMapIfRequestPresent(Object transformedMap) {
Object requestBody = contract.getRequest().getBody();
if (requestBody == null) {
return transformedMap;
}
String testSideBody = toJson(MapConverter.getTestSideValues(requestBody));
DocumentContext context = JsonPath.parse(testSideBody);
return processEntriesForTemplating(transformedMap, context);
}
private Object processEntriesForTemplating(Object transformedMap, DocumentContext context) {
return transformValues(transformedMap, (val) -> {
if (val instanceof String && processor.containsJsonPathTemplateEntry((String) val)) {
String jsonPath = processor.jsonPathFromTemplateEntry((String) val);
if (jsonPath == null) {
return val;
}
Object value = context.read(jsonPath);
if (value instanceof String) {
return val;
}
return WRAPPER + val + WRAPPER;
}
else if (val instanceof String && processor.containsTemplateEntry((String) val)
&& template.escapedBody().equals(val)) {
return template.escapedBody();
}
return val;
});
}
/**
* For the given {@link ContentType} returns the String version of the body.
*/
String parseBody(List<?> list, ContentType contentType) {
final List<Object> result = new ArrayList<>();
list.forEach(l -> {
if (l instanceof Map) {
result.add(MapConverter.getStubSideValues(l));
}
else if (l instanceof List) {
result.add(parseBody((List<?>) l, contentType));
}
else {
result.add(parseBody(l, contentType));
}
});
return parseBody(toJson(result), contentType);
}
/**
* For the given {@link ContentType} returns the String version of the body.
*/
String parseBody(GString value, ContentType contentType) {
Object processedValue = extractValue(value, contentType,
(o) -> o instanceof DslProperty ? ((DslProperty<?>) o).getClientValue() : o);
if (processedValue instanceof GString) {
return parseBody(processedValue.toString(), contentType);
}
return parseBody(processedValue, contentType);
}
/**
* For the given {@link ContentType} returns the String version of the body.
*/
String parseBody(String value, ContentType contentType) {
return value;
}
private static String toJson(Object value) {
try {
if (value instanceof Map) {
Object convertedMap = MapConverter.transformValues(value,
(v) -> v instanceof GString ? ((GString) v).toString() : v);
String jsonOutput = new ObjectMapper().writeValueAsString(convertedMap);
return jsonOutput.replaceAll("\\\\\\\\\\\\", "\\\\");
}
return new ObjectMapper().writeValueAsString(value);
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException("The current object [" + value + "] could not be serialized");
}
}
/**
* Attempts to guess the {@link ContentType} from body and headers. Returns
* {@link ContentType#UNKNOWN} if it fails to guess.
*/
protected ContentType tryToGetContentType(Object body, Headers headers) {
ContentType contentType = ContentUtils.recognizeContentTypeFromHeader(headers);
if (UNKNOWN == contentType) {
if (body == null) {
return UNKNOWN;
}
return getClientContentType(body);
}
return contentType;
}
}

View File

@@ -26,10 +26,10 @@ import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.matching.MatchResult;
import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension;
import net.javacrumbs.jsonunit.assertj.JsonAssertions;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.assertj.core.api.Assertions;
import org.codehaus.plexus.util.StringUtils;
import org.springframework.cloud.contract.verifier.converter.YamlContract;
import org.springframework.cloud.contract.verifier.converter.YamlContractConverter;

View File

@@ -0,0 +1,549 @@
/*
* Copyright 2013-2020 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.dsl.wiremock;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.http.RequestMethod;
import com.github.tomakehurst.wiremock.matching.ContentPattern;
import com.github.tomakehurst.wiremock.matching.RequestPattern;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.matching.StringValuePattern;
import com.github.tomakehurst.wiremock.matching.UrlPattern;
import groovy.lang.GString;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Body;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.spec.internal.MatchingStrategy;
import org.springframework.cloud.contract.spec.internal.MatchingType;
import org.springframework.cloud.contract.spec.internal.NamedProperty;
import org.springframework.cloud.contract.spec.internal.OptionalProperty;
import org.springframework.cloud.contract.spec.internal.PathBodyMatcher;
import org.springframework.cloud.contract.spec.internal.QueryParameters;
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Url;
import org.springframework.cloud.contract.verifier.converter.YamlContract;
import org.springframework.cloud.contract.verifier.converter.YamlContractConverter;
import org.springframework.cloud.contract.verifier.dsl.ContractVerifierMetadata;
import org.springframework.cloud.contract.verifier.file.SingleContractMetadata;
import org.springframework.cloud.contract.verifier.util.ContentType;
import org.springframework.cloud.contract.verifier.util.ContentUtils;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import org.springframework.cloud.contract.verifier.util.xml.XmlToXPathsConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.contract.spec.internal.MatchingStrategy.Type.BINARY_EQUAL_TO;
import static org.springframework.cloud.contract.spec.internal.MatchingType.EQUALITY;
import static org.springframework.cloud.contract.verifier.util.ContentType.FORM;
import static org.springframework.cloud.contract.verifier.util.ContentType.JSON;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.getEqualsTypeFromContentType;
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildGStringRegexpForStubSide;
import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.buildJSONRegexpMatch;
import static org.springframework.cloud.contract.verifier.util.xml.XmlToXPathsConverter.retrieveValue;
/**
* Converts a {@link Request} into {@link RequestPattern}.
*
* @author Marcin Grzejszczak
* @author Tim Ysewyn
* @author Olga Maciaszek-Sharma
* @since 1.0.0
*/
class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
private final Request request;
private final ContentType contentType;
WireMockRequestStubStrategy(Contract groovyDsl, SingleContractMetadata singleContractMetadata) {
super(groovyDsl);
this.request = groovyDsl.getRequest();
this.contentType = contentType(singleContractMetadata);
}
protected ContentType contentType(SingleContractMetadata singleContractMetadata) {
return singleContractMetadata.getEvaluatedInputStubContentType();
}
RequestPattern buildClientRequestContent() {
if (request == null) {
return null;
}
RequestPatternBuilder requestPatternBuilder = appendMethodAndUrl();
appendCookies(requestPatternBuilder);
appendHeaders(requestPatternBuilder);
appendQueryParameters(requestPatternBuilder);
appendBody(requestPatternBuilder);
appendMultipart(requestPatternBuilder);
return requestPatternBuilder.build();
}
private void appendBody(RequestPatternBuilder requestPatternBuilder) {
if (contract.getMetadata().containsKey(ContractVerifierMetadata.METADATA_KEY)) {
ContractVerifierMetadata metadata = ContractVerifierMetadata.fromMetadata(contract.getMetadata());
appendSpringCloudContractMatcher(metadata, requestPatternBuilder);
if (!StringUtils.hasLength(metadata.getTool())) {
doAppendBody(requestPatternBuilder);
}
}
else {
doAppendBody(requestPatternBuilder);
}
}
private void appendSpringCloudContractMatcher(ContractVerifierMetadata metadata,
RequestPatternBuilder requestPatternBuilder) {
Parameters parameters = Parameters.one("tool", metadata.getTool() != null ? metadata.getTool() : "unknown");
YamlContractConverter converter = new YamlContractConverter();
List<YamlContract> contracts = converter.convertTo(Collections.singleton(contract));
Map<String, byte[]> store = converter.store(contracts);
parameters.put("contract", new String(store.entrySet().iterator().next().getValue()));
requestPatternBuilder.andMatching(SpringCloudContractRequestMatcher.NAME, parameters);
}
private RequestPatternBuilder appendMethodAndUrl() {
if (request.getMethod() == null) {
return null;
}
RequestMethod requestMethod = RequestMethod.fromString(
Optional.ofNullable(request.getMethod().getClientValue()).map(c -> c.toString()).orElse(null));
UrlPattern urlPattern = urlPattern();
return RequestPatternBuilder.newRequestPattern(requestMethod, urlPattern);
}
private void doAppendBody(RequestPatternBuilder requestPattern) {
if (request.getBody() == null) {
return;
}
boolean bodyHasMatchingStrategy = request.getBody().getClientValue() instanceof MatchingStrategy;
MatchingStrategy matchingStrategy = getMatchingStrategyFromBody(request.getBody());
if (contentType == ContentType.JSON) {
Object clientSideBody = MapConverter.transformToClientValues(request.getBody());
Object originalBody = Optional.ofNullable(matchingStrategy).map(DslProperty::getClientValue).orElse(null);
if (bodyHasMatchingStrategy) {
requestPattern.withRequestBody(convertToValuePattern(matchingStrategy));
}
else if (clientSideBody instanceof Pattern || clientSideBody instanceof RegexProperty) {
requestPattern.withRequestBody(
convertToValuePattern(appendBodyRegexpMatchPattern(request.getBody(), contentType)));
}
else {
Object body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.getBodyMatchers());
JsonPaths values = JsonToJsonPathsConverter
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(body);
if ((values.isEmpty() && request.getBodyMatchers() != null && !request.getBodyMatchers().hasMatchers())
|| onlySizeAssertionsArePresent(values)) {
try {
requestPattern.withRequestBody(WireMock.equalToJson(
new ObjectMapper().writeValueAsString(
getMatchingStrategy(request.getBody().getClientValue()).getClientValue()),
false, false));
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException("The MatchingStrategy could not be serialized", e);
}
}
else {
values.stream().filter(v -> !v.assertsSize()).forEach(it -> requestPattern
.withRequestBody(WireMock.matchingJsonPath(it.jsonPath().replace("\\\\", "\\"))));
}
}
Optional.ofNullable(request.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach(bodyMatcher -> {
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(bodyMatcher,
originalBody);
requestPattern.withRequestBody(WireMock.matchingJsonPath(newPath.replace("\\\\", "\\")));
}));
}
else if (contentType == ContentType.XML) {
Object originalBody = Optional.ofNullable(matchingStrategy).map(DslProperty::getClientValue).orElse(null);
if (bodyHasMatchingStrategy) {
requestPattern.withRequestBody(convertToValuePattern(matchingStrategy));
}
else {
Object body = XmlToXPathsConverter.removeMatchingXPaths(originalBody, request.getBodyMatchers());
List<BodyMatcher> byEqualityMatchersFromXml = XmlToXPathsConverter.mapToMatchers(body);
byEqualityMatchersFromXml.forEach(
bodyMatcher -> addWireMockStubMatchingSection(bodyMatcher, requestPattern, originalBody));
}
Optional.ofNullable(request.getBodyMatchers()).map(BodyMatchers::matchers)
.ifPresent(bodyMatchers -> bodyMatchers.forEach(
bodyMatcher -> addWireMockStubMatchingSection(bodyMatcher, requestPattern, originalBody)));
}
else if (containsPattern(request.getBody())) {
requestPattern.withRequestBody(convertToValuePattern(appendBodyRegexpMatchPattern(request.getBody())));
}
else {
requestBodyGuessedFromMatchingStrategy(requestPattern);
}
}
private Object generateConcreteValue(Object originalBody) {
if (originalBody instanceof Pattern || originalBody instanceof RegexProperty) {
return new RegexProperty(originalBody).generate();
}
return originalBody;
}
private RequestPatternBuilder requestBodyGuessedFromMatchingStrategy(RequestPatternBuilder requestPattern) {
return requestPattern
.withRequestBody(convertToValuePattern(getMatchingStrategy(request.getBody().getClientValue())));
}
private static void addWireMockStubMatchingSection(BodyMatcher matcher, RequestPatternBuilder requestPattern,
Object body) {
Set<MatchingType> matchingTypesUnsupportedForRequest = new HashSet<>(
Arrays.asList(MatchingType.NULL, MatchingType.COMMAND, MatchingType.TYPE));
if (!(matcher instanceof PathBodyMatcher)) {
throw new IllegalArgumentException("Only jsonPath and XPath matchers can be processed.");
}
String retrievedValue = Optional.ofNullable(matcher.value()).map(Object::toString).orElseGet(() -> {
if (matchingTypesUnsupportedForRequest.contains(matcher.matchingType())) {
throw new IllegalArgumentException("Null, Command and Type matchers are not supported in requests.");
}
if (EQUALITY == matcher.matchingType()) {
return retrieveValue(matcher, body);
}
else {
return "";
}
});
PathBodyMatcher pathMatcher = (PathBodyMatcher) matcher;
requestPattern.withRequestBody(
WireMock.matchingXPath(pathMatcher.path(), XPathBodyMatcherToWireMockValuePatternConverter
.mapToPattern(pathMatcher.matchingType(), String.valueOf(retrievedValue))));
}
private boolean onlySizeAssertionsArePresent(JsonPaths values) {
return !CollectionUtils.isEmpty(values)
&& (request.getBodyMatchers() == null || !request.getBodyMatchers().hasMatchers())
&& this.every(values.iterator(), MethodBufferingJsonVerifiable::assertsSize);
}
private <T> boolean every(Iterator<T> self, Function<T, Boolean> function) {
while (self.hasNext()) {
if (!function.apply(self.next())) {
return false;
}
}
return true;
}
private void appendMultipart(RequestPatternBuilder requestPattern) {
if (request.getMultipart() == null) {
return;
}
if (request.getMultipart().getClientValue() instanceof Map) {
List<StringValuePattern> multipartPattern = ((Map<?, ?>) request.getMultipart()
.getClientValue())
.entrySet().stream().map(
it -> it.getValue() instanceof NamedProperty
? WireMock.matching(RegexPatterns.multipartFile(it.getKey(),
((NamedProperty) it.getValue()).getName().getClientValue(),
((NamedProperty) it.getValue()).getValue().getClientValue(),
Optional.ofNullable(
((NamedProperty) it.getValue()).getContentType())
.map(DslProperty::getClientValue).orElse(null)))
: WireMock.matching(RegexPatterns.multipartParam(it.getKey(),
MapConverter.getStubSideValuesForNonBody(it.getValue()))))
.collect(Collectors.toList());
multipartPattern.forEach(requestPattern::withRequestBody);
}
}
private void appendHeaders(RequestPatternBuilder requestPattern) {
if (request.getHeaders() != null) {
request.getHeaders().getEntries().forEach(header -> requestPattern.withHeader(header.getName(),
(StringValuePattern) convertToValuePattern(header.getClientValue())));
}
}
private void appendCookies(RequestPatternBuilder requestPattern) {
if (request.getCookies() == null) {
return;
}
request.getCookies().getEntries().forEach(cookie -> requestPattern.withCookie(cookie.getKey(),
(StringValuePattern) convertToValuePattern(cookie.getClientValue())));
}
private UrlPattern urlPattern() {
Object urlPath = urlPathOrUrlIfQueryPresent();
if (urlPath != null) {
if (urlPath instanceof Pattern || urlPath instanceof RegexProperty) {
return WireMock.urlPathMatching((String) getStubSideValue(new RegexProperty(urlPath).pattern()));
}
else {
return WireMock.urlPathEqualTo((String) getStubSideValue(urlPath.toString()));
}
}
if (request.getUrl() == null) {
throw new IllegalStateException("URL is required!");
}
Object url = getUrlIfGstring(request.getUrl().getClientValue());
if (url instanceof Pattern || url instanceof RegexProperty) {
return WireMock.urlMatching(new RegexProperty(url).pattern());
}
return WireMock.urlEqualTo(url.toString());
}
private Object urlPathOrUrlIfQueryPresent() {
Object urlPath = Optional.ofNullable(request).map(Request::getUrlPath).map(DslProperty::getClientValue)
.orElse(null);
Object queryParamsFromUrl = Optional.ofNullable(request).map(Request::getUrl).map(Url::getQueryParameters)
.map(QueryParameters::getParameters).orElse(null);
if (urlPath != null) {
return urlPath;
}
if (queryParamsFromUrl != null) {
return Optional.ofNullable(request).map(Request::getUrl).map(Url::getClientValue).orElse(null);
}
return null;
}
private Object getUrlIfGstring(Object clientSide) {
if (clientSide instanceof GString) {
if (Arrays.stream(((GString) clientSide).getValues()).anyMatch(it -> {
Object value = getStubSideValue(it);
return value instanceof Pattern || value instanceof RegexProperty;
})) {
String string = getStubSideValue(clientSide).toString();
return new RegexProperty(Pattern.compile(string));
}
else {
return getStubSideValue(clientSide).toString();
}
}
return clientSide;
}
private void appendQueryParameters(RequestPatternBuilder requestPattern) {
QueryParameters queryParameters = Optional.ofNullable(request).map(Request::getUrlPath)
.map(Url::getQueryParameters).orElseGet(() -> Optional.ofNullable(request).map(Request::getUrl)
.map(Url::getQueryParameters).orElse(null));
Optional.ofNullable(queryParameters).map(QueryParameters::getParameters).ifPresent(
parameters -> parameters.forEach(parameter -> requestPattern.withQueryParam(parameter.getName(),
(StringValuePattern) convertToValuePattern(parameter.getClientValue()))));
}
protected ContentPattern<?> convertToValuePattern(Object object) {
if (object instanceof Pattern || object instanceof RegexProperty) {
return WireMock.matching(new RegexProperty(object).pattern());
}
else if (object instanceof OptionalProperty) {
return WireMock.matching(((OptionalProperty) object).optionalPattern());
}
else if (object instanceof MatchingStrategy) {
MatchingStrategy value = (MatchingStrategy) object;
switch (value.getType()) {
case NOT_MATCHING:
return WireMock.notMatching(value.getClientValue().toString());
case ABSENT:
return WireMock.absent();
case EQUAL_TO:
return WireMock.equalTo(clientBody(value.getClientValue(), contentType).toString());
case CONTAINS:
return WireMock.containing(clientBody(value.getClientValue(), contentType).toString());
case MATCHING:
return WireMock.matching(clientBody(value.getClientValue(), contentType).toString());
case EQUAL_TO_JSON:
return WireMock.equalToJson(clientBody(value.getClientValue(), contentType).toString());
case EQUAL_TO_XML:
return WireMock.equalToXml(clientBody(value.getClientValue(), contentType).toString());
case BINARY_EQUAL_TO:
return WireMock.binaryEqualTo((byte[]) clientBody(value.getClientValue(), contentType));
default:
throw new UnsupportedOperationException("Unknown matching strategy " + value.getType());
}
}
else {
return WireMock.equalTo(clientBody(object, contentType).toString());
}
}
protected Object clientBody(Object bodyValue, ContentType contentType) {
if (FORM == contentType) {
if (bodyValue instanceof Map) {
// [a:3, b:4] == "a=3&b=4"
return ((Map<?, ?>) bodyValue).entrySet().stream()
.map(e -> StringEscapeUtils.unescapeEcmaScript(e.getKey().toString() + "=" + e.getValue()))
.collect(Collectors.joining("&"));
}
else if (bodyValue instanceof List) {
// ["a=3", "b=4"] == "a=3&b=4"
return ((List<?>) bodyValue).stream().map(it -> StringEscapeUtils.unescapeEcmaScript(it.toString()))
.collect(Collectors.joining("&"));
}
}
else if (bodyValue instanceof FromFileProperty) {
return ((FromFileProperty) bodyValue).isByte() ? ((FromFileProperty) bodyValue).asBytes()
: ((FromFileProperty) bodyValue).asString();
}
else if (JSON == contentType) {
return parseBody(bodyValue, contentType);
}
return bodyValue;
}
private MatchingStrategy getMatchingStrategyFromBody(Body body) {
if (body == null) {
return null;
}
return getMatchingStrategy(body.getClientValue());
}
private MatchingStrategy getMatchingStrategy(Object bodyValue) {
if (bodyValue instanceof GString) {
return this.getMatchingStrategy((GString) bodyValue);
}
else if (bodyValue instanceof MatchingStrategy) {
return this.getMatchingStrategy((MatchingStrategy) bodyValue);
}
else if (bodyValue instanceof FromFileProperty) {
return this.getMatchingStrategy((FromFileProperty) bodyValue);
}
else {
return tryToFindMachingStrategy(bodyValue);
}
}
private MatchingStrategy getMatchingStrategy(FromFileProperty bodyValue) {
return new MatchingStrategy(bodyValue, BINARY_EQUAL_TO);
}
private MatchingStrategy getMatchingStrategy(MatchingStrategy matchingStrategy) {
return getMatchingStrategyIncludingContentType(matchingStrategy);
}
private MatchingStrategy getMatchingStrategy(GString gString) {
if (gString == null) {
return new MatchingStrategy("", MatchingStrategy.Type.EQUAL_TO);
}
Object extractedValue = ContentUtils.extractValue(gString,
it -> it instanceof DslProperty ? ((DslProperty<?>) it).getClientValue() : getStringFromGString(it));
Object value = getStringFromGString(extractedValue);
return getMatchingStrategy(value);
}
private Object getStringFromGString(Object object) {
return object instanceof GString ? object.toString() : object;
}
private MatchingStrategy tryToFindMachingStrategy(Object bodyValue) {
return new MatchingStrategy(MapConverter.transformToClientValues(bodyValue),
getEqualsTypeFromContentType(contentType));
}
private MatchingStrategy getMatchingStrategyIncludingContentType(MatchingStrategy matchingStrategy) {
MatchingStrategy.Type type = matchingStrategy.getType();
Object value = matchingStrategy.getClientValue();
ContentType contentType = ContentUtils.recognizeContentTypeFromMatchingStrategy(type);
if (contentType == ContentType.UNKNOWN && type == MatchingStrategy.Type.EQUAL_TO) {
contentType = ContentUtils.recognizeContentTypeFromContent(value);
type = getEqualsTypeFromContentType(contentType);
}
MatchingStrategy newMatchingStrategy;
if (value instanceof Map) {
newMatchingStrategy = new MatchingStrategy(parseBody((Map<?, ?>) value, contentType), type);
}
else if (value instanceof List) {
newMatchingStrategy = new MatchingStrategy(parseBody((List<?>) value, contentType), type);
}
else if (value instanceof GString) {
newMatchingStrategy = new MatchingStrategy(parseBody((GString) value, contentType), type);
}
else {
newMatchingStrategy = new MatchingStrategy(parseBody(value, contentType), type);
}
return newMatchingStrategy;
}
private MatchingStrategy appendBodyRegexpMatchPattern(Object value, ContentType contentType) {
Object clientValue = MapConverter.transformToClientValues(value);
switch (contentType) {
case JSON:
return new MatchingStrategy(buildJSONRegexpMatch(clientValue), MatchingStrategy.Type.MATCHING);
case UNKNOWN:
return new MatchingStrategy(buildGStringRegexpForStubSide(clientValue), MatchingStrategy.Type.MATCHING);
default:
throw new IllegalStateException(contentType.name() + " pattern matching is not implemented yet");
}
}
private MatchingStrategy appendBodyRegexpMatchPattern(Object value) {
return appendBodyRegexpMatchPattern(value, ContentType.UNKNOWN);
}
private boolean containsPattern(Object o) {
if (o instanceof GString) {
return containsPattern(((GString) o).getValues());
}
else if (o instanceof Map) {
return containsPattern(((Map<?, ?>) o).entrySet());
}
else if (o instanceof Collection) {
List<Boolean> result = (List<Boolean>) ((Collection) o).stream().map(this::containsPattern)
.collect(Collectors.toList());
return result.stream().reduce(false, (a, b) -> a || b);
}
else if (o instanceof Object[]) {
return containsPattern(Arrays.asList((Object[]) o));
}
else if (o instanceof Map.Entry<?, ?>) {
return containsPattern(((Map.Entry<?, ?>) o).getValue());
}
else if (o instanceof RegexProperty) {
return true;
}
else if (o instanceof DslProperty<?>) {
return containsPattern(((DslProperty<?>) o).getClientValue());
}
else {
return o instanceof Pattern;
}
}
}

View File

@@ -19,13 +19,13 @@ package org.springframework.cloud.contract.verifier.dsl.wiremock;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.extension.Extension;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.HttpHeaders;
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
import groovy.lang.Closure;
import groovy.lang.GString;
import org.springframework.cloud.contract.spec.Contract;
@@ -122,9 +122,9 @@ class WireMockResponseStubStrategy extends BaseWireMockStubStrategy {
}
}
Closure parsingClosureForContentType() {
return contractMetadata.getDefinedOutputStubContentType().contains("/stream") ? Closure.IDENTITY
: MapConverter.JSON_PARSING_CLOSURE;
Function<String, Object> parsingClosureForContentType() {
return contractMetadata.getDefinedOutputStubContentType().contains("/stream") ? MapConverter.IDENTITY
: MapConverter.JSON_PARSING_FUNCTION;
}
private void appendResponseDelayTime(ResponseDefinitionBuilder builder) {

View File

@@ -0,0 +1,442 @@
/*
* Copyright 2013-2020 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.file;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wiremock.com.google.common.collect.HashMultiset;
import wiremock.com.google.common.collect.ListMultimap;
import wiremock.com.google.common.collect.Multimap;
import wiremock.com.google.common.collect.Multiset;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractConverter;
import org.springframework.cloud.contract.verifier.converter.YamlContractConverter;
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
/**
* Scans the provided file path for the DSLs. There's a possibility to provide inclusion
* and exclusion filters.
*
* @author Jakub Kubrynski, codearte.io
* @author Stessy Delcroix
* @since 1.0.0
*/
public class ContractFileScanner {
private static final Logger LOG = LoggerFactory.getLogger(ContractFileScanner.class);
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;
private final Set<PathMatcher> excludeMatchers;
private final Set<PathMatcher> ignoreMatchers;
private final Set<PathMatcher> includeMatchers;
private final String includeMatcher;
public ContractFileScanner(File baseDir, Set<String> excluded, Set<String> ignored, Set<String> included,
String includeMatcher) {
this.baseDir = baseDir;
this.excludeMatchers = processPatterns(excluded != null ? excluded : Collections.emptySet());
this.ignoreMatchers = processPatterns(ignored != null ? ignored : Collections.emptySet());
this.includeMatchers = processPatterns(included != null ? included : Collections.emptySet());
this.includeMatcher = includeMatcher != null ? includeMatcher : "";
}
private Set<PathMatcher> processPatterns(Set<String> patterns) {
FileSystem fileSystem = FileSystems.getDefault();
Set<PathMatcher> pathMatchers = new HashSet<>();
for (String pattern : patterns) {
String syntaxAndPattern = MATCH_PREFIX + "**" + File.separator + pattern;
// FIXME: This looks strange, need to be checked on windows
if (IS_OS_WINDOWS) {
syntaxAndPattern = syntaxAndPattern.replace("\\", "\\\\");
}
pathMatchers.add(fileSystem.getPathMatcher(syntaxAndPattern));
}
return pathMatchers;
}
/**
* @return for a map of paths for which a list of matching contracts has been found
* @deprecated use the {@link ContractFileScanner#findContractsRecursively} version
*/
@Deprecated
public ListMultimap<Path, ContractMetadata> findContracts() {
MultiValueMap<Path, ContractMetadata> contracts = findContractsRecursively();
return new ListMultimap<Path, ContractMetadata>() {
@Override
public List<ContractMetadata> get(Path key) {
return contracts.get(key);
}
@Override
public List<ContractMetadata> removeAll(Object key) {
return contracts.remove(key);
}
@Override
public List<ContractMetadata> replaceValues(Path key, Iterable<? extends ContractMetadata> values) {
return contracts.put(key, asList(values));
}
List<ContractMetadata> asList(Iterable<? extends ContractMetadata> self) {
if (self instanceof List) {
return (List<ContractMetadata>) self;
}
else {
return toList(self.iterator());
}
}
private List<ContractMetadata> toList(Iterator<? extends ContractMetadata> self) {
List<ContractMetadata> answer = new ArrayList<>();
while (self.hasNext()) {
answer.add(self.next());
}
return answer;
}
@Override
public Map<Path, Collection<ContractMetadata>> asMap() {
return contracts.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public int size() {
return contracts.size();
}
@Override
public boolean isEmpty() {
return contracts.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return contracts.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return contracts.entrySet()
.stream()
.anyMatch(it -> it.getValue().contains(value));
}
@Override
public boolean containsEntry(Object key, Object value) {
return contracts.entrySet()
.stream()
.anyMatch(it -> it.getKey().equals(key) && it.getValue().contains(value));
}
@Override
public boolean put(Path key, ContractMetadata value) {
contracts.add(key, value);
return true;
}
@Override
public boolean remove(Object key, Object value) {
return contracts.getOrDefault(key, new ArrayList<>()).remove(value);
}
@Override
public boolean putAll(Path key, Iterable<? extends ContractMetadata> values) {
return contracts.getOrDefault(key, new ArrayList<>())
.addAll(StreamSupport.stream(values.spliterator(), false)
.collect(Collectors.toList()));
}
@Override
public boolean putAll(Multimap<? extends Path, ? extends ContractMetadata> multimap) {
multimap.entries().forEach(it -> contracts.add(it.getKey(), it.getValue()));
return true;
}
@Override
public void clear() {
contracts.clear();
}
@Override
public Set<Path> keySet() {
return contracts.keySet();
}
@Override
public Multiset<Path> keys() {
return HashMultiset.create(contracts.keySet());
}
@Override
public Collection<ContractMetadata> values() {
return contracts.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
}
@Override
public Collection<Map.Entry<Path, ContractMetadata>> entries() {
Collection<Map.Entry<Path, ContractMetadata>> entries = new LinkedList<>();
contracts.forEach((path, list) -> list
.forEach(c -> entries.add(new AbstractMap.SimpleEntry<>(path, c))));
return entries;
}
};
}
public MultiValueMap<Path, ContractMetadata> findContractsRecursively() {
MultiValueMap<Path, ContractMetadata> result = CollectionUtils.toMultiValueMap(new LinkedHashMap<>());
appendRecursively(baseDir, result);
return result;
}
/**
* We iterate over found contracts, filter out those that should be excluded and try
* to convert via pluggable Contract Converters any possible contracts.
*/
private void appendRecursively(File baseDir, MultiValueMap<Path, ContractMetadata> result) {
List<ContractConverter> converters = convertersWithYml();
if (LOG.isTraceEnabled()) {
LOG.trace("Found the following contract converters ${converters}");
}
File[] files = baseDir.listFiles();
if (files == null) {
return;
}
Arrays.sort(files);
for (int i = 0; i < files.length; i++) {
File file = files[i];
boolean excluded = matchesPattern(file, excludeMatchers);
if (!excluded) {
boolean contractFile = isContractFile(file);
boolean included = StringUtils.isEmpty(includeMatcher) || file.getAbsolutePath()
.matches(includeMatcher);
included = !CollectionUtils.isEmpty(includeMatchers) ? matchesPattern(file, includeMatchers) : included;
if (contractFile && included) {
addContractToTestGeneration(result, files, file, i,
ContractVerifierDslConverter.convertAsCollection(baseDir, file));
}
if (!contractFile && included) {
addContractToTestGeneration(converters, result, files, file, i);
}
else {
appendRecursively(file, result);
if (LOG.isDebugEnabled()) {
LOG.debug(
"File [$file] is ignored. Is a contract file? [$contractFile]. Should be included by pattern? [$included]");
}
}
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("File [$file] is ignored. Should be excluded? [$excluded]");
}
}
}
}
protected List<ContractConverter> convertersWithYml() {
List<ContractConverter> converters = converters();
converters.add(ContractVerifierDslConverter.INSTANCE);
converters.add(YamlContractConverter.INSTANCE);
return converters;
}
protected List<ContractConverter> converters() {
return SpringFactoriesLoader.loadFactories(ContractConverter.class, null);
}
private void addContractToTestGeneration(List<ContractConverter> converters,
MultiValueMap<Path, ContractMetadata> result, File[] files, File file, int index) {
boolean converted = false;
if (!file.isDirectory()) {
for (ContractConverter converter : converters) {
Collection<Contract> contracts = tryConvert(converter, file);
if (contracts != null) {
addContractToTestGeneration(result, files, file, index, contracts);
converted = true;
break;
}
}
}
if (!converted) {
appendRecursively(file, result);
if (LOG.isDebugEnabled()) {
LOG.debug(
"File [$file] wasn't ignored but no converter was applicable. The file is a directory [${file.isDirectory()}]");
}
}
}
private Collection<Contract> tryConvert(ContractConverter converter, File file) {
boolean accepted = converter.isAccepted(file);
if (!accepted) {
return null;
}
try {
return converter.convertFrom(file);
}
catch (Exception e) {
throw new IllegalStateException("Failed to convert file [" + file + "]", e);
}
}
private void addContractToTestGeneration(MultiValueMap<Path, ContractMetadata> result, File[] files, File file,
int index, Collection<Contract> convertedContract) {
Path path = file.toPath();
Integer order = null;
if (hasScenarioFilenamePattern(path)) {
order = index;
}
Path parent = file.getParentFile().toPath();
ContractMetadata metadata = new ContractMetadata(path, matchesPattern(file, ignoreMatchers), files.length,
order, convertedContract);
if (LOG.isDebugEnabled()) {
LOG.debug("Creating a contract entry for path [" + path + "] and metadata [" + metadata + "]");
}
result.add(parent, metadata);
}
private boolean hasScenarioFilenamePattern(Path path) {
return SCENARIO_STEP_FILENAME_PATTERN.matcher(path.getFileName().toString()).matches();
}
private boolean matchesPattern(File file, Set<PathMatcher> matchers) {
for (PathMatcher matcher : matchers) {
if (matcher.matches(file.toPath())) {
return true;
}
LOG.debug("Path [{}] doesn't match the pattern [{}]", file.toPath(), matcher);
}
return false;
}
private boolean isContractFile(File file) {
return file.isFile() && ContractVerifierDslConverter.INSTANCE.isAccepted(file);
}
/**
* 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);
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private File baseDir;
private Set<String> excluded;
private Set<String> ignored;
private Set<String> included = Collections.emptySet();
private String includeMatcher = "";
public Builder baseDir(File baseDir) {
this.baseDir = baseDir;
return this;
}
public Builder excluded(Set<String> excluded) {
this.excluded = excluded;
return this;
}
public Builder ignored(Set<String> ignored) {
this.ignored = ignored;
return this;
}
public Builder included(Set<String> included) {
this.included = included;
return this;
}
public Builder includeMatcher(String includeMatcher) {
this.includeMatcher = includeMatcher;
return this;
}
public ContractFileScanner build() {
return new ContractFileScanner(this.baseDir,
this.excluded,
this.ignored,
this.included,
this.includeMatcher);
}
}
}

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2013-2020 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.util;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import groovy.lang.GroovyShell;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.ContractConverter;
import org.springframework.cloud.function.compiler.java.CompilationResult;
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler;
import org.springframework.util.ObjectUtils;
/**
* Converts a String or a Groovy or Java file into a {@link Contract}.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Stessy Delcroix
* @since 1.0.0
*/
public class ContractVerifierDslConverter implements ContractConverter<Collection<Contract>> {
private static final Logger LOG = LoggerFactory.getLogger(ContractVerifierDslConverter.class);
/**
* {@link ContractVerifierDslConverter} instance.
*/
public static final ContractVerifierDslConverter INSTANCE = new ContractVerifierDslConverter();
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".*package (.+?);.+?", Pattern.DOTALL);
private static final Pattern CLASS_PATTERN = Pattern.compile(".+?class (.+?)( |\\{).+?", Pattern.DOTALL);
private static final RuntimeJavaCompiler COMPILER = new RuntimeJavaCompiler();
/**
* @deprecated - use
* {@link ContractVerifierDslConverter#convertAsCollection(java.io.File, java.lang.String)}
*/
@Deprecated
public static Collection<Contract> convertAsCollection(String dsl) {
try {
Object object = groovyShell().evaluate(dsl);
return listOfContracts(object);
}
catch (DslParseException e) {
throw e;
}
catch (Exception e) {
LOG.error("Exception occurred while trying to evaluate the contract", e);
throw new DslParseException(e);
}
}
public static Collection<Contract> convertAsCollection(File rootFolder, String dsl) {
ClassLoader classLoader = ContractVerifierDslConverter.class.getClassLoader();
try {
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader);
Object object = groovyShell(urlCl, rootFolder).evaluate(dsl);
return listOfContracts(object);
}
catch (DslParseException e) {
throw e;
}
catch (Exception e) {
LOG.error("Exception occurred while trying to evaluate the contract", e);
throw new DslParseException(e);
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
public static Collection<Contract> convertAsCollection(File dsl) {
return convertAsCollection(dsl.getParentFile(), dsl);
}
public static Collection<Contract> convertAsCollection(File rootFolder, File dsl) {
ClassLoader classLoader = ContractVerifierDslConverter.class.getClassLoader();
try {
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader);
Object object = toObject(urlCl, rootFolder, dsl);
return listOfContracts(dsl, object);
}
catch (DslParseException e) {
throw e;
}
catch (Exception e) {
LOG.error("Exception occurred while trying to evaluate the contract at path [" + dsl.getPath() + "]", e);
throw new DslParseException(e);
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
private static ClassLoader updatedClassLoader(File rootFolder, ClassLoader classLoader) {
ClassLoader urlCl;
try {
urlCl = URLClassLoader.newInstance(
Collections.singletonList(rootFolder.toURI().toURL()).toArray(new URL[0]), classLoader);
}
catch (MalformedURLException e) {
LOG.error("Exception occurred while trying to construct the URL from the root folder at path ["
+ rootFolder.getPath() + "]", e);
throw new DslParseException(e);
}
updateTheThreadClassLoader(urlCl);
return urlCl;
}
private static void updateTheThreadClassLoader(ClassLoader urlCl) {
Thread.currentThread().setContextClassLoader(urlCl);
}
private static GroovyShell groovyShell() {
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setSourceEncoding("UTF-8");
return new GroovyShell(ContractVerifierDslConverter.class.getClassLoader(), compilerConfiguration);
}
private static Object toObject(ClassLoader cl, File rootFolder, File dsl) throws IOException {
if (isJava(dsl)) {
try {
return parseJavaFile(dsl);
}
catch (Exception ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("Exception occurred while trying to parse the file [" + dsl
+ "] as a contract. Will not parse it.", ex);
}
return null;
}
}
return groovyShell(cl, rootFolder).evaluate(dsl);
}
private static Object parseJavaFile(File dsl) throws IllegalAccessException, InvocationTargetException,
InstantiationException, IOException, NoSuchMethodException {
Constructor<?> constructor = classConstructor(dsl);
Object newInstance = constructor.newInstance();
if (!(newInstance instanceof Supplier)) {
if (LOG.isDebugEnabled()) {
LOG.debug("The class [" + dsl + "] is not instance of Supplier. Will not parse it as a contract");
}
return null;
}
Supplier<?> supplier = (Supplier<?>) newInstance;
return supplier.get();
}
private static Constructor<?> classConstructor(File dsl)
throws IllegalAccessException, IOException, NoSuchMethodException {
String classText = Files.lines(Paths.get(dsl.getAbsolutePath())).collect(Collectors.joining("\n"));
String fqn = fqn(classText);
CompilationResult compilationResult = COMPILER.compile(fqn, classText);
if (!compilationResult.wasSuccessful()) {
throw new IllegalStateException("Exceptions occurred while trying to compile the file "
+ compilationResult.getCompilationMessages());
}
Class<?> clazz = compilationResult.getCompiledClasses().stream().filter(it -> it.getName().equals(fqn))
.findFirst().orElseThrow(() -> new IllegalStateException("Class with name [" + fqn + "] not found"));
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor;
}
private static boolean isJava(File dsl) {
return dsl.getName().endsWith(".java");
}
private static String fqn(String classText) throws IllegalAccessException {
Matcher packageMatcher = PACKAGE_PATTERN.matcher(classText);
String fqn = "";
if (packageMatcher.matches()) {
fqn = packageMatcher.group(1) + ".";
}
Matcher classMatcher = CLASS_PATTERN.matcher(classText);
if (!classMatcher.matches()) {
throw new IllegalAccessException("Can't parse the class name");
}
return fqn + classMatcher.group(1);
}
private static GroovyShell groovyShell(ClassLoader cl, File rootFolder) {
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setSourceEncoding("UTF-8");
compilerConfiguration.setClasspathList(Collections.singletonList(rootFolder.getAbsolutePath()));
return new GroovyShell(cl, compilerConfiguration);
}
private static Collection<Contract> listOfContracts(Object object) {
if (object instanceof Collection) {
return (Collection<Contract>) object;
}
else if (!(object instanceof Contract)) {
throw new DslParseException("Contract is not returning a Contract or list of Contracts");
}
return Collections.singletonList((Contract) object);
}
private static Collection<Contract> listOfContracts(File file, Object object) {
if (object == null) {
return Collections.emptyList();
}
else if (isACollectionOfContracts(object)) {
return withName(file, (Collection<Contract>) object);
}
else if (!(object instanceof Contract)) {
throw new DslParseException("Contract is not returning a Contract or list of Contracts");
}
return withName(file, Collections.singletonList((Contract) object));
}
private static boolean isACollectionOfContracts(Object object) {
return object instanceof Collection && ((Collection) object).stream().allMatch(it -> it instanceof Contract);
}
private static Collection<Contract> withName(File file, Collection<Contract> contracts) {
AtomicInteger counter = new AtomicInteger(0);
return contracts.stream().peek(it -> {
if (contractNameEmpty(it)) {
it.name(NamesUtil.defaultContractName(file, contracts, counter.get()));
}
counter.getAndIncrement();
}).collect(Collectors.toList());
}
private static boolean contractNameEmpty(Contract it) {
return it != null && ObjectUtils.isEmpty(it.getName());
}
@Override
public boolean isAccepted(File file) {
return file.getName().endsWith(".groovy") || file.getName().endsWith(".gvy")
|| file.getName().endsWith(".java");
}
@Override
public Collection<Contract> convertFrom(File file) {
return convertAsCollection(file);
}
@Override
public Collection<Contract> convertTo(Collection<Contract> contract) {
return contract;
}
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2013-2020 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.util;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import groovy.json.JsonSlurper;
import groovy.lang.Closure;
import groovy.lang.GString;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.FromFileProperty;
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor;
import org.springframework.cloud.contract.verifier.template.TemplateProcessor;
/**
* Converts an object into either client or server side representation. Iterates over the
* structure of an object (depending on whether it's an iterable or a primitive type
* etc.), converts the {@link DslProperty} into their client / server representation and
* returns the result
*
* @author Marcin Grzejszczak
* @author Stessy Delcroix
* @since 1.1.0
*/
public class MapConverter {
private static final boolean STUB_SIDE = true;
private static final boolean TEST_SIDE = false;
/**
* Generic {@link Function} used to deserialize a json file.
*/
public static final Function<String, Object> JSON_PARSING_FUNCTION = (value) -> {
try {
return new ObjectMapper().readValue(value, Object.class);
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException("The current json [" + value + "] could not be deserialized");
}
};
/**
* Generic {@link Closure} used to deserialize a json file.
*/
public static final Closure<Object> JSON_PARSING_CLOSURE = new Closure<Object>(null) {
public Object doCall(Object it) {
return new JsonSlurper().parseText((String) it);
}
};
/**
* Function used to return its input argument. {@link Function#identity()} cannot be
* used as the return type is Function&lt;T,T&gt;, whilst this function return type is
* Function&lt;T,R&gt;
*/
public static final Function<String, Object> IDENTITY = (value) -> value;
private final TemplateProcessor templateProcessor;
MapConverter() {
this.templateProcessor = processor();
}
private TemplateProcessor processor() {
return new HandlebarsTemplateProcessor();
}
/**
* @return the object with client side values of
* {@link org.springframework.cloud.contract.spec.internal.DslProperty}
*/
public static Object transformToClientValues(Object value) {
return transformValues(value, (v) -> v instanceof DslProperty ? ((DslProperty<?>) v).getClientValue() : v);
}
public static Object transformValues(Object value, Function<Object, ?> function) {
return transformValues(value, function, JSON_PARSING_FUNCTION);
}
/**
* Iterates over the structure of the object and executes the function on each element
* of that structure.
* @return the transformed structure
*/
public static Object transformValues(Object value, Function<Object, ?> function,
Function<String, Object> parsingFunction) {
if (value instanceof String) {
try {
Object parsed = parsingFunction.apply((String) value);
if (parsed instanceof Map) {
return convert((Map) parsed, function, parsingFunction);
}
else if (parsed instanceof List) {
return transformValues(parsed, function, parsingFunction);
}
}
catch (Exception ignore) {
}
return extractValue(value, function);
}
else if (value instanceof Map) {
return convert((Map) value, function, parsingFunction);
}
else if (value instanceof List) {
return ((List) value).stream().map((v) -> transformValues(v, function, parsingFunction))
.collect(Collectors.toList());
}
return transformValue(function, value, parsingFunction);
}
/**
* Transforms a value with the given function. Needs to be protected, otherwise method
* access exception will occur at runtime.
*/
protected static Object transformValue(Function<Object, ?> function, Object value,
Function<String, Object> parsingFunction) {
return extractValue(value, (val) -> {
Object newValue = function.apply(val);
if (newValue instanceof Map || newValue instanceof List || newValue instanceof String && val != null) {
return transformValues(newValue, function, parsingFunction);
}
return newValue;
});
}
private static Object extractValue(Object value, Function<Object, ?> function) {
try {
return function.apply(value);
}
catch (Exception ignore) {
return value;
}
}
private static Map<?, ?> convert(Map<?, ?> map, Function<Object, ?> function,
Function<String, Object> parsingFunction) {
Map<Object, Object> convertedMap = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
convertedMap.put(entry.getKey(), transformValues(entry.getValue(), function, parsingFunction));
}
return convertedMap;
}
public static Object getClientOrServerSideValues(Object json, boolean clientSide) {
return getClientOrServerSideValues(json, clientSide, JSON_PARSING_FUNCTION);
}
/**
* If {@code clientSide} is {@code true} returns the client side value for the
* provided object.
*/
public static Object getClientOrServerSideValues(Object json, boolean clientSide,
Function<String, Object> parsingFunction) {
return transformValues(json, val -> {
if (val instanceof DslProperty) {
DslProperty<?> dslProperty = ((DslProperty<?>) val);
return clientSide
? getClientOrServerSideValues(dslProperty.getClientValue(), clientSide, parsingFunction)
: getClientOrServerSideValues(dslProperty.getServerValue(), clientSide, parsingFunction);
}
else if (val instanceof GString) {
ContentType type = new MapConverter().templateProcessor.containsJsonPathTemplateEntry(
ContentUtils.extractValueForGString((GString) val, ContentUtils.GET_TEST_SIDE).toString())
? ContentType.TEXT : null;
return ContentUtils.extractValue((GString) val, type, (v) -> {
if (v instanceof DslProperty) {
return clientSide
? getClientOrServerSideValues(((DslProperty<?>) v).getClientValue(), clientSide,
parsingFunction)
: getClientOrServerSideValues(((DslProperty<?>) v).getServerValue(), clientSide,
parsingFunction);
}
return v;
});
}
else if (val instanceof FromFileProperty) {
return ((FromFileProperty) val).isByte() ? ((FromFileProperty) val).asBytes()
: ((FromFileProperty) val).asString();
}
return val;
}, parsingFunction);
}
public static Object getStubSideValues(Object json) {
return getClientOrServerSideValues(json, STUB_SIDE, JSON_PARSING_FUNCTION);
}
public static Object getStubSideValues(Object json, Function<String, Object> parsingClosure) {
return getClientOrServerSideValues(json, STUB_SIDE, parsingClosure);
}
public static Object getTestSideValues(Object json) {
return getTestSideValues(json, JSON_PARSING_FUNCTION);
}
public static Object getTestSideValues(Object json, Function<String, Object> parsingClosure) {
return getClientOrServerSideValues(json, TEST_SIDE, parsingClosure);
}
public static Object getTestSideValuesForText(Object json) {
return getClientOrServerSideValues(json, TEST_SIDE, IDENTITY);
}
public static Object getStubSideValuesForNonBody(Object object) {
return getClientOrServerSideValues(object, STUB_SIDE, IDENTITY);
}
public static Object getTestSideValuesForNonBody(Object object) {
return getClientOrServerSideValues(object, TEST_SIDE, IDENTITY);
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2013-2020 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.util;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import groovy.lang.GString;
import org.codehaus.groovy.runtime.GStringImpl;
import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.util.RegexpUtils;
import static org.apache.commons.text.StringEscapeUtils.escapeJson;
import static org.springframework.cloud.contract.verifier.util.ContentType.JSON;
import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue;
/**
* Useful utility methods to work with regular expressions.
*
* @since 1.0.0
*/
public final class RegexpBuilders {
private final static String WS = "/\\s*/";
private static final Function<DslProperty<?>, Object> CLIENT_VALUE_EXTRACTOR = DslProperty::getClientValue;
private RegexpBuilders() {
}
/**
* Converts the {@link Object} passed values into their stub side String
* representations.
*/
public static String buildGStringRegexpForStubSide(Object o) {
if (o instanceof DslProperty) {
return buildGStringRegexpForStubSide((DslProperty<?>) o);
}
else if (o instanceof Pattern) {
return buildGStringRegexpForStubSide((Pattern) o);
}
else if (o instanceof GString) {
return buildGStringRegexpForStubSide((GString) o);
}
return escapeSpecialRegexChars(o.toString());
}
/**
* Converts the {@link GString} passed values into their stub side String
* representations.
*/
static String buildGStringRegexpForStubSide(GString gString) {
return new GStringImpl(
Stream.of(gString.getValues()).map(RegexpBuilders::buildGStringRegexpForStubSide).map(s -> (Object) s)
.toArray(),
Stream.of(gString.getStrings()).map(RegexpBuilders::escapeSpecialRegexChars).toArray(String[]::new))
.toString();
}
/**
* Converts the {@link Pattern} passed values into their stub side String
* representations.
*/
static String buildGStringRegexpForStubSide(Pattern pattern) {
return pattern.pattern();
}
/**
* Converts the {@link org.springframework.cloud.contract.spec.internal.DslProperty}
* passed values into their stub side String representations.
*/
static String buildGStringRegexpForStubSide(DslProperty<?> dslProperty) {
return buildGStringRegexpForStubSide(dslProperty.getClientValue());
}
/**
* Converts the {@link GString} passed values into their test side String
* representations.
*/
public static String buildGStringRegexpForTestSide(GString gString) {
return new GStringImpl(
Stream.of(gString.getValues()).map(RegexpBuilders::buildGStringRegexpForTestSide).map(s -> (Object) s)
.toArray(),
Stream.of(gString.getStrings()).map(RegexpBuilders::escapeSpecialRegexChars).toArray(String[]::new))
.toString();
}
/**
* Converts the {@link Object} passed values into their test side String
* representations.
*/
public static String buildGStringRegexpForTestSide(Object o) {
return o.toString().replaceAll("\\\\", "\\\\\\\\");
}
public static String escapeSpecialRegexChars(String str) {
return RegexpUtils.escapeSpecialRegexChars(str);
}
public static String buildJSONRegexpMatch(GString gString) {
return buildJSONRegexpMatch(extractValue(gString, JSON, CLIENT_VALUE_EXTRACTOR));
}
public static String buildJSONRegexpMatch(Map<String, Object> jsonMap) {
return WS + "\\{"
+ jsonMap.entrySet().stream().map(RegexpBuilders::buildJSONRegexpMatch).collect(Collectors.joining(","))
+ "\\}" + WS;
}
public static String buildJSONRegexpMatch(List<?> jsonList) {
return WS + "\\[" + jsonList.stream().map(RegexpBuilders::buildJSONRegexpMatch).collect(Collectors.joining(","))
+ "\\]" + WS;
}
/**
* Converts the map into String representation of regular expressions.
*/
public static String buildJSONRegexpMatch(Map.Entry<String, Object> entry) {
return buildJSONRegexpMatchString(escapeJson(entry.getKey())) + ":" + buildJSONRegexpMatch(entry.getValue());
}
/**
* Converts the object into String representation of regular expressions.
*/
public static String buildJSONRegexpMatch(Object value) {
return buildJSONRegexpMatchStringOptionalQuotes(escapeJson(value.toString()));
}
/**
* Converts the pattern into String representation of regular expressions.
*/
public static String buildJSONRegexpMatch(Pattern pattern) {
return buildJSONRegexpMatchStringOptionalQuotes(pattern.pattern());
}
/**
* Converts the String into String representation of regular expressions.
*/
public static String buildJSONRegexpMatchString(String value) {
return WS + '"' + value + '"' + WS;
}
/**
* Converts the String into an optional String representation of regular expressions.
*/
public static String buildJSONRegexpMatchStringOptionalQuotes(String value) {
return WS + "\"?" + value + "\"?" + WS;
}
}

View File

@@ -55,7 +55,7 @@ public class TestGeneratorTests {
r.status(r.OK());
});
})));
ContractFileScanner scanner = new ContractFileScanner(null, null, null) {
ContractFileScanner scanner = new ContractFileScanner(null, null, null, null, null) {
@Override
public MultiValueMap<Path, ContractMetadata> findContractsRecursively() {
return multimap;
@@ -92,7 +92,7 @@ public class TestGeneratorTests {
r.status(r.OK());
});
})));
ContractFileScanner scanner = new ContractFileScanner(null, null, null) {
ContractFileScanner scanner = new ContractFileScanner(null, null, null, null, null) {
@Override
public MultiValueMap<Path, ContractMetadata> findContractsRecursively() {
return multimap;

View File

@@ -1,376 +0,0 @@
/*
* Copyright 2013-2020 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.assertion
import org.assertj.core.api.Assertions
import spock.lang.Specification
/**
* @author Marcin Grzejszczak, Artem Ptushkin
*/
class CollectionAssertSpec extends Specification {
def should_not_throw_an_exception_when_all_elements_match_regex() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection).allElementsMatch("[a-z]")
then:
noExceptionThrown()
}
def should_throw_an_exception_when_at_least_one_element_doesnt_match_regex() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection)
.allElementsMatch("[0-9]")
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"The value <a> doesn't match the regex <[0-9]>")
}
def should_throw_an_exception_when_element_is_null() {
setup:
Collection collection = collectionWithNulls()
when:
SpringCloudContractAssertions.assertThat(collection)
.allElementsMatch("[0-9]")
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"The value <null> doesn't match the regex <[0-9]>")
}
def should_throw_an_exception_when_collection_is_null() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection).allElementsMatch("foo")
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_throw_an_exception_when_collection_is_empty() {
setup:
Collection collection = new ArrayList()
when:
SpringCloudContractAssertions.assertThat(collection).allElementsMatch("foo")
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be empty")
}
def should_not_throw_an_exception_when_flattened_size_is_greater_than_or_equal_to_provided_size() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeGreaterThanOrEqualTo(0)
.hasFlattenedSizeGreaterThanOrEqualTo(4)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_flattened_size_is_not_greater_than_or_equal_to_provided_size() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeGreaterThanOrEqualTo(5)
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"The flattened size <4> is not greater or equal to <5>")
}
def should_throw_an_exception_when_collection_is_null_for_flattened_greater_than_or_equal() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeGreaterThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_not_throw_an_exception_when_flattened_size_is_less_than_or_equal_to_provided_size() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeLessThanOrEqualTo(5)
.hasFlattenedSizeLessThanOrEqualTo(4)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_flattened_size_is_not_less_than_or_equal_to_provided_size() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeLessThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"The flattened size <4> is not less or equal to <1>")
}
def should_throw_an_exception_when_collection_is_null_for_flattened_less_than_or_equal() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeLessThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_not_throw_an_exception_when_flattened_size_is_between_the_provided_sizes() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection).hasFlattenedSizeBetween(1, 5)
.hasFlattenedSizeBetween(4, 4)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_flattened_size_is_not_between_the_provided_sizes() {
setup:
Collection collection = nestedCollection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeBetween(5, 7)
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"The flattened size <4> is not between <5> and <7>")
}
def should_throw_an_exception_when_collection_is_null_for_flattened_between() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection)
.hasFlattenedSizeBetween(1, 2)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_not_throw_an_exception_when_size_is_greater_than_or_equal_to_provided_size() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasSizeGreaterThanOrEqualTo(0).hasSizeGreaterThanOrEqualTo(3)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_size_is_not_greater_than_or_equal_to_provided_size() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasSizeGreaterThanOrEqualTo(5)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("The size <3> is not greater or equal to <5>")
}
def should_throw_an_exception_when_collection_is_null_for_greater_than_or_equal() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection)
.hasSizeGreaterThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_not_throw_an_exception_when_size_is_less_than_or_equal_to_provided_size() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection).hasSizeLessThanOrEqualTo(4)
.hasSizeLessThanOrEqualTo(3)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_size_is_not_less_than_or_equal_to_provided_size() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection)
.hasSizeLessThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("The size <3> is not less or equal to <1>")
}
def should_throw_an_exception_when_collection_is_null_for_less_than_or_equal() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection)
.hasSizeLessThanOrEqualTo(1)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
def should_not_throw_an_exception_when_size_is_between_the_provided_sizes() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection).hasSizeBetween(1, 4)
.hasSizeBetween(3, 3)
then:
noExceptionThrown()
}
def should_throw_an_exception_when_size_is_not_between_the_provided_sizes() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection).hasSizeBetween(5, 7)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("The size <3> is not between <5> and <7>")
}
def should_not_break_compilation_when_using_as() {
setup:
Collection collection = collection()
when:
SpringCloudContractAssertions.assertThat(collection).as("for jsonpath x.y.z")
.hasSizeBetween(5, 7)
then:
AssertionError e = thrown()
Assertions.assertThat(e).hasMessageContaining(
"[for jsonpath x.y.z] The size <3> is not between <5> and <7>")
}
def should_throw_an_exception_when_collection_is_null_for_between() {
setup:
Collection collection = null
when:
SpringCloudContractAssertions.assertThat(collection).hasSizeBetween( 1, 2)
then:
AssertionError e = thrown()
Assertions.assertThat(e)
.hasMessageContaining("Expecting actual not to be null")
}
Collection<String> collection() {
List<String> list = new ArrayList<>()
list.add("a")
list.add("b")
list.add("c")
return list
}
Collection<String> collectionWithNulls() {
List<String> list = new ArrayList<>()
list.add(null)
return list
}
Collection nestedCollection() {
List list = new ArrayList<>()
List list1 = new ArrayList<>()
Map<String, String> map1 = new HashMap<>()
map1.put("a", "1")
map1.put("b", "2")
map1.put("c", "3")
List list2 = new ArrayList<>()
Map<String, String> map2 = new HashMap<>()
map2.put("d", "4")
list.add(list1)
list.add(list2)
list1.add(map1)
list2.add(map2)
return list
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.verifier.dsl.wiremock
import java.util.function.Function
import groovy.json.JsonSlurper
import spock.lang.Issue
import spock.lang.Specification
@@ -47,8 +49,8 @@ class WireMockResponseStubStrategySpec extends Specification {
metadata.evaluatedOutputStubContentType >> ContentType.JSON
def subject = new WireMockResponseStubStrategy(contract, metadata) {
@Override
protected Closure parsingClosureForContentType() {
return MapConverter.JSON_PARSING_CLOSURE
Function parsingClosureForContentType() {
return MapConverter.JSON_PARSING_FUNCTION
}
}
def content = subject.buildClientResponseContent()
@@ -80,8 +82,8 @@ class WireMockResponseStubStrategySpec extends Specification {
metadata.evaluatedOutputStubContentType >> ContentType.JSON
def subject = new WireMockResponseStubStrategy(contract, metadata) {
@Override
protected Closure parsingClosureForContentType() {
return MapConverter.JSON_PARSING_CLOSURE
Function parsingClosureForContentType() {
return MapConverter.JSON_PARSING_FUNCTION
}
}
def content = subject.buildClientResponseContent()
@@ -260,8 +262,8 @@ class WireMockResponseStubStrategySpec extends Specification {
}
@Override
Closure parsingClosureForContentType() {
return MapConverter.JSON_PARSING_CLOSURE
Function parsingClosureForContentType() {
return MapConverter.JSON_PARSING_FUNCTION
}
}
response.buildClientResponseContent()

View File

@@ -50,7 +50,7 @@ class ContractFileScannerNewApiSpec extends Specification {
File baseDir = tmpFolder
Set<String> excluded = ["package/**"] as Set
Set<String> ignored = ["other/different/**"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set, null)
when:
MultiValueMap<Path, ContractMetadata> result = scanner.findContractsRecursively()
then:
@@ -68,7 +68,7 @@ class ContractFileScannerNewApiSpec extends Specification {
File baseDir = new File(this.getClass().getResource("/strange_[3.3.3]_directory").toURI())
Set<String> excluded = ["foo/**"] as Set
Set<String> ignored = ["bar/**"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set, null)
when:
MultiValueMap<Path, ContractMetadata> result = scanner.findContractsRecursively()
then:
@@ -82,7 +82,7 @@ class ContractFileScannerNewApiSpec extends Specification {
def "should find contracts group in scenario"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/scenario").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, [] as Set, null)
when:
MultiValueMap<Path, ContractMetadata> contracts = scanner.findContractsRecursively()
then:
@@ -106,7 +106,7 @@ class ContractFileScannerNewApiSpec extends Specification {
def "should find contract files with converters"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/mixed").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null) {
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null, null) {
@Override
protected List<ContractConverter> converters() {
return [new ContractConverter() {
@@ -138,7 +138,7 @@ class ContractFileScannerNewApiSpec extends Specification {
def "should prefer custom yaml converter over standard yaml converter"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/custom/yml").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null) {
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null, null) {
@Override
protected List<ContractConverter> converters() {
return [new ContractConverter() {
@@ -181,7 +181,7 @@ class ContractFileScannerNewApiSpec extends Specification {
and:
File baseDir = tmpFolder
Set<String> included = ["social-service/**", "**/coupon-collected/**/*V1*"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, included)
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, included, null)
when:
MultiValueMap<Path, ContractMetadata> result = scanner.findContractsRecursively()
then:

View File

@@ -50,7 +50,7 @@ class ContractFileScannerSpec extends Specification {
File baseDir = tmpFolder
Set<String> excluded = ["package/**"] as Set
Set<String> ignored = ["other/different/**"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set, "")
when:
ListMultimap<Path, ContractMetadata> result = scanner.findContracts()
then:
@@ -68,7 +68,7 @@ class ContractFileScannerSpec extends Specification {
File baseDir = new File(this.getClass().getResource("/strange_[3.3.3]_directory").toURI())
Set<String> excluded = ["foo/**"] as Set
Set<String> ignored = ["bar/**"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored, [] as Set, "")
when:
ListMultimap<Path, ContractMetadata> result = scanner.findContracts()
then:
@@ -82,7 +82,7 @@ class ContractFileScannerSpec extends Specification {
def "should find contracts group in scenario"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/scenario").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, [] as Set)
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, [] as Set, "")
when:
ListMultimap<Path, ContractMetadata> contracts = scanner.findContracts()
then:
@@ -104,7 +104,7 @@ class ContractFileScannerSpec extends Specification {
def "should find contract files with converters"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/mixed").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null) {
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null, null) {
@Override
protected List<ContractConverter> converters() {
return [new ContractConverter() {
@@ -136,7 +136,7 @@ class ContractFileScannerSpec extends Specification {
def "should prefer custom yaml converter over standard yaml converter"() {
given:
File baseDir = new File(this.getClass().getResource("/directory/with/custom/yml").toURI())
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null) {
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null, null, null) {
@Override
protected List<ContractConverter> converters() {
return [new ContractConverter() {
@@ -179,7 +179,7 @@ class ContractFileScannerSpec extends Specification {
and:
File baseDir = tmpFolder
Set<String> included = ["social-service/**", "**/coupon-collected/**/*V1*"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, included)
ContractFileScanner scanner = new ContractFileScanner(baseDir, [] as Set, [] as Set, included, null)
when:
ListMultimap<Path, ContractMetadata> result = scanner.findContracts()
then: