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:
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<T,T>, whilst this function return type is
|
||||
* Function<T,R>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user