Don't delete additional files before generating stubs on the fly (#1544)
This behaviour did break contracts which include request or response bodies from separate files (via the `body(file(...))` directives).
This commit is contained in:
@@ -18,12 +18,8 @@ package org.springframework.cloud.contract.stubrunner;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -32,8 +28,6 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter;
|
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.cloud.contract.verifier.messaging.MessageVerifier;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
@@ -94,12 +88,12 @@ class StubRunnerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateMappingsAtRuntime(Path path) {
|
private void generateMappingsAtRuntime(Path path) {
|
||||||
removeCurrentMappings(path);
|
|
||||||
generateNewMappings(path);
|
generateNewMappings(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path resolvePath(File unpackedLocation) {
|
private Path resolvePath(File unpackedLocation) {
|
||||||
Resource resource = ResourceResolver.resource(unpackedLocation.toURI().toString());
|
Resource resource = ResourceResolver
|
||||||
|
.resource(unpackedLocation.toURI().toString());
|
||||||
Path path = unpackedLocation.toPath();
|
Path path = unpackedLocation.toPath();
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
try {
|
try {
|
||||||
@@ -112,40 +106,6 @@ class StubRunnerFactory {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeCurrentMappings(Path path) {
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
|
||||||
|
|
||||||
private final Log log = LogFactory.getLog(StubRunnerFactory.class);
|
|
||||||
|
|
||||||
private final StubGeneratorProvider provider = new StubGeneratorProvider();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
|
||||||
Collection<StubGenerator> stubGenerators = this.provider
|
|
||||||
.converterForName(file.toString());
|
|
||||||
if (!stubGenerators.isEmpty()) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Deleting file [" + file.toString()
|
|
||||||
+ "] since at least one stub generator would run it");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Files.delete(file);
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
log.warn("Failed to delete file [" + file.toString() + "]",
|
|
||||||
ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
log.warn("Exception occurred while trying to delete mappings", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateNewMappings(Path path) {
|
private void generateNewMappings(Path path) {
|
||||||
File unpackedLocation = path.toFile();
|
File unpackedLocation = path.toFile();
|
||||||
RecursiveFilesConverter converter = new RecursiveFilesConverter(
|
RecursiveFilesConverter converter = new RecursiveFilesConverter(
|
||||||
|
|||||||
@@ -61,18 +61,45 @@ class StubRunnerSpec extends Specification {
|
|||||||
def 'should generate stubs at runtime'() {
|
def 'should generate stubs at runtime'() {
|
||||||
given:
|
given:
|
||||||
Arguments args = argumentsWithGenerateStubs()
|
Arguments args = argumentsWithGenerateStubs()
|
||||||
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions);
|
StubDownloader downloader = new FileStubDownloader().build(args.stubRunnerOptions)
|
||||||
StubRunner runner = new StubRunnerFactory(args.stubRunnerOptions,
|
StubRunner runner = new StubRunnerFactory(args.stubRunnerOptions,
|
||||||
downloader, new NoOpStubMessages()).createStubsFromServiceConfiguration().first()
|
downloader, new NoOpStubMessages()).createStubsFromServiceConfiguration().first()
|
||||||
when:
|
when:
|
||||||
runner.runStubs()
|
runner.runStubs()
|
||||||
then:
|
then:
|
||||||
URL url = runner.findStubUrl("groupId2", "artifactId2")
|
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:
|
when:
|
||||||
new URL(url.toString() + "/hello").text
|
runner.runStubs()
|
||||||
then:
|
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:
|
cleanup:
|
||||||
runner.close()
|
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.Parameters
|
||||||
import com.github.tomakehurst.wiremock.extension.ResponseTransformer
|
import com.github.tomakehurst.wiremock.extension.ResponseTransformer
|
||||||
import com.github.tomakehurst.wiremock.http.ChunkedDribbleDelay
|
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.Request
|
||||||
import com.github.tomakehurst.wiremock.http.Response
|
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.DefaultResponseTransformer
|
||||||
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions
|
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
|
* Transformer adds a X-My-Header with value "surprise!" to the response
|
||||||
* the stub mapping returns
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
Response transform(Request request, Response response, FileSource files, Parameters parameters) {
|
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,
|
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)
|
response.initialDelay, new ChunkedDribbleDelay(0, 0), response.fromProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import spock.lang.Specification
|
|||||||
|
|
||||||
import org.springframework.boot.test.system.OutputCaptureRule
|
import org.springframework.boot.test.system.OutputCaptureRule
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate
|
import org.springframework.boot.test.web.client.TestRestTemplate
|
||||||
|
import org.springframework.http.HttpEntity
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.web.client.RestTemplate
|
import org.springframework.web.client.RestTemplate
|
||||||
|
|
||||||
class WireMockHttpServerStubSpec extends Specification {
|
class WireMockHttpServerStubSpec extends Specification {
|
||||||
@@ -50,7 +52,9 @@ class WireMockHttpServerStubSpec extends Specification {
|
|||||||
then:
|
then:
|
||||||
noExceptionThrown()
|
noExceptionThrown()
|
||||||
expect:
|
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:
|
cleanup:
|
||||||
mappingDescriptor?.stop()
|
mappingDescriptor?.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user