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:
Dominik Meister
2020-10-30 09:32:25 +01:00
committed by GitHub
parent 692c5cf828
commit 6d44c54c80
8 changed files with 100 additions and 50 deletions

View File

@@ -18,12 +18,8 @@ package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
@@ -32,8 +28,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;
@@ -94,12 +88,12 @@ class StubRunnerFactory {
}
private void generateMappingsAtRuntime(Path path) {
removeCurrentMappings(path);
generateNewMappings(path);
}
private Path resolvePath(File unpackedLocation) {
Resource resource = ResourceResolver.resource(unpackedLocation.toURI().toString());
Resource resource = ResourceResolver
.resource(unpackedLocation.toURI().toString());
Path path = unpackedLocation.toPath();
if (resource != null) {
try {
@@ -112,40 +106,6 @@ class StubRunnerFactory {
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) {
File unpackedLocation = path.toFile();
RecursiveFilesConverter converter = new RecursiveFilesConverter(

View File

@@ -61,18 +61,45 @@ class StubRunnerSpec extends Specification {
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()
}

View File

@@ -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)
}

View File

@@ -23,6 +23,8 @@ 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 {
@@ -50,7 +52,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()
}

View 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("/goodbye_from_file")
method(GET())
}
response {
status(OK())
body(file("goodbye_response.txt"))
}
}

View 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!")
}
}