Added support for writing contracts in Java
fixes gh-1161
This commit is contained in:
@@ -123,6 +123,9 @@ class TestGenerator {
|
||||
void generateTestClasses(final String basePackageName) {
|
||||
ListMultimap<Path, ContractMetadata> contracts = contractFileScanner.
|
||||
findContracts()
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found the following contracts " + contracts.keySet())
|
||||
}
|
||||
Set<Map.Entry<Path,Collection<ContractMetadata>>> inProgress = contracts.asMap().entrySet()
|
||||
.findAll { Map.Entry<Path, Collection<ContractMetadata>> entry -> entry.value.any { it.anyInProgress() }}
|
||||
if (!inProgress.isEmpty() && configProperties.failOnInProgress) {
|
||||
|
||||
@@ -41,6 +41,7 @@ class ToYamlConverter {
|
||||
}
|
||||
|
||||
private static YamlContractConverter yamlContractConverter = new YamlContractConverter()
|
||||
|
||||
private static final List<ContractConverter> CONTRACT_CONVERTERS = converters()
|
||||
|
||||
protected static void doReplaceContractWithYaml(ContractConverter converter, File file) {
|
||||
@@ -105,27 +106,7 @@ class ToYamlConverter {
|
||||
List<ContractConverter> converters =
|
||||
SpringFactoriesLoader.loadFactories(ContractConverter, null)
|
||||
converters.add(YamlContractConverter.INSTANCE)
|
||||
converters.add(GroovyContractConverter.INSTANCE)
|
||||
converters.add(ContractVerifierDslConverter.INSTANCE)
|
||||
return converters
|
||||
}
|
||||
}
|
||||
|
||||
class GroovyContractConverter implements ContractConverter<Collection<Contract>> {
|
||||
|
||||
static final GroovyContractConverter INSTANCE = new GroovyContractConverter()
|
||||
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
return file.name.endsWith(".groovy")
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
return ContractVerifierDslConverter.convertAsCollection(file)
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertTo(Collection<Contract> contract) {
|
||||
return contract
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ class ContractFileScanner {
|
||||
|
||||
protected List<ContractConverter> convertersWithYml() {
|
||||
List<ContractConverter> converters = converters()
|
||||
converters.add(ContractVerifierDslConverter.INSTANCE)
|
||||
converters.add(YamlContractConverter.INSTANCE)
|
||||
return converters
|
||||
}
|
||||
@@ -213,22 +214,7 @@ class ContractFileScanner {
|
||||
}
|
||||
|
||||
private boolean isContractFile(File file) {
|
||||
return file.isFile() && getFilenameExtension(file.toString())?.equalsIgnoreCase("groovy")
|
||||
}
|
||||
|
||||
private static String getFilenameExtension(String path) {
|
||||
if (path == null) {
|
||||
return null
|
||||
}
|
||||
int extIndex = path.lastIndexOf('.')
|
||||
if (extIndex == -1) {
|
||||
return null
|
||||
}
|
||||
int folderIndex = path.lastIndexOf('/')
|
||||
if (folderIndex > extIndex) {
|
||||
return null
|
||||
}
|
||||
return path.substring(extIndex + 1)
|
||||
return file.isFile() && ContractVerifierDslConverter.INSTANCE.isAccepted(file)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,7 +85,7 @@ final class ContractScanner {
|
||||
contractDescriptors
|
||||
.addAll(ContractVerifierDslConverter
|
||||
.convertAsCollection(
|
||||
file.getParentFile(), file))
|
||||
file.getParentFile(), file).findAll { it })
|
||||
}
|
||||
else if (converter != null
|
||||
&& converter.isAccepted(file)) {
|
||||
@@ -120,6 +120,6 @@ final class ContractScanner {
|
||||
}
|
||||
|
||||
private static boolean isContractDescriptor(File file) {
|
||||
return file.isFile() && file.getName().endsWith(".groovy")
|
||||
return ContractVerifierDslConverter.INSTANCE.isAccepted(file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,23 @@
|
||||
|
||||
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.codehaus.groovy.control.customizers.ASTTransformationCustomizer
|
||||
|
||||
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 file or String into a {@link Contract}
|
||||
* Converts a String or a Groovy or Java file into a {@link Contract}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*
|
||||
@@ -33,7 +40,15 @@ import org.springframework.util.StringUtils
|
||||
*/
|
||||
@CompileStatic
|
||||
@Commons
|
||||
class ContractVerifierDslConverter {
|
||||
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)}
|
||||
@@ -80,7 +95,7 @@ class ContractVerifierDslConverter {
|
||||
ClassLoader classLoader = ContractVerifierDslConverter.getClassLoader()
|
||||
try {
|
||||
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader)
|
||||
Object object = groovyShell(urlCl, rootFolder).evaluate(dsl)
|
||||
Object object = toObject(urlCl, rootFolder, dsl)
|
||||
return listOfContracts(dsl, object)
|
||||
}
|
||||
catch (DslParseException e) {
|
||||
@@ -106,6 +121,65 @@ class ContractVerifierDslConverter {
|
||||
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(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) {
|
||||
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}
|
||||
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',
|
||||
@@ -123,7 +197,10 @@ class ContractVerifierDslConverter {
|
||||
}
|
||||
|
||||
private static Collection<Contract> listOfContracts(File file, Object object) {
|
||||
if (object instanceof Collection) {
|
||||
if (object == null) {
|
||||
return Collections.emptyList()
|
||||
}
|
||||
else if (isACollectionOfContracts(object)) {
|
||||
return withName(file, object as Collection<Contract>)
|
||||
}
|
||||
else if (!object instanceof Contract) {
|
||||
@@ -132,6 +209,10 @@ class ContractVerifierDslConverter {
|
||||
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 {
|
||||
@@ -146,4 +227,19 @@ class ContractVerifierDslConverter {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
@@ -105,4 +109,95 @@ public final class ContractVerifierUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a builder for map
|
||||
*/
|
||||
public static ContractVerifierMap map() {
|
||||
return new ContractVerifierMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* A map with a fluent interface.
|
||||
*/
|
||||
public static class ContractVerifierMap implements Map<Object, Object> {
|
||||
|
||||
private final Map<Object, Object> delegate = new HashMap<>();
|
||||
|
||||
public ContractVerifierMap entry(String key, Object value) {
|
||||
put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return this.delegate.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.delegate.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return this.delegate.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
return this.delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return this.delegate.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Object, ?> m) {
|
||||
this.delegate.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> keySet() {
|
||||
return this.delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return this.delegate.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Object, Object>> entrySet() {
|
||||
return this.delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this.delegate.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.contract.verifier.builder
|
||||
|
||||
import org.junit.Rule
|
||||
import org.mdkt.compiler.CompilationException
|
||||
import spock.lang.Issue
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
@@ -237,10 +238,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
try {
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
|
||||
}
|
||||
catch (ClassFormatError classFormatError) {
|
||||
String output = outputCapture.toString()
|
||||
assert output.contains('error: cannot find symbol')
|
||||
assert output.contains('assertThatValueIsANumber(parsedJson.read("$.duck"));')
|
||||
catch (CompilationException classFormatError) {
|
||||
String output = classFormatError.message
|
||||
assert output.contains('cannot find symbol')
|
||||
assert output.contains('assertThatValueIsANumber')
|
||||
}
|
||||
where:
|
||||
methodBuilderName | methodBuilder | rootElement
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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 groovy.json.JsonSlurper
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.FromFileProperty
|
||||
import org.springframework.cloud.contract.spec.internal.RegexProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Url
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.COMMAND
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.NULL
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.REGEX
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class JavaContractConverterSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
URL javaRest = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest.java")
|
||||
@Shared
|
||||
File javaRestFile = new File(javaRest.toURI())
|
||||
|
||||
@Shared
|
||||
URL javaRestWithTags = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_with_tags.java")
|
||||
@Shared
|
||||
File javaRestWithTagsFile = new File(javaRestWithTags.toURI())
|
||||
|
||||
@Shared
|
||||
URL contractBody = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_from_file.java")
|
||||
@Shared
|
||||
File contractBodyFile = new File(contractBody.toURI())
|
||||
|
||||
@Shared
|
||||
URL contractBodyBytes = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_from_pdf.java")
|
||||
@Shared
|
||||
File contractBodyBytesFile = new File(contractBodyBytes.toURI())
|
||||
|
||||
@Shared
|
||||
URL docs = JavaContractConverterSpec.getResource("/contractsToCompile/contract_docs_examples.java")
|
||||
@Shared
|
||||
File docsFile = new File(docs.toURI())
|
||||
|
||||
def "should convert Java DSL with REST to DSL for [#contractFile]"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractFile)
|
||||
then:
|
||||
Contract contract = contracts.first()
|
||||
contract.description == "Some description"
|
||||
contract.name == "some name"
|
||||
contract.priority == 8
|
||||
contract.ignored == true
|
||||
Url url = contract.request.url
|
||||
url.clientValue == "/foo"
|
||||
url.queryParameters.parameters[0].name == "a"
|
||||
url.queryParameters.parameters[0].serverValue == "b"
|
||||
url.queryParameters.parameters[1].name == "b"
|
||||
url.queryParameters.parameters[1].serverValue == "c"
|
||||
contract.request.method.clientValue == "PUT"
|
||||
contract.request.headers.entries.find {
|
||||
it.name == "foo" &&
|
||||
((RegexProperty) it.clientValue).pattern() == "bar" && it.serverValue == "bar"
|
||||
}
|
||||
contract.request.headers.entries.find {
|
||||
it.name == "fooReq" &&
|
||||
it.serverValue == "baz"
|
||||
}
|
||||
MapConverter.getStubSideValues(contract.request.body) == [foo: "bar"]
|
||||
contract.request.bodyMatchers.matchers[0].path() == '$.foo'
|
||||
contract.request.bodyMatchers.matchers[0].matchingType() == REGEX
|
||||
contract.request.bodyMatchers.matchers[0].value().pattern() == 'bar'
|
||||
and:
|
||||
contract.response.status.clientValue == 200
|
||||
contract.response.delay.clientValue == 1000
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "foo2" &&
|
||||
((RegexProperty) it.serverValue).pattern() == "bar" && it.clientValue == "bar"
|
||||
}
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "foo3" &&
|
||||
((ExecutionProperty) it.serverValue).insertValue('foo') == "andMeToo(foo)"
|
||||
}
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "fooRes" &&
|
||||
it.clientValue == "baz"
|
||||
}
|
||||
MapConverter.getStubSideValues(contract.response.body) == [foo2: "bar", foo3: "baz", nullValue: null]
|
||||
contract.response.bodyMatchers.matchers[0].path() == '$.foo2'
|
||||
contract.response.bodyMatchers.matchers[0].matchingType() == REGEX
|
||||
contract.response.bodyMatchers.matchers[0].value().pattern() == 'bar'
|
||||
contract.response.bodyMatchers.matchers[1].path() == '$.foo3'
|
||||
contract.response.bodyMatchers.matchers[1].matchingType() == COMMAND
|
||||
contract.response.bodyMatchers.matchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
contract.response.bodyMatchers.matchers[2].path() == '$.nullValue'
|
||||
contract.response.bodyMatchers.matchers[2].matchingType() == NULL
|
||||
contract.response.bodyMatchers.matchers[2].value() == null
|
||||
where:
|
||||
contractFile << [javaRestFile, javaRestWithTagsFile]
|
||||
}
|
||||
|
||||
def "should convert java with REST with body from file"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractBodyFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
Contract contract = contracts.first()
|
||||
new JsonSlurper().parseText(contract.request.body.clientValue.toString()) ==
|
||||
new JsonSlurper().parseText('''{ "hello" : "request" }''')
|
||||
and:
|
||||
new JsonSlurper().parseText(contract.response.body.clientValue.toString()) ==
|
||||
new JsonSlurper().parseText('''{ "hello" : "response" }''')
|
||||
}
|
||||
|
||||
def "should convert java with REST with body as bytes"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractBodyBytesFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
Contract contract = contracts.first()
|
||||
contract.request.body.clientValue instanceof FromFileProperty
|
||||
and:
|
||||
contract.response.body.clientValue instanceof FromFileProperty
|
||||
}
|
||||
|
||||
def "should convert java with REST for docs"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), docsFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,10 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
|
||||
URL single = ContractVerifierDslConverterSpec.getResource("/contract.groovy")
|
||||
File singleContract = new File(single.toURI())
|
||||
URL singleJava = ContractVerifierDslConverterSpec.getResource("/contractsToCompile/contract.java")
|
||||
File singleContractJava = new File(singleJava.toURI())
|
||||
URL singleRestJava = ContractVerifierDslConverterSpec.getResource("/contractsToCompile/contract_rest.java")
|
||||
File singleContractRestJava = new File(singleRestJava.toURI())
|
||||
URL multiple = ContractVerifierDslConverterSpec.getResource("/multiple_contracts.groovy")
|
||||
File multipleContracts = new File(multiple.toURI())
|
||||
URL invalid = ContractVerifierDslConverterSpec.getResource("/contract.yml")
|
||||
@@ -51,6 +55,25 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
}
|
||||
}
|
||||
|
||||
Contract expectedSingleContractForJava = Contract.make {
|
||||
name("contract")
|
||||
request {
|
||||
method('PUT')
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(""" { "status" : "OK" } """)
|
||||
url("/1")
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body(""" { "status" : "OK" } """)
|
||||
headers {
|
||||
contentType(textPlain())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Contract expectedSingleContractForText = Contract.make {
|
||||
request {
|
||||
method('PUT')
|
||||
@@ -154,6 +177,20 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
contract == [expectedSingleContract]
|
||||
}
|
||||
|
||||
def "should convert file to a list of Contracts when there's only one declared java contract"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContractJava)
|
||||
then:
|
||||
contract == [expectedSingleContract]
|
||||
}
|
||||
|
||||
def "should convert file to a list of Contracts for a REST contract for docs"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContractRestJava)
|
||||
then:
|
||||
contract == [expectedSingleContractForJava]
|
||||
}
|
||||
|
||||
def "should convert text to a list of Contracts when there's only one declared contract"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContract.text)
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.cloud.contract.verifier.messaging.internal.ContractVe
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper
|
||||
import org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil
|
||||
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler
|
||||
import org.springframework.util.ReflectionUtils
|
||||
/**
|
||||
* checking the syntax of produced scripts
|
||||
@@ -53,7 +54,7 @@ import org.springframework.util.ReflectionUtils
|
||||
@Commons
|
||||
class SyntaxChecker {
|
||||
|
||||
Entity entity
|
||||
public static final RuntimeJavaCompiler COMPILER = new RuntimeJavaCompiler()
|
||||
|
||||
private static final String[] DEFAULT_IMPORTS = [
|
||||
Contract.name,
|
||||
@@ -179,20 +180,26 @@ private void test(String test) {
|
||||
String fqnClassName = "com.example.${className}"
|
||||
test = test.replaceAll("class FooTest", "class " + className)
|
||||
.replaceAll("import javax.ws.rs.core.Response", "import javax.ws.rs.core.Response; import javax.ws.rs.client.WebTarget;")
|
||||
return InMemoryJavaCompiler.compile(fqnClassName, test)
|
||||
return compileJava(fqnClassName, test)
|
||||
|
||||
}
|
||||
|
||||
private static Class<?> compileJava(String fqnClassName, String test) {
|
||||
return InMemoryJavaCompiler.newInstance()
|
||||
.ignoreWarnings()
|
||||
.compile(fqnClassName, test)
|
||||
}
|
||||
|
||||
private static String className(String test) {
|
||||
Random random = new Random()
|
||||
int first = Math.abs(random.nextInt())
|
||||
int hashCode = Math.abs(test.hashCode())
|
||||
StringBuffer sourceCode = new StringBuffer()
|
||||
String className = "TestClass_${first}_${hashCode}"
|
||||
return className
|
||||
}
|
||||
|
||||
static boolean tryToCompileJavaWithoutImports(String fqn, String test) {
|
||||
InMemoryJavaCompiler.compile(fqn, test)
|
||||
compileJava(fqn, test)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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 example;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
r.body(" { \"status\" : \"OK\" } ");
|
||||
r.url("/1");
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(" { \"status\" : \"OK\" } ");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.textPlain());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
//tag::class[]
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_docs_examples implements Supplier<Collection<Contract>> {
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract httpDsl =
|
||||
// tag::http_dsl[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
// Definition of HTTP request part of the contract
|
||||
// (this can be a valid request or invalid depending
|
||||
// on type of contract being specified).
|
||||
c.request(r -> {
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
// ...
|
||||
});
|
||||
|
||||
// Definition of HTTP response part of the contract
|
||||
// (a service implementing this contract should respond
|
||||
// with following response after receiving request
|
||||
// specified in "request" part above).
|
||||
c.response(r -> {
|
||||
r.status(200);
|
||||
// ...
|
||||
});
|
||||
|
||||
// Contract priority, which can be used for overriding
|
||||
// contracts (1 is highest). Priority is optional.
|
||||
c.priority(1);
|
||||
});
|
||||
|
||||
// end::http_dsl[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract methodDsl = org.springframework.cloud.contract.spec.Contract
|
||||
.make(c -> {
|
||||
c.request(r -> {
|
||||
// tag::method[]
|
||||
r.method(r.GET());
|
||||
// end::method[]
|
||||
r.url("/foo");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
r.status(200);
|
||||
});
|
||||
|
||||
c.priority(1);
|
||||
});
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract request =
|
||||
// tag::request[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// HTTP request method (GET/POST/PUT/DELETE).
|
||||
r.method("GET");
|
||||
|
||||
// Path component of request URL is specified as follows.
|
||||
r.urlPath("/users");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::request[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract url =
|
||||
// tag::url[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("GET");
|
||||
|
||||
// Specifying `url` and `urlPath` in one contract is illegal.
|
||||
r.url("http://localhost:8888/users");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::url[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract urlPaths =
|
||||
// tag::urlpath[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
|
||||
r.urlPath("/users", u -> {
|
||||
|
||||
// Each parameter is specified in form
|
||||
// `'paramName' : paramValue` where parameter value
|
||||
// may be a simple literal or one of matcher functions,
|
||||
// all of which are used in this example.
|
||||
u.queryParameters(q -> {
|
||||
|
||||
// If a simple literal is used as value
|
||||
// default matcher function is used (equalTo)
|
||||
q.parameter("limit", 100);
|
||||
|
||||
// `equalTo` function simply compares passed value
|
||||
// using identity operator (==).
|
||||
q.parameter("filter", r.equalTo("email"));
|
||||
|
||||
// `containing` function matches strings
|
||||
// that contains passed substring.
|
||||
q.parameter("gender", r.value(
|
||||
r.consumer(r.containing("[mf]")), r.producer("mf")));
|
||||
|
||||
// `matching` function tests parameter
|
||||
// against passed regular expression.
|
||||
q.parameter("offset", r.value(
|
||||
r.consumer(r.matching("[0-9]+")), r.producer(123)));
|
||||
|
||||
// `notMatching` functions tests if parameter
|
||||
// does not match passed regular expression.
|
||||
q.parameter("loginStartsWith", r.value(
|
||||
r.consumer(r.notMatching(".{0,2}")), r.producer(3)));
|
||||
});
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::urlpath[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract headers =
|
||||
// tag::headers[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Each header is added in form `'Header-Name' : 'Header-Value'`.
|
||||
// there are also some helper methods
|
||||
r.headers(h -> {
|
||||
h.header("key", "value");
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::headers[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract cookies =
|
||||
// tag::cookies[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Each Cookies is added in form `'Cookie-Key' : 'Cookie-Value'`.
|
||||
// there are also some helper methods
|
||||
r.cookies(ck -> {
|
||||
ck.cookie("key", "value");
|
||||
ck.cookie("another_key", "another_value");
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::cookies[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract body =
|
||||
// tag::body[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Currently only JSON format of request body is supported.
|
||||
// Format will be determined from a header or body's content.
|
||||
r.body("{ \"login\" : \"john\", \"name\": \"John The Contract\" }");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::body[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract bodyAsXml =
|
||||
// tag::bodyAsXml[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// In this case body will be formatted as XML.
|
||||
r.body(r.equalToXml(
|
||||
"<user><login>john</login><name>John The Contract</name></user>"));
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::bodyAsXml[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract response =
|
||||
// tag::response[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
});
|
||||
c.response(r -> {
|
||||
// Status code sent by the server
|
||||
// in response to request specified above.
|
||||
r.status(r.OK());
|
||||
});
|
||||
});
|
||||
|
||||
// end::response[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract regex =
|
||||
// tag::regex[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("GET");
|
||||
r.url(r.$(r.consumer(r.regex("\\/[0-9]{2}")), r.producer("/12")));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(ContractVerifierUtil.map().entry("id", r.$(r.anyNumber()))
|
||||
.entry("surname", r.$(r.consumer("Kowalsky"),
|
||||
r.producer(r.regex("[a-zA-Z]+")))));
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "text/plain");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// end::regex[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract optionals =
|
||||
// tag::optionals[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.priority(1);
|
||||
c.name("optionals");
|
||||
c.request(r -> {
|
||||
r.method("POST");
|
||||
r.url("/users/password");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
r.body(ContractVerifierUtil.map()
|
||||
.entry("email",
|
||||
r.$(r.consumer(r.optional(r.regex(r.email()))),
|
||||
r.producer("abc@abc.com")))
|
||||
.entry("callback_url", r.$(r.consumer(r.regex(r.hostname())),
|
||||
r.producer("https://partners.com"))));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(404);
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "application/json");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("code", r.value(
|
||||
r.consumer("123123"), r.producer(r.optional("123123")))));
|
||||
});
|
||||
});
|
||||
|
||||
// end::optionals[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract method =
|
||||
// tag::methodBuilder[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("PUT");
|
||||
r.url(r.$(r.consumer(r.regex("^/api/[0-9]{2}$")),
|
||||
r.producer("/api/12")));
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "application/json");
|
||||
});
|
||||
r.body("[{\"text\": \"Gonna see you at Warsaw\" }]");
|
||||
});
|
||||
c.response(r -> {
|
||||
r.body(ContractVerifierUtil.map()
|
||||
.entry("path",
|
||||
r.$(r.consumer("/api/12"),
|
||||
r.producer(r.regex("^/api/[0-9]{2}$"))))
|
||||
.entry("correlationId", r.$(r.consumer("1223456"), r
|
||||
.producer(r.execute("isProperCorrelationId($it)")))));
|
||||
r.status(r.OK());
|
||||
});
|
||||
});
|
||||
|
||||
// end::methodBuilder[]
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(method);
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
//tag::class[]
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty;
|
||||
import org.springframework.cloud.contract.spec.internal.Request;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_multipart implements Supplier<Collection<Contract>> {
|
||||
|
||||
private static Map<String, DslProperty> namedProps(Request r) {
|
||||
Map<String, DslProperty> map = new HashMap<>();
|
||||
// name of the file
|
||||
map.put("name", r.$(r.c(r.regex(r.nonEmpty())), r.p("filename.csv")));
|
||||
// content of the file
|
||||
map.put("content", r.$(r.c(r.regex(r.nonEmpty())), r.p("file content")));
|
||||
// content type for the part
|
||||
map.put("contentType", r.$(r.c(r.regex(r.nonEmpty())), r.p("application/json")));
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("PUT");
|
||||
r.url("/multipart");
|
||||
r.headers(h -> {
|
||||
h.contentType("multipart/form-data;boundary=AaB03x");
|
||||
});
|
||||
r.multipart(ContractVerifierUtil.map()
|
||||
// key (parameter name), value (parameter value) pair
|
||||
.entry("formParameter",
|
||||
r.$(r.c(r.regex("\".+\"")),
|
||||
r.p("\"formParameterValue\"")))
|
||||
.entry("someBooleanParameter",
|
||||
r.$(r.c(r.regex(r.anyBoolean())), r.p("true")))
|
||||
// a named parameter (e.g. with `file` name) that represents file
|
||||
// with
|
||||
// `name` and `content`. You can also call `named("fileName",
|
||||
// "fileContent")`
|
||||
.entry("file", r.named(namedProps(r))));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_rest implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
c.name("some name");
|
||||
c.priority(8);
|
||||
c.ignored();
|
||||
c.request(r -> {
|
||||
r.url("/foo", u -> {
|
||||
u.queryParameters(q -> {
|
||||
q.parameter("a", "b");
|
||||
q.parameter("b", "c");
|
||||
});
|
||||
});
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.header("foo", r.value(r.client(r.regex("bar")), r.server("bar")));
|
||||
h.header("fooReq", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo", "bar"));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo", m.byRegex("bar"));
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.fixedDelayMilliseconds(1000);
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.header("foo2", r.value(r.server(r.regex("bar")), r.client("bar")));
|
||||
h.header("foo3", r.value(r.server(r.execute("andMeToo($it)")),
|
||||
r.client("foo33")));
|
||||
h.header("fooRes", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo2", "bar")
|
||||
.entry("foo3", "baz").entry("nullValue", null));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo2", m.byRegex("bar"));
|
||||
m.jsonPath("$.foo3", m.byCommand("executeMe($it)"));
|
||||
m.jsonPath("$.nullValue", m.byNull());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_rest_from_file implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.url("/foo");
|
||||
r.method(r.GET());
|
||||
r.body(r.file("request.json"));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(r.file("response.json"));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
//tag::class[]
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_rest_from_pdf implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.url("/1");
|
||||
r.method(r.PUT());
|
||||
r.body(r.fileAsBytes("request.pdf"));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationOctetStream());
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(r.fileAsBytes("response.pdf"));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationOctetStream());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_rest_with_tags implements Supplier<Collection<Contract>> {
|
||||
|
||||
static Object description = Collections.singletonList(
|
||||
// tag::description[]
|
||||
Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
}));
|
||||
|
||||
// end::description[]
|
||||
|
||||
static Object name = Collections.singletonList(
|
||||
// tag::name[]
|
||||
Contract.make(c -> {
|
||||
c.name("some name");
|
||||
}));
|
||||
|
||||
// end::name[]
|
||||
|
||||
static Object ignored = Collections.singletonList(
|
||||
// tag::ignored[]
|
||||
Contract.make(c -> {
|
||||
c.ignored();
|
||||
}));
|
||||
|
||||
// end::ignored[]
|
||||
|
||||
static Object in_progress = Collections.singletonList(
|
||||
// tag::in_progress[]
|
||||
Contract.make(c -> {
|
||||
c.inProgress();
|
||||
}));
|
||||
|
||||
// end::in_progress[]
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
c.name("some name");
|
||||
c.priority(8);
|
||||
c.ignored();
|
||||
c.inProgress();
|
||||
c.request(r -> {
|
||||
r.url("/foo", u -> {
|
||||
u.queryParameters(q -> {
|
||||
q.parameter("a", "b");
|
||||
q.parameter("b", "c");
|
||||
});
|
||||
});
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.header("foo", r.value(r.client(r.regex("bar")), r.server("bar")));
|
||||
h.header("fooReq", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo", "bar"));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo", m.byRegex("bar"));
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.fixedDelayMilliseconds(1000);
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.header("foo2", r.value(r.server(r.regex("bar")), r.client("bar")));
|
||||
h.header("foo3", r.value(r.server(r.execute("andMeToo($it)")),
|
||||
r.client("foo33")));
|
||||
h.header("fooRes", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo2", "bar")
|
||||
.entry("foo3", "baz").entry("nullValue", null));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo2", m.byRegex("bar"));
|
||||
m.jsonPath("$.foo3", m.byCommand("executeMe($it)"));
|
||||
m.jsonPath("$.nullValue", m.byNull());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
/*
|
||||
* Copyright 2013-2019 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.
|
||||
*/
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_xml implements Supplier<Contract> {
|
||||
|
||||
@Override
|
||||
public Contract get() {
|
||||
return Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method(r.GET());
|
||||
r.urlPath("/get");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationXml());
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationXml());
|
||||
});
|
||||
r.body("<test>\n" + "<duck type='xtype'>123</duck>\n"
|
||||
+ "<alpha>abc</alpha>\n" + "<list>\n" + "<elem>abc</elem>\n"
|
||||
+ "<elem>def</elem>\n" + "<elem>ghi</elem>\n" + "</list>\n"
|
||||
+ "<number>123</number>\n" + "<aBoolean>true</aBoolean>\n"
|
||||
+ "<date>2017-01-01</date>\n"
|
||||
+ "<dateTime>2017-01-01T01:23:45</dateTime>\n"
|
||||
+ "<time>01:02:34</time>\n"
|
||||
+ "<valueWithoutAMatcher>foo</valueWithoutAMatcher>\n"
|
||||
+ "<key><complex>foo</complex></key>\n" + "</test>");
|
||||
r.bodyMatchers(m -> {
|
||||
m.xPath("/test/duck/text()", m.byRegex("[0-9]{3}"));
|
||||
m.xPath("/test/duck/text()", m.byCommand("equals($it)"));
|
||||
m.xPath("/test/duck/xxx", m.byNull());
|
||||
m.xPath("/test/duck/text()", m.byEquality());
|
||||
m.xPath("/test/alpha/text()", m.byRegex(r.onlyAlphaUnicode()));
|
||||
m.xPath("/test/alpha/text()", m.byEquality());
|
||||
m.xPath("/test/number/text()", m.byRegex(r.number()));
|
||||
m.xPath("/test/date/text()", m.byDate());
|
||||
m.xPath("/test/dateTime/text()", m.byTimestamp());
|
||||
m.xPath("/test/time/text()", m.byTime());
|
||||
m.xPath("/test/*/complex/text()", m.byEquality());
|
||||
m.xPath("/test/duck/@type", m.byEquality());
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "request"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "response"
|
||||
}
|
||||
Binary file not shown.
@@ -9,6 +9,8 @@
|
||||
|
||||
<logger name="com.github.jknack.handlebars.internal" level="INFO"/>
|
||||
|
||||
<logger name="org.springframework.cloud.function" level="INFO"/>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
Reference in New Issue
Block a user