Generate stubs at runtime (#1152)
* Generate stubs at runtime without this change the producer side must publish the stubs for the consumer side to use them with this change the consumer can toggle a switch so that the stubs get generated at runtime. This might of course lead to false positivies but we assume that the users know what they're doing fixes gh-881
This commit is contained in:
committed by
GitHub
parent
d97db26001
commit
dbb1af8d98
@@ -1820,7 +1820,7 @@ own implementation of the `StubGenerator` interface. The following code listing
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
include::{converters_path}/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGenerator.groovy[indent=0,lines=16..-1]
|
||||
include::{converters_path}/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGenerator.java[indent=0,lines=16..-1]
|
||||
----
|
||||
|
||||
Again, you must provide a `spring.factories` file, such as the one shown in the following
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 com.example.loan;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerPort;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// tag::autoconfigure_stubrunner[]
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
|
||||
@AutoConfigureStubRunner(ids = {
|
||||
"com.example:http-server-dsl"},
|
||||
repositoryRoot = "stubs://classpath://contractsAtRuntime/",
|
||||
stubsMode = StubRunnerProperties.StubsMode.LOCAL,
|
||||
generateStubs = true)
|
||||
public class GoodbyeWorldTests {
|
||||
|
||||
// end::autoconfigure_stubrunner[]
|
||||
|
||||
@StubRunnerPort("http-server-dsl")
|
||||
int port;
|
||||
|
||||
@Test
|
||||
public void shouldGenerateStubsAtRuntime() {
|
||||
// when:
|
||||
String response = new RestTemplate().getForObject("http://localhost:" + this.port + "/goodbye", String.class);
|
||||
// then:
|
||||
assertThat(response)
|
||||
.isEqualTo("Goodbye World!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
Contract.make {
|
||||
request {
|
||||
url("/goodbye")
|
||||
method(GET())
|
||||
}
|
||||
response {
|
||||
status(OK())
|
||||
body("Goodbye World!")
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-shade</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-converters</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.stubrunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider;
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
final class MappingGenerator {
|
||||
|
||||
private static final Log log = LogFactory.getLog(MappingGenerator.class);
|
||||
|
||||
private MappingGenerator() {
|
||||
throw new IllegalStateException("Can't instantiate utility class");
|
||||
}
|
||||
|
||||
static Collection<Path> toMappings(File contractFile, Collection<Contract> contracts,
|
||||
File mappingsFolder) {
|
||||
StubGeneratorProvider provider = new StubGeneratorProvider();
|
||||
Collection<StubGenerator> stubGenerators = provider
|
||||
.converterForName(contractFile.getName());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found following matching stub generators " + stubGenerators);
|
||||
}
|
||||
Collection<Path> mappings = new LinkedList<>();
|
||||
for (StubGenerator stubGenerator : stubGenerators) {
|
||||
Map<Contract, String> map = stubGenerator.convertContents(
|
||||
contractFile.getName(), new ContractMetadata(contractFile.toPath(),
|
||||
false, contracts.size(), null, contracts));
|
||||
for (Map.Entry<Contract, String> entry : map.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
File mapping = new File(mappingsFolder,
|
||||
StringUtils.stripFilenameExtension(contractFile.getName()) + "_"
|
||||
+ Math.abs(entry.getKey().hashCode())
|
||||
+ stubGenerator.fileExtension());
|
||||
mappings.add(storeFile(mapping.toPath(), value.getBytes()));
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private static Path storeFile(Path path, byte[] contents) {
|
||||
try {
|
||||
Path storedPath = Files.write(path, contents);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Stored file [" + path.toString() + "]");
|
||||
}
|
||||
return storedPath;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,13 @@
|
||||
package org.springframework.cloud.contract.stubrunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
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;
|
||||
@@ -25,7 +31,12 @@ import java.util.Map;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Factory of StubRunners. Basing on the options and passed collaborators downloads the
|
||||
@@ -33,8 +44,7 @@ import org.springframework.cloud.contract.verifier.messaging.MessageVerifier;
|
||||
*/
|
||||
class StubRunnerFactory {
|
||||
|
||||
private static final Log log = LogFactory
|
||||
.getLog(MethodHandles.lookup().lookupClass());
|
||||
private static final Log log = LogFactory.getLog(StubRunnerFactory.class);
|
||||
|
||||
private final StubRunnerOptions stubRunnerOptions;
|
||||
|
||||
@@ -69,13 +79,93 @@ class StubRunnerFactory {
|
||||
+ "] the downloaded entry is [" + entry + "]");
|
||||
}
|
||||
if (entry != null) {
|
||||
result.add(createStubRunner(entry.getKey(), entry.getValue()));
|
||||
Path path = resolvePath(entry.getValue());
|
||||
File unpackedLocation = path.toFile();
|
||||
if (this.stubRunnerOptions.isGenerateStubs()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"Flag to generate stubs at runtime was switched on. Will remove the current mappings and will generate new ones.");
|
||||
}
|
||||
generateMappingsAtRuntime(path);
|
||||
}
|
||||
result.add(createStubRunner(entry.getKey(), unpackedLocation));
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void generateMappingsAtRuntime(Path path) {
|
||||
removeCurrentMappings(path);
|
||||
generateNewMappings(path);
|
||||
}
|
||||
|
||||
private Path resolvePath(File unpackedLocation) {
|
||||
Resource resource = ResourceResolver.resource(unpackedLocation.getPath());
|
||||
Path path = unpackedLocation.toPath();
|
||||
if (resource != null) {
|
||||
try {
|
||||
return Paths.get(resource.getURI());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return unpackedLocation.toPath();
|
||||
}
|
||||
}
|
||||
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();
|
||||
ContractVerifierConfigProperties configProperties = new ContractVerifierConfigProperties();
|
||||
configProperties
|
||||
.setContractsDslDir(subfolderIfPresent(unpackedLocation, "contracts"));
|
||||
configProperties
|
||||
.setStubsOutputDir(subfolderIfPresent(unpackedLocation, "mappings"));
|
||||
RecursiveFilesConverter converter = new RecursiveFilesConverter(configProperties);
|
||||
converter.processFiles();
|
||||
}
|
||||
|
||||
private File subfolderIfPresent(File unpackedLocation, String subfolder) {
|
||||
File subfolderDir = new File(unpackedLocation, subfolder);
|
||||
if (subfolderDir.exists()) {
|
||||
return subfolderDir;
|
||||
}
|
||||
return unpackedLocation;
|
||||
}
|
||||
|
||||
private StubRunner createStubRunner(StubConfiguration stubsConfiguration,
|
||||
File unzipedStubDir) {
|
||||
if (unzipedStubDir == null) {
|
||||
|
||||
@@ -116,6 +116,12 @@ public class StubRunnerOptions {
|
||||
*/
|
||||
private boolean deleteStubsAfterTest;
|
||||
|
||||
/**
|
||||
* When enabled, this flag will tell stub runner to not load the generated stubs, but
|
||||
* convert the found contracts at runtime to a stub format and run those stubs.
|
||||
*/
|
||||
private boolean generateStubs;
|
||||
|
||||
/**
|
||||
* Map of properties that can be passed to custom
|
||||
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
|
||||
@@ -128,7 +134,8 @@ public class StubRunnerOptions {
|
||||
Map<StubConfiguration, Integer> stubIdsToPortMapping, String username,
|
||||
String password, final StubRunnerProxyOptions stubRunnerProxyOptions,
|
||||
boolean stubsPerConsumer, String consumerName, String mappingsOutputFolder,
|
||||
boolean deleteStubsAfterTest, Map<String, String> properties,
|
||||
boolean deleteStubsAfterTest, boolean generateStubs,
|
||||
Map<String, String> properties,
|
||||
Class<? extends HttpServerStubConfigurer> httpServerStubConfigurer) {
|
||||
this.minPortValue = minPortValue;
|
||||
this.maxPortValue = maxPortValue;
|
||||
@@ -145,6 +152,7 @@ public class StubRunnerOptions {
|
||||
this.consumerName = consumerName;
|
||||
this.mappingsOutputFolder = mappingsOutputFolder;
|
||||
this.deleteStubsAfterTest = deleteStubsAfterTest;
|
||||
this.generateStubs = generateStubs;
|
||||
this.properties = properties;
|
||||
this.httpServerStubConfigurer = httpServerStubConfigurer;
|
||||
}
|
||||
@@ -169,6 +177,8 @@ public class StubRunnerOptions {
|
||||
System.getProperty("stubrunner.mappings-output-folder"))
|
||||
.withDeleteStubsAfterTest(Boolean.parseBoolean(
|
||||
System.getProperty("stubrunner.delete-stubs-after-test", "true")))
|
||||
.withGenerateStubs(Boolean.parseBoolean(
|
||||
System.getProperty("stubrunner.generate-stubs", "false")))
|
||||
.withProperties(stubRunnerProps());
|
||||
builder = httpStubConfigurer(builder);
|
||||
String proxyHost = System.getProperty("stubrunner.proxy.host");
|
||||
@@ -318,6 +328,10 @@ public class StubRunnerOptions {
|
||||
this.deleteStubsAfterTest = deleteStubsAfterTest;
|
||||
}
|
||||
|
||||
public boolean isGenerateStubs() {
|
||||
return this.generateStubs;
|
||||
}
|
||||
|
||||
public Map<String, String> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public class StubRunnerOptionsBuilder {
|
||||
|
||||
private boolean deleteStubsAfterTest = true;
|
||||
|
||||
private boolean generateStubs;
|
||||
|
||||
private Map<String, String> properties = new HashMap<>();
|
||||
|
||||
private Class httpServerStubConfigurer = HttpServerStubConfigurer.NoOpHttpServerStubConfigurer.class;
|
||||
@@ -194,6 +196,7 @@ public class StubRunnerOptionsBuilder {
|
||||
this.stubIdsToPortMapping = options.stubIdsToPortMapping != null
|
||||
? options.stubIdsToPortMapping : new LinkedHashMap<>();
|
||||
this.deleteStubsAfterTest = options.isDeleteStubsAfterTest();
|
||||
this.generateStubs = options.isGenerateStubs();
|
||||
this.properties = options.getProperties();
|
||||
this.httpServerStubConfigurer = options.getHttpServerStubConfigurer();
|
||||
return this;
|
||||
@@ -211,6 +214,11 @@ public class StubRunnerOptionsBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public StubRunnerOptionsBuilder withGenerateStubs(boolean generateStubs) {
|
||||
this.generateStubs = generateStubs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StubRunnerOptionsBuilder withProperties(Map<String, String> properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
@@ -228,7 +236,7 @@ public class StubRunnerOptionsBuilder {
|
||||
buildDependencies(), this.stubIdsToPortMapping, this.username,
|
||||
this.password, this.stubRunnerProxyOptions, this.stubsPerConsumer,
|
||||
this.consumerName, this.mappingsOutputFolder, this.deleteStubsAfterTest,
|
||||
this.properties, this.httpServerStubConfigurer);
|
||||
this.generateStubs, this.properties, this.httpServerStubConfigurer);
|
||||
}
|
||||
|
||||
private Collection<StubConfiguration> buildDependencies() {
|
||||
|
||||
@@ -252,6 +252,12 @@ public class StubRunnerExtension implements BeforeAllCallback, AfterAllCallback,
|
||||
return new PortStubRunnerExtension(this.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StubRunnerExtension withGenerateStubs(boolean generateStubs) {
|
||||
builder().withGenerateStubs(generateStubs);
|
||||
return new PortStubRunnerExtension(this.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StubRunnerExtension withProperties(Map<String, String> properties) {
|
||||
builder().withProperties(properties);
|
||||
|
||||
@@ -149,6 +149,13 @@ interface StubRunnerExtensionOptions {
|
||||
*/
|
||||
StubRunnerExtension withDeleteStubsAfterTest(boolean deleteStubsAfterTest);
|
||||
|
||||
/**
|
||||
* @param generateStubs If set to {@code true} will NOT load generated stubs but will
|
||||
* generate stubs from contract definitions at runtime.
|
||||
* @return the rule
|
||||
*/
|
||||
StubRunnerExtension withGenerateStubs(boolean generateStubs);
|
||||
|
||||
/**
|
||||
* @param properties Map of properties that can be passed to custom
|
||||
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}
|
||||
|
||||
@@ -190,6 +190,12 @@ public class StubRunnerRule implements TestRule, StubFinder, StubRunnerRuleOptio
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StubRunnerRule withGenerateStubs(boolean generateStubs) {
|
||||
builder().withGenerateStubs(true);
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StubRunnerRule withProperties(Map<String, String> properties) {
|
||||
builder().withProperties(properties);
|
||||
|
||||
@@ -145,6 +145,13 @@ interface StubRunnerRuleOptions {
|
||||
*/
|
||||
StubRunnerRule withDeleteStubsAfterTest(boolean deleteStubsAfterTest);
|
||||
|
||||
/**
|
||||
* @param generateStubs If set to {@code true} will NOT load generated stubs but will
|
||||
* generate stubs from contract definitions at runtime.
|
||||
* @return the rule
|
||||
*/
|
||||
StubRunnerRule withGenerateStubs(boolean generateStubs);
|
||||
|
||||
/**
|
||||
* @param properties Map of properties that can be passed to custom
|
||||
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}
|
||||
|
||||
@@ -137,6 +137,13 @@ public @interface AutoConfigureStubRunner {
|
||||
*/
|
||||
boolean deleteStubsAfterTest() default true;
|
||||
|
||||
/**
|
||||
* @return When enabled, this flag will tell stub runner to not load the generated
|
||||
* stubs, but convert the found contracts at runtime to a stub format and run those
|
||||
* stubs.
|
||||
*/
|
||||
boolean generateStubs() default false;
|
||||
|
||||
/**
|
||||
* Configuration for an HTTP server stub.
|
||||
* @return class that allows to perform additional HTTP server stub configuration
|
||||
|
||||
@@ -101,6 +101,7 @@ public class StubRunnerConfiguration {
|
||||
.withConsumerName(consumerName())
|
||||
.withMappingsOutputFolder(this.props.getMappingsOutputFolder())
|
||||
.withDeleteStubsAfterTest(this.props.isDeleteStubsAfterTest())
|
||||
.withGenerateStubs(this.props.isGenerateStubs())
|
||||
.withProperties(this.props.getProperties())
|
||||
.withHttpServerStubConfigurer(this.props.getHttpServerStubConfigurer());
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("stubrunner")
|
||||
public class StubRunnerProperties {
|
||||
@@ -109,6 +108,12 @@ public class StubRunnerProperties {
|
||||
*/
|
||||
private boolean deleteStubsAfterTest = true;
|
||||
|
||||
/**
|
||||
* When enabled, this flag will tell stub runner to not load the generated stubs, but
|
||||
* convert the found contracts at runtime to a stub format and run those stubs.
|
||||
*/
|
||||
private boolean generateStubs;
|
||||
|
||||
/**
|
||||
* Map of properties that can be passed to custom
|
||||
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
|
||||
@@ -247,6 +252,14 @@ public class StubRunnerProperties {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isGenerateStubs() {
|
||||
return this.generateStubs;
|
||||
}
|
||||
|
||||
public void setGenerateStubs(boolean generateStubs) {
|
||||
this.generateStubs = generateStubs;
|
||||
}
|
||||
|
||||
public Class getHttpServerStubConfigurer() {
|
||||
return this.httpServerStubConfigurer;
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
|
||||
given:
|
||||
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"), StubRunnerProperties.StubsMode.LOCAL,
|
||||
"classifier", [new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "foo", "bar",
|
||||
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, [foo: "bar"], Foo))
|
||||
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, [foo: "bar"], Foo))
|
||||
builder.withStubs("foo:bar:baz")
|
||||
when:
|
||||
StubRunnerOptions options = builder.build()
|
||||
@@ -251,6 +251,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
|
||||
options.consumerName == "consumer"
|
||||
options.mappingsOutputFolder == "folder"
|
||||
options.deleteStubsAfterTest == false
|
||||
options.generateStubs == true
|
||||
options.properties == [foo: "bar"]
|
||||
options.httpServerStubConfigurer == Foo
|
||||
}
|
||||
@@ -260,7 +261,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
|
||||
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"),
|
||||
StubRunnerProperties.StubsMode.CLASSPATH, "classifier",
|
||||
[new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "username123", "password123",
|
||||
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, [:], Foo))
|
||||
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, [:], Foo))
|
||||
builder.withStubs("foo:bar:baz")
|
||||
when:
|
||||
String options = builder.build().toString()
|
||||
@@ -290,6 +291,8 @@ class StubRunnerOptionsBuilderSpec extends Specification {
|
||||
System.setProperty("stubrunner.properties.foo-bar", "bar")
|
||||
System.setProperty("stubrunner.properties.foo-baz", "baz")
|
||||
System.setProperty("stubrunner.properties.bar.bar", "foo")
|
||||
System.setProperty("stubrunner.delete-stubs-after-test", "false")
|
||||
System.setProperty("stubrunner.generate-stubs", "true")
|
||||
System.setProperty("stubrunner.http-server-stub-configurer", "org.springframework.cloud.contract.stubrunner.Foo")
|
||||
when:
|
||||
StubRunnerOptions options = StubRunnerOptions.fromSystemProps()
|
||||
@@ -305,6 +308,8 @@ class StubRunnerOptionsBuilderSpec extends Specification {
|
||||
options.proxyOptions.proxyHost == "host"
|
||||
options.proxyOptions.proxyPort == 4
|
||||
options.stubsPerConsumer == true
|
||||
options.deleteStubsAfterTest == false
|
||||
options.generateStubs == true
|
||||
options.consumerName == "consumer"
|
||||
options.mappingsOutputFolder == "folder"
|
||||
options.properties == ["foo-bar": "bar", "foo-baz": "baz", "bar.bar": "foo"]
|
||||
|
||||
@@ -16,14 +16,21 @@
|
||||
|
||||
package org.springframework.cloud.contract.stubrunner
|
||||
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
|
||||
import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages
|
||||
import org.springframework.util.SocketUtils
|
||||
|
||||
class StubRunnerSpec extends Specification {
|
||||
|
||||
private static final int MIN_PORT = 8111
|
||||
private static final int MAX_PORT = 8111
|
||||
private static final int MIN_PORT = SocketUtils.findAvailableTcpPort()
|
||||
private static final int MAX_PORT = MIN_PORT
|
||||
private static final URL EXPECTED_STUB_URL = new URL("http://localhost:$MIN_PORT")
|
||||
|
||||
private static final URL generateStubs = StubRunnerSpec.getResource('/generateStubs/')
|
||||
|
||||
def 'should provide stub URL for provided groupid and artifactId'() {
|
||||
given:
|
||||
Arguments args = argumentsWithProjectDefinition()
|
||||
@@ -50,10 +57,42 @@ class StubRunnerSpec extends Specification {
|
||||
runner.close()
|
||||
}
|
||||
|
||||
|
||||
def 'should generate 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")
|
||||
new URL(url.toString() + "/goodbye").text
|
||||
when:
|
||||
new URL(url.toString() + "/hello").text
|
||||
then:
|
||||
thrown(FileNotFoundException)
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
Arguments argumentsWithProjectDefinition() {
|
||||
StubConfiguration stubConfiguration = new StubConfiguration("groupId", "artifactId", "classifier")
|
||||
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder().withMinMaxPort(MIN_PORT, MAX_PORT).build()
|
||||
return new Arguments(stubRunnerOptions, 'src/test/resources/repository', stubConfiguration)
|
||||
}
|
||||
|
||||
Arguments argumentsWithGenerateStubs() {
|
||||
StubConfiguration stubConfiguration = new StubConfiguration("groupId2", "artifactId2", "classifier2")
|
||||
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
|
||||
.withMinMaxPort(MIN_PORT, MAX_PORT)
|
||||
.withGenerateStubs(true)
|
||||
.withStubs(stubConfiguration.toString())
|
||||
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
|
||||
.withStubRepositoryRoot("stubs://file://" + generateStubs.path)
|
||||
.build()
|
||||
return new Arguments(stubRunnerOptions, 'src/test/resources/generateStubs', stubConfiguration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
Contract.make {
|
||||
request {
|
||||
url("/goodbye")
|
||||
method(GET())
|
||||
}
|
||||
response {
|
||||
status(OK())
|
||||
body("Goodbye World!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/hello"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "Hello world!",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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.transform.CompileStatic
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata
|
||||
|
||||
/**
|
||||
* Converts contracts into their stub representation.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@CompileStatic
|
||||
interface StubGenerator {
|
||||
|
||||
/**
|
||||
* @return {@code true} if the converter can handle the file to convert it into a stub.
|
||||
*/
|
||||
boolean canHandleFileName(String fileName)
|
||||
|
||||
/**
|
||||
* @return the collection of converted contracts into stubs. One contract can
|
||||
* result in multiple stubs.
|
||||
*/
|
||||
Map<Contract, String> convertContents(String rootName, ContractMetadata content)
|
||||
|
||||
/**
|
||||
* @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#name} 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 by the implementation to {@code foo.json}. The recursive file
|
||||
* converter will create two files {@code 0_foo.json} and {@code 1_foo.json}
|
||||
*/
|
||||
String generateOutputFileNameForInput(String inputFileName)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
|
||||
|
||||
/**
|
||||
* Converts contracts into their stub representation.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface StubGenerator {
|
||||
|
||||
/**
|
||||
* @param fileName - file name
|
||||
* @return {@code true} if the converter can handle the file to convert it into a
|
||||
* stub.
|
||||
*/
|
||||
default boolean canHandleFileName(String fileName) {
|
||||
return fileName.endsWith(fileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rootName - root name of the contract
|
||||
* @param content - metadata of the contract
|
||||
* @return the collection of converted contracts into stubs. One contract can result
|
||||
* in multiple stubs.
|
||||
*/
|
||||
Map<Contract, String> convertContents(String rootName, ContractMetadata content);
|
||||
|
||||
/**
|
||||
* @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#name} 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
|
||||
* by the implementation to {@code foo.json}. The recursive file converter will create
|
||||
* two files {@code 0_foo.json} and {@code 1_foo.json}
|
||||
*/
|
||||
String generateOutputFileNameForInput(String inputFileName);
|
||||
|
||||
/**
|
||||
* Describes the file extension that this stub generator can handle.
|
||||
* @return string describing the file extension
|
||||
*/
|
||||
default String fileExtension() {
|
||||
return ".json";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,11 +28,6 @@ import org.springframework.cloud.contract.verifier.converter.StubGenerator
|
||||
@CompileStatic
|
||||
abstract class DslToWireMockConverter implements StubGenerator {
|
||||
|
||||
@Override
|
||||
boolean canHandleFileName(String fileName) {
|
||||
return fileName.endsWith('.groovy')
|
||||
}
|
||||
|
||||
@Override
|
||||
String generateOutputFileNameForInput(String inputFileName) {
|
||||
return inputFileName.replaceAll(extension(inputFileName), 'json')
|
||||
|
||||
@@ -38,10 +38,10 @@ import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConve
|
||||
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter;
|
||||
|
||||
/**
|
||||
* Convert Spring Cloud Contract Verifier contracts into WireMock stubs mappings.
|
||||
* Convert Spring Cloud Contract Verifier contracts into stubs mappings.
|
||||
* <p>
|
||||
* This goal allows you to generate `stubs-jar` or execute `spring-cloud-contract:run`
|
||||
* with generated WireMock mappings.
|
||||
* with generated mappings.
|
||||
*
|
||||
* @author Mariusz Smykula
|
||||
*/
|
||||
|
||||
@@ -47,9 +47,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider;
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
|
||||
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter;
|
||||
import org.springframework.core.io.AbstractResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -216,32 +213,16 @@ class PactStubDownloader implements StubDownloader {
|
||||
log.debug("Converted pact file [" + file + "] to [" + contracts.size()
|
||||
+ "] contracts");
|
||||
}
|
||||
StubGeneratorProvider provider = new StubGeneratorProvider();
|
||||
Collection<StubGenerator> stubGenerators = provider
|
||||
.converterForName(ARTIFICIAL_NAME_ENDING_WITH_GROOVY);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found following matching stub generators " + stubGenerators);
|
||||
}
|
||||
for (StubGenerator stubGenerator : stubGenerators) {
|
||||
Map<Contract, String> map = stubGenerator.convertContents(file.getName(),
|
||||
new ContractMetadata(file.toPath(), false, contracts.size(), null,
|
||||
contracts));
|
||||
for (Map.Entry<Contract, String> entry : map.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
File mapping = new File(mappingsFolder,
|
||||
StringUtils.stripFilenameExtension(file.getName()) + "_"
|
||||
+ Math.abs(entry.getKey().hashCode()) + ".json");
|
||||
storeFile(mapping.toPath(), value.getBytes());
|
||||
}
|
||||
}
|
||||
MappingGenerator.toMappings(file, contracts, mappingsFolder);
|
||||
}
|
||||
|
||||
private void storeFile(Path path, byte[] contents) {
|
||||
private Path storeFile(Path path, byte[] contents) {
|
||||
try {
|
||||
Files.write(path, contents);
|
||||
Path storedPath = Files.write(path, contents);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Stored file [" + path.toString() + "]");
|
||||
}
|
||||
return storedPath;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
||||
Reference in New Issue
Block a user