Merge remote-tracking branch 'origin/2.2.x'
This commit is contained in:
@@ -43,7 +43,7 @@ final class MappingGenerator {
|
||||
|
||||
static Collection<Path> toMappings(File contractFile, Collection<Contract> contracts, File mappingsFolder) {
|
||||
StubGeneratorProvider provider = new StubGeneratorProvider();
|
||||
Collection<StubGenerator> stubGenerators = provider.converterForName(contractFile.getName());
|
||||
Collection<StubGenerator> stubGenerators = provider.converterForName(contractFile);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found following matching stub generators " + stubGenerators);
|
||||
}
|
||||
|
||||
@@ -26,16 +26,19 @@ import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStub;
|
||||
import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider;
|
||||
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
|
||||
/**
|
||||
* Factory of StubRunners. Basing on the options and passed collaborators downloads the
|
||||
@@ -109,6 +112,12 @@ class StubRunnerFactory {
|
||||
}
|
||||
|
||||
private void removeCurrentMappings(Path path) {
|
||||
|
||||
List<HttpServerStub> httpServerStubs = SpringFactoriesLoader.loadFactories(HttpServerStub.class, null);
|
||||
if (httpServerStubs.isEmpty()) {
|
||||
httpServerStubs.add(new WireMockHttpServerStub());
|
||||
}
|
||||
|
||||
try {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@@ -116,13 +125,15 @@ class StubRunnerFactory {
|
||||
|
||||
private final StubGeneratorProvider provider = new StubGeneratorProvider();
|
||||
|
||||
private final HttpServerStub wireMockHttpServerStub = new WireMockHttpServerStub();
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
Collection<StubGenerator> stubGenerators = this.provider.converterForName(file.toString());
|
||||
if (!stubGenerators.isEmpty()) {
|
||||
File fileToConvert = file.toFile();
|
||||
Collection<StubGenerator> stubGenerators = this.provider.converterForName(fileToConvert);
|
||||
if (!stubGenerators.isEmpty() || this.wireMockHttpServerStub.isAccepted(fileToConvert)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Deleting file [" + file.toString()
|
||||
+ "] since at least one stub generator would run it");
|
||||
log.debug("Deleting file [" + file.toString() + "] since it contains a valid mapping.");
|
||||
}
|
||||
try {
|
||||
Files.delete(file);
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import com.github.tomakehurst.wiremock.common.JsonException;
|
||||
import com.github.tomakehurst.wiremock.common.Slf4jNotifier;
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||
import com.github.tomakehurst.wiremock.extension.Extension;
|
||||
@@ -232,14 +233,24 @@ public class WireMockHttpServerStub implements HttpServerStub {
|
||||
|
||||
@Override
|
||||
public boolean isAccepted(File file) {
|
||||
return file.getName().endsWith(".json");
|
||||
return file.getName().endsWith(".json") && validMapping(file);
|
||||
}
|
||||
|
||||
private boolean validMapping(File file) {
|
||||
try {
|
||||
getMapping(file);
|
||||
return true;
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
StubMapping getMapping(File file) {
|
||||
try (InputStream stream = Files.newInputStream(file.toPath())) {
|
||||
return StubMapping.buildFrom(StreamUtils.copyToString(stream, Charset.forName("UTF-8")));
|
||||
}
|
||||
catch (IOException e) {
|
||||
catch (IOException | JsonException e) {
|
||||
throw new IllegalStateException("Cannot read file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,22 +57,48 @@ class StubRunnerSpec extends Specification {
|
||||
runner.close()
|
||||
}
|
||||
|
||||
|
||||
def 'should generate stubs at runtime'() {
|
||||
given:
|
||||
Arguments args = argumentsWithGenerateStubs()
|
||||
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions);
|
||||
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions)
|
||||
StubRunner runner = new StubRunnerFactory(args.stubRunnerOptions,
|
||||
downloader, new NoOpStubMessages()).createStubsFromServiceConfiguration().first()
|
||||
when:
|
||||
runner.runStubs()
|
||||
then:
|
||||
URL url = runner.findStubUrl("groupId2", "artifactId2")
|
||||
new URL(url.toString() + "/goodbye").text
|
||||
"Goodbye World!" == new URL(url.toString() + "/goodbye").text
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
def 'should override existing mappings when generating stubs at runtime'() {
|
||||
given:
|
||||
Arguments args = argumentsWithGenerateStubs()
|
||||
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions)
|
||||
StubRunner runner = new StubRunnerFactory(args.stubRunnerOptions,
|
||||
downloader, new NoOpStubMessages()).createStubsFromServiceConfiguration().first()
|
||||
when:
|
||||
new URL(url.toString() + "/hello").text
|
||||
runner.runStubs()
|
||||
then:
|
||||
thrown(FileNotFoundException)
|
||||
URL url = runner.findStubUrl("groupId2", "artifactId2")
|
||||
// don't return the response defined in hello.json, but the one defined in the contract
|
||||
"Hello New World!" == new URL(url.toString() + "/hello").text
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
def 'should handle contracts with body contents loaded from external file when generating stubs at runtime'() {
|
||||
given:
|
||||
Arguments args = argumentsWithGenerateStubs()
|
||||
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions)
|
||||
StubRunner runner = new StubRunnerFactory(args.stubRunnerOptions,
|
||||
downloader, new NoOpStubMessages()).createStubsFromServiceConfiguration().first()
|
||||
when:
|
||||
runner.runStubs()
|
||||
then:
|
||||
URL url = runner.findStubUrl("groupId2", "artifactId2")
|
||||
"Goodbye from file!" == new URL(url.toString() + "/goodbye_from_file").text
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import com.github.tomakehurst.wiremock.extension.Extension
|
||||
import com.github.tomakehurst.wiremock.extension.Parameters
|
||||
import com.github.tomakehurst.wiremock.extension.ResponseTransformer
|
||||
import com.github.tomakehurst.wiremock.http.ChunkedDribbleDelay
|
||||
import com.github.tomakehurst.wiremock.http.HttpHeader
|
||||
import com.github.tomakehurst.wiremock.http.Request
|
||||
import com.github.tomakehurst.wiremock.http.Response
|
||||
import wiremock.org.apache.http.HttpHeaders
|
||||
|
||||
import org.springframework.cloud.contract.verifier.dsl.wiremock.DefaultResponseTransformer
|
||||
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions
|
||||
@@ -52,13 +54,13 @@ class CustomExtension extends ResponseTransformer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformer returns the "surprise!" body regardless of what you
|
||||
* the stub mapping returns
|
||||
* Transformer adds a X-My-Header with value "surprise!" to the response
|
||||
*/
|
||||
@Override
|
||||
Response transform(Request request, Response response, FileSource files, Parameters parameters) {
|
||||
def headers = response.headers + new HttpHeader("X-My-Header", "surprise!")
|
||||
return new Response(response.status, response.statusMessage,
|
||||
"surprise!", response.headers, response.wasConfigured(), response.fault,
|
||||
response.body, headers, response.wasConfigured(), response.fault,
|
||||
response.initialDelay, new ChunkedDribbleDelay(0, 0), response.fromProxy)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,16 @@ import spock.lang.Specification
|
||||
|
||||
import org.springframework.boot.test.system.OutputCaptureRule
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
class WireMockHttpServerStubSpec extends Specification {
|
||||
public static
|
||||
final File MAPPING_DESCRIPTOR = new File('src/test/resources/transformers.json')
|
||||
public static final File ARBITRARY_JSON = new File('src/test/resources/sample_response.json')
|
||||
public static final File PDF = new File('src/test/resources/request.pdf')
|
||||
public static final File BROKEN_MAPPING = new File('src/test/resources/broken.json')
|
||||
|
||||
@Rule
|
||||
OutputCaptureRule capture = new OutputCaptureRule()
|
||||
@@ -50,7 +55,9 @@ class WireMockHttpServerStubSpec extends Specification {
|
||||
then:
|
||||
noExceptionThrown()
|
||||
expect:
|
||||
"surprise!" == new RestTemplate().getForObject("http://localhost:" + mappingDescriptor.port() + "/ping", String.class)
|
||||
URI uri = new URI("http://localhost:" + mappingDescriptor.port() + "/ping")
|
||||
"surprise!" == new RestTemplate().exchange(uri, HttpMethod.GET, (HttpEntity)null, String.class)
|
||||
.getHeaders().getFirst("X-My-Header")
|
||||
cleanup:
|
||||
mappingDescriptor?.stop()
|
||||
}
|
||||
@@ -72,4 +79,40 @@ class WireMockHttpServerStubSpec extends Specification {
|
||||
cleanup:
|
||||
mappingDescriptor?.stop()
|
||||
}
|
||||
|
||||
def 'should accept a valid mapping'() {
|
||||
given:
|
||||
WireMockHttpServerStub httpServerStub = new WireMockHttpServerStub()
|
||||
when:
|
||||
boolean accepted = httpServerStub.isAccepted(MAPPING_DESCRIPTOR)
|
||||
then:
|
||||
accepted
|
||||
}
|
||||
|
||||
def 'should not accept an arbitrary JSON file'() {
|
||||
given:
|
||||
WireMockHttpServerStub httpServerStub = new WireMockHttpServerStub()
|
||||
when:
|
||||
boolean accepted = httpServerStub.isAccepted(ARBITRARY_JSON)
|
||||
then:
|
||||
!accepted
|
||||
}
|
||||
|
||||
def 'should not accept a broken mapping file'() {
|
||||
given:
|
||||
WireMockHttpServerStub httpServerStub = new WireMockHttpServerStub()
|
||||
when:
|
||||
boolean accepted = httpServerStub.isAccepted(BROKEN_MAPPING)
|
||||
then:
|
||||
!accepted
|
||||
}
|
||||
|
||||
def 'should not accept a non-JSON file'() {
|
||||
given:
|
||||
WireMockHttpServerStub httpServerStub = new WireMockHttpServerStub()
|
||||
when:
|
||||
boolean accepted = httpServerStub.isAccepted(PDF)
|
||||
then:
|
||||
!accepted
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"firstname": "John",
|
||||
"name": "Doe"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"firstname": "Lisa",
|
||||
"name": "Smith"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/ping"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
Contract.make {
|
||||
request {
|
||||
url("/goodbye_from_file")
|
||||
method(GET())
|
||||
}
|
||||
response {
|
||||
status(OK())
|
||||
body(file("goodbye_response.txt"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Goodbye from file!
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
Contract.make {
|
||||
request {
|
||||
url("/hello")
|
||||
method(GET())
|
||||
}
|
||||
response {
|
||||
status(OK())
|
||||
body("Hello New World!")
|
||||
}
|
||||
}
|
||||
@@ -120,8 +120,7 @@ public class RecursiveFilesConverter {
|
||||
}
|
||||
File sourceFile = contract.getPath().toFile();
|
||||
Collection<StubGenerator> stubGenerators = contract.getConvertedContract() != null
|
||||
? holder.allOrDefault(new DslToWireMockClientConverter())
|
||||
: holder.converterForName(sourceFile.getAbsolutePath());
|
||||
? holder.allOrDefault(new DslToWireMockClientConverter()) : holder.converterForName(sourceFile);
|
||||
try {
|
||||
String path = sourceFile.getPath();
|
||||
if (excludeBuildFolders && (matchesPath(path, "target") || matchesPath(path, "build"))) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.converter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -32,12 +33,12 @@ import org.springframework.cloud.contract.verifier.file.ContractMetadata;
|
||||
public interface StubGenerator<T> {
|
||||
|
||||
/**
|
||||
* @param fileName - file name or absolute path of a contract
|
||||
* @return {@code true} if the converter can handle the contract file to convert it
|
||||
* into a stub.
|
||||
* @param file - file
|
||||
* @return {@code true} if the converter can handle the file to convert it into a
|
||||
* stub.
|
||||
*/
|
||||
default boolean canHandleFileName(String fileName) {
|
||||
return true;
|
||||
default boolean canHandleFileName(File file) {
|
||||
return file.getName().endsWith(fileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +82,7 @@ public interface StubGenerator<T> {
|
||||
* @param inputFileName - name of the input file
|
||||
* @return the name of the converted stub file. If you have multiple contracts in a
|
||||
* single file then a prefix will be added to the generated file. If you provide the
|
||||
* {@link Contract#getName()} field then that field will override the generated file
|
||||
* {@link Contract#getName} field then that field will override the generated file
|
||||
* name.
|
||||
*
|
||||
* Example: name of file with 2 contracts is {@code foo.groovy}, it will be converted
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.converter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -32,7 +33,7 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
*/
|
||||
public class StubGeneratorProvider {
|
||||
|
||||
private final List<StubGenerator> converters = new ArrayList<StubGenerator>();
|
||||
private final List<StubGenerator> converters = new ArrayList<>();
|
||||
|
||||
public StubGeneratorProvider() {
|
||||
this.converters.addAll(SpringFactoriesLoader.loadFactories(StubGenerator.class, null));
|
||||
@@ -42,8 +43,8 @@ public class StubGeneratorProvider {
|
||||
this.converters.addAll(converters);
|
||||
}
|
||||
|
||||
public Collection<StubGenerator> converterForName(final String fileName) {
|
||||
return this.converters.stream().filter(stubGenerator -> stubGenerator.canHandleFileName(fileName))
|
||||
public Collection<StubGenerator> converterForName(final File file) {
|
||||
return this.converters.stream().filter(stubGenerator -> stubGenerator.canHandleFileName(file))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,18 @@
|
||||
package org.springframework.cloud.contract.verifier.wiremock;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import com.github.tomakehurst.wiremock.common.JsonException;
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* WireMock implementation of the {@link StubGenerator}.
|
||||
@@ -30,6 +37,8 @@ import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
*/
|
||||
public abstract class DslToWireMockConverter implements StubGenerator<StubMapping> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(DslToWireMockConverter.class);
|
||||
|
||||
@Override
|
||||
public String generateOutputFileNameForInput(String inputFileName) {
|
||||
return inputFileName.replaceAll(extension(inputFileName), "json");
|
||||
@@ -44,15 +53,18 @@ public abstract class DslToWireMockConverter implements StubGenerator<StubMappin
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandleFileName(String fileName) {
|
||||
if (!fileName.endsWith(fileExtension())) {
|
||||
public boolean canHandleFileName(File file) {
|
||||
if (!file.getName().endsWith(fileExtension())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
StubMapping.buildFrom(new String(Files.readAllBytes(new File(fileName).toPath())));
|
||||
try (InputStream stream = Files.newInputStream(file.toPath())) {
|
||||
StubMapping.buildFrom(StreamUtils.copyToString(stream, Charset.forName("UTF-8")));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (IOException | JsonException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Cannot read file", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +193,9 @@ org.springframework.cloud.contract.spec.Contract.make {
|
||||
|
||||
private StubGenerator stubGenerator(String stub) {
|
||||
return new StubGenerator() {
|
||||
|
||||
@Override
|
||||
boolean canHandleFileName(String fileName) {
|
||||
boolean canHandleFileName(File fileName) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user