Merge branch '1.2.x'

also accidentally fixed gh-507
This commit is contained in:
Marcin Grzejszczak
2018-02-16 16:05:54 +01:00
28 changed files with 331 additions and 169 deletions

View File

@@ -166,7 +166,7 @@ From the Consumer perspective, when shooting a request in the integration test:
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that matches a regular expression `[0-9]{10}`
* has a field `client.id` that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the response will be sent with
@@ -181,7 +181,7 @@ From the Producer perspective, in the autogenerated producer-side test:
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the test will assert if the response has been sent with
@@ -223,7 +223,7 @@ response: # (7)
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `clientId`
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
@@ -239,7 +239,7 @@ response: # (7)
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `clientId` `1234567890`
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with
@@ -524,7 +524,7 @@ From the Consumer perspective, when shooting a request in the integration test:
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that matches a regular expression `[0-9]{10}`
* has a field `client.id` that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the response will be sent with
@@ -539,7 +539,7 @@ From the Producer perspective, in the autogenerated producer-side test:
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the test will assert if the response has been sent with
@@ -581,7 +581,7 @@ response: # (7)
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `clientId`
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
@@ -597,7 +597,7 @@ response: # (7)
#(2) - With the "PUT" method
#(3) - to the URL "/fraudcheck"
#(4) - with the JSON body that
# * has a field `clientId` `1234567890`
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with

View File

@@ -255,9 +255,10 @@ closure to set it up.
* *contractsPath*: Specifies the path to the jar. If contract dependencies are
downloaded, the path defaults to `groupid/artifactid` where `groupid` is slash
separated. Otherwise, it scans contracts under the provided directory.
* *contractsWorkOffline*: Specifies whether to download the dependencies each time, so
that you can work online. In other words, it specifies whether to reuses the local Maven
repo.
* *contractsMode*: Specifies the mode of downloading contracts (whether the
JAR is available offline, remotely etc.)
* *contractsSnapshotCheckSkip*: If set to `true` will not assert whether the
downloaded stubs / contract JAR was downloaded from a remote location or a local one
[[gradle-single-base-class]]
==== Single Base Class for All Tests
@@ -393,7 +394,6 @@ following sections:
* <<maven-different-base>>
* <<maven-invoking-generated-tests>>
* <<maven-sts>>
* <<maven-consumer>>
[[maven-add-plugin]]
==== Add maven plugin
@@ -581,8 +581,9 @@ the following options:
* *contractDependency*: The contract dependency that contains all the packaged contracts.
* *contractsPath*: The path to the concrete contracts in the JAR with packaged contracts.
Defaults to `groupid/artifactid` where `gropuid` is slash separated.
* *contractsWorkOffline*: Dictates whether the dependencies should be downloaded or the
local Maven artifacts should be reused.
* *contractsMode*: Picks the mode in which stubs will be found and registered
* *contractsSnapshotCheckSkip*: If `true` then will not assert whether a stub / contract
JAR was downloaded from local or remote location
* *contractsRepositoryUrl*: URL to a repo with the artifacts that have contracts. If it is not provided,
use the current Maven ones.
* *contractsRepositoryUsername*: The user name to be used to connect to the repo with contracts.
@@ -767,67 +768,6 @@ In order to fix this issue, provide the following section in your `pom.xml`:
</build>
----
[[maven-consumer]]
==== Spring Cloud Contract Verifier on the Consumer Side
You can also use the Spring Cloud Contract Verifier for the consumer side. To do so, use
the plugin so that it only converts the contracts and generates the stubs. To achieve
that, you need to configure Spring Cloud Contract Verifier plugin in exactly the same way
as you would for a provider. You need to copy contracts stored in
`src/test/resources/contracts` and generate WireMock JSON stubs using the
`mvn generateStubs` command. By default, the generated WireMock mapping is stored in a
directory named `target/mappings`. From these generated mappings, your project should
create additional artifacts with a classifier of `stubs` for easy deployment to the maven
repository.
Here is a sample configuration:
[source,xml,indent=0]
----
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${verifier-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>convert</goal>
<goal>generateStubs</goal>
</goals>
</execution>
</executions>
</plugin>
----
When present, JSON stubs can be used in consumer automated tests, as shown here:
[source,groovy,indent=0]
----
@RunWith(SpringTestRunner.class)
@SpringBootTest
@AutoConfigureStubRunner
public class LoanApplicationServiceTests {
@Autowired
LoanApplicationService service;
@Test
public void shouldSuccessfullyApplyForLoan() {
//given:
LoanApplication application =
new LoanApplication(new Client("12345678901"), 123.123);
//when:
LoanApplicationResult loanApplication = service.loanApplication(application);
// then:
assertThat(loanApplication.loanApplicationStatus).isEqualTo(LoanApplicationStatus.LOAN_APPLIED);
assertThat(loanApplication.rejectionReason).isNull();
}
}
----
`LoanApplication` makes a call to the `FraudDetection` service. This request is handled
by a WireMock server configured with stubs generated by the Spring Cloud Contract
Verifier.
=== Stubs and Transitive Dependencies
The Maven and Gradle plugin that add the tasks that create the stubs jar for you. One
@@ -872,6 +812,27 @@ For example, you might decide to have no dependencies at all.
As a consumer, if you add the stub dependency to your classpath, you can explicitly
exclude the unwanted dependencies.
=== CI Server setup
When fetching stubs / contracts in a CI, shared environment, what might happen is that
both the producer and the consumer reuse the same local Maven repository. Due to this,
the framework, responsible for downloading a stub JAR from remote location,
can't decide which JAR should be picked, local or remote one. That caused
the `"The artifact was found in the local repository but you have explicitly
stated that it should be downloaded from a remote one"` exception
and failed the build.
For such cases we're introducing the property and plugin setup mechanism:
- via `stubrunner.snapshot-check-skip` system property
- via `STUBRUNNER_SNAPSHOT_CHECK_SKIP` environment variable
if either of these values is set to `true`, then the stub downloader will not
verify the origin of the downloaded JAR.
For the plugins you need to set the `contractsSnapshotSkipCheck` property
to `true`.
=== Scenarios
You can handle scenarios with Spring Cloud Contract Verifier. All you need to do is to

View File

@@ -43,7 +43,7 @@ Example for Maven
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<stubsMode>LOCAL</stubsMode>
<contractsMode>LOCAL</contractsMode>
<contractDependency>
<groupId>com.example.standalone</groupId>
<artifactId>contracts</artifactId>

View File

@@ -73,11 +73,14 @@ public class AetherStubDownloader implements StubDownloader {
private static final String ARTIFACT_EXTENSION = "jar";
private static final String LATEST_ARTIFACT_VERSION = "(,]";
private static final String LATEST_VERSION_IN_IVY = "+";
private static final String STUBRUNNER_SNAPSHOT_CHECK_SKIP_SYSTEM_PROP = "stubrunner.snapshot-check-skip";
private static final String STUBRUNNER_SNAPSHOT_CHECK_SKIP_ENV_VAR = "STUBRUNNER_SNAPSHOT_CHECK_SKIP";
private final List<RemoteRepository> remoteRepos;
private final RepositorySystem repositorySystem;
private final RepositorySystemSession session;
private final boolean workOffline;
private final boolean snapshotCheckSkip;
public AetherStubDownloader(StubRunnerOptions stubRunnerOptions) {
if (log.isDebugEnabled()) {
@@ -86,19 +89,23 @@ public class AetherStubDownloader implements StubDownloader {
this.remoteRepos = remoteRepositories(stubRunnerOptions);
boolean remoteReposMissing = remoteReposMissing();
switch (stubRunnerOptions.stubsMode) {
case LOCAL:
log.info("Remote repos not passed but the switch to work offline was set. "
+ "Stubs will be used from your local Maven repository.");
break;
case REMOTE:
if (remoteReposMissing) throw new IllegalStateException("Remote repositories for stubs are not specified and work offline flag wasn't passed");
break;
case CLASSPATH:
throw new UnsupportedOperationException("You can't use Aether downloader when you use classpath to find stubs");
case LOCAL:
log.info("Remote repos not passed but the switch to work offline was set. " + "Stubs will be used from your local Maven repository.");
break;
case REMOTE:
if (remoteReposMissing)
throw new IllegalStateException(
"Remote repositories for stubs are not specified and work offline flag wasn't passed");
break;
case CLASSPATH:
throw new UnsupportedOperationException(
"You can't use Aether downloader when you use classpath to find stubs");
}
this.repositorySystem = newRepositorySystem();
this.workOffline = stubRunnerOptions.stubsMode == StubRunnerProperties.StubsMode.LOCAL;
this.session = newSession(this.repositorySystem, this.workOffline);
this.snapshotCheckSkip =
stubRunnerOptions.isSnapshotCheckSkip() || skipSnapshotCheck();
registerShutdownHook();
}
@@ -122,6 +129,7 @@ public class AetherStubDownloader implements StubDownloader {
log.error("Remote repositories for stubs are not specified and work offline flag wasn't passed");
}
this.workOffline = false;
this.snapshotCheckSkip = skipSnapshotCheck();
registerShutdownHook();
}
@@ -171,7 +179,7 @@ public class AetherStubDownloader implements StubDownloader {
ArtifactResult result = this.repositorySystem.resolveArtifact(this.session, request);
log.info("Resolved artifact [" + artifact + "] to "
+ result.getArtifact().getFile());
if (resolvedFromLocalRepo(result) && shouldDownloadFromRemote()) {
if (!this.snapshotCheckSkip && resolvedFromLocalRepo(result) && shouldDownloadFromRemote()) {
throw new IllegalStateException("The artifact was found in the local repository "
+ "but you have explicitly stated that it should be downloaded from a remote one");
}
@@ -190,7 +198,23 @@ public class AetherStubDownloader implements StubDownloader {
+ "] and classifier [" + classifier + "] in " + this.remoteRepos,
e);
}
}
private boolean skipSnapshotCheck() {
// still checking the system / env props setting for backward compatibility
// when running this for plugins
String skipSnapCheckProp = System.getProperty(STUBRUNNER_SNAPSHOT_CHECK_SKIP_SYSTEM_PROP);
String skipSnapCheckEnv = getSkipSnapEnvProp();
if (StringUtils.hasText(skipSnapCheckProp)) {
return Boolean.parseBoolean(skipSnapCheckProp);
}
return StringUtils.hasText(skipSnapCheckEnv) && Boolean
.parseBoolean(skipSnapCheckEnv);
}
// Visible for testing
String getSkipSnapEnvProp() {
return System.getenv(STUBRUNNER_SNAPSHOT_CHECK_SKIP_ENV_VAR);
}
private boolean resolvedFromLocalRepo(ArtifactResult result) {

View File

@@ -73,7 +73,7 @@ class StubRepository {
}
StubRepository(File repository) {
this(repository, new ArrayList<HttpServerStub>(), new StubRunnerOptionsBuilder().build());
this(repository, new ArrayList<>(), new StubRunnerOptionsBuilder().build());
}
public File getPath() {

View File

@@ -27,6 +27,12 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
/**
* Class to run stub runner as a standalone process
*
* @deprecated - use Stub Runner Boot server
*/
@Deprecated
public class StubRunnerMain {
private static final Logger log = LoggerFactory.getLogger(StubRunnerMain.class);

View File

@@ -25,7 +25,7 @@ import org.springframework.cloud.contract.stubrunner.util.StringUtils;
/**
* Technical options related to running StubRunner
*
* Use {@class StubRunnerOptionsBuilder} to build this object.
* Use {@see StubRunnerOptionsBuilder} to build this object.
*
* @see StubRunnerOptionsBuilder
*/
@@ -91,12 +91,18 @@ public class StubRunnerOptions {
final StubRunnerProperties.StubsMode stubsMode;
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one
*/
private boolean snapshotCheckSkip;
StubRunnerOptions(Integer minPortValue, Integer maxPortValue,
String stubRepositoryRoot, StubRunnerProperties.StubsMode stubsMode, String stubsClassifier,
Collection<StubConfiguration> dependencies,
Map<StubConfiguration, Integer> stubIdsToPortMapping,
String username, String password, final StubRunnerProxyOptions stubRunnerProxyOptions,
boolean stubsPerConsumer, String consumerName, String mappingsOutputFolder) {
boolean stubsPerConsumer, String consumerName, String mappingsOutputFolder, boolean snapshotCheckSkip) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
this.stubRepositoryRoot = stubRepositoryRoot;
@@ -110,6 +116,7 @@ public class StubRunnerOptions {
this.stubsPerConsumer = stubsPerConsumer;
this.consumerName = consumerName;
this.mappingsOutputFolder = mappingsOutputFolder;
this.snapshotCheckSkip = snapshotCheckSkip;
}
public Integer port(StubConfiguration stubConfiguration) {
@@ -133,7 +140,8 @@ public class StubRunnerOptions {
.withPassword(System.getProperty("stubrunner.password"))
.withStubPerConsumer(Boolean.parseBoolean(System.getProperty("stubrunner.stubs-per-consumer", "false")))
.withConsumerName(System.getProperty("stubrunner.consumer-name"))
.withMappingsOutputFolder(System.getProperty("stubrunner.mappings-output-folder"));
.withMappingsOutputFolder(System.getProperty("stubrunner.mappings-output-folder"))
.withSnapshotCheckSkip(Boolean.parseBoolean(System.getProperty("stubrunner.snapshot-check-skip", "false")));
String proxyHost = System.getProperty("stubrunner.proxy.host");
if (proxyHost != null) {
builder.withProxy(proxyHost, Integer.parseInt(System.getProperty("stubrunner.proxy.port")));
@@ -213,6 +221,14 @@ public class StubRunnerOptions {
this.mappingsOutputFolder = mappingsOutputFolder;
}
public boolean isSnapshotCheckSkip() {
return this.snapshotCheckSkip;
}
public void setSnapshotCheckSkip(boolean snapshotCheckSkip) {
this.snapshotCheckSkip = snapshotCheckSkip;
}
public static class StubRunnerProxyOptions {
private final String proxyHost;

View File

@@ -47,6 +47,7 @@ public class StubRunnerOptionsBuilder {
private String consumerName;
private String mappingsOutputFolder;
private StubRunnerProperties.StubsMode stubsMode;
private boolean snapshotCheckSkip = false;
public StubRunnerOptionsBuilder() {
}
@@ -125,6 +126,7 @@ public class StubRunnerOptionsBuilder {
options.dependencies : new ArrayList<StubConfiguration>();
this.stubIdsToPortMapping = options.stubIdsToPortMapping != null ?
options.stubIdsToPortMapping : new LinkedHashMap<StubConfiguration, Integer>();
this.snapshotCheckSkip = options.isSnapshotCheckSkip();
return this;
}
@@ -133,11 +135,16 @@ public class StubRunnerOptionsBuilder {
return this;
}
public StubRunnerOptionsBuilder withSnapshotCheckSkip(boolean snapshotCheckSkip) {
this.snapshotCheckSkip = snapshotCheckSkip;
return this;
}
public StubRunnerOptions build() {
return new StubRunnerOptions(this.minPortValue, this.maxPortValue, this.stubRepositoryRoot,
this.stubsMode, this.stubsClassifier, buildDependencies(), this.stubIdsToPortMapping,
this.username, this.password, this.stubRunnerProxyOptions, this.stubsPerConsumer, this.consumerName,
this.mappingsOutputFolder);
this.mappingsOutputFolder, this.snapshotCheckSkip);
}
private Collection<StubConfiguration> buildDependencies() {

View File

@@ -154,6 +154,11 @@ public class StubRunnerRule implements TestRule, StubFinder, StubRunnerRuleOptio
return this.delegate;
}
@Override public StubRunnerRule withSnapshotCheckSkip(boolean snapshotCheckSkip) {
builder().withSnapshotCheckSkip(snapshotCheckSkip);
return this.delegate;
}
@Override
public URL findStubUrl(String groupId, String artifactId) {
return this.stubFinder().findStubUrl(groupId, artifactId);

View File

@@ -93,4 +93,10 @@ interface StubRunnerRuleOptions {
* Allows setting the output folder for mappings
*/
StubRunnerRule withMappingsOutputFolder(String mappingsOutputFolder);
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one
*/
StubRunnerRule withSnapshotCheckSkip(boolean snapshotCheckSkip);
}

View File

@@ -108,4 +108,10 @@ public @interface AutoConfigureStubRunner {
* @return the type of stubs mode
*/
StubRunnerProperties.StubsMode stubsMode() default StubRunnerProperties.StubsMode.CLASSPATH;
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one
*/
boolean snapshotCheckSkip() default false;
}

View File

@@ -93,7 +93,8 @@ public class StubRunnerConfiguration {
.withPassword(this.props.getPassword())
.withStubPerConsumer(this.props.isStubsPerConsumer())
.withConsumerName(consumerName())
.withMappingsOutputFolder(this.props.getMappingsOutputFolder());
.withMappingsOutputFolder(this.props.getMappingsOutputFolder())
.withSnapshotCheckSkip(this.props.isSnapshotCheckSkip());
}
private String consumerName() {

View File

@@ -90,8 +90,17 @@ public class StubRunnerProperties {
*/
private String mappingsOutputFolder;
/**
* Pick where the stubs should come from
*/
private StubsMode stubsMode;
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one
*/
private boolean snapshotCheckSkip;
/**
* An enumeration stub modes.
*/
@@ -221,12 +230,21 @@ public class StubRunnerProperties {
this.stubsMode = stubsMode;
}
public boolean isSnapshotCheckSkip() {
return this.snapshotCheckSkip;
}
public void setSnapshotCheckSkip(boolean snapshotCheckSkip) {
this.snapshotCheckSkip = snapshotCheckSkip;
}
@Override public String toString() {
return "StubRunnerProperties{" + "minPort=" + this.minPort + ", maxPort=" + this.maxPort
+ ", repositoryRoot=" + this.repositoryRoot
+ ", ids=" + Arrays.toString(this.ids) + ", classifier='" + this.classifier + '\''
+ ", setStubsPerConsumer='" + this.stubsPerConsumer + "', consumerName='" + this.consumerName + '\''
+ ", stubsMode='" + this.stubsMode + '\''
+ ", snapshotCheckSkip='" + this.snapshotCheckSkip + '\''
+ '}';
}
}

View File

@@ -11,66 +11,148 @@ import org.springframework.util.ResourceUtils
class AetherStubDownloaderSpec extends Specification {
@Rule
HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode("simulation.json")
@Rule
HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode("simulation.json")
def 'Should throw an exception when artifact not found'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.LOCAL)
.build()
def 'Should throw an exception when artifact not found'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.LOCAL)
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("non.existing.group", "missing-artifact-id", "1.0-SNAPSHOT"))
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("non.existing.group", "missing-artifact-id", "1.0-SNAPSHOT"))
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("Exception occurred while trying to download a stub for group")
}
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("Exception occurred while trying to download a stub for group")
}
def 'Should throw an exception when a jar is in local m2 and not in remote repo'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.build()
def 'Should throw an exception when a jar is in local m2 and not in remote repo'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("The artifact was found in the local repository but you have explicitly stated that it should be downloaded from a remote one")
}
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("The artifact was found in the local repository but you have explicitly stated that it should be downloaded from a remote one")
}
@RestoreSystemProperties
def 'Should use local repository from settings.xml'() {
given:
File tempSettings = File.createTempFile("settings", ".xml")
def m2repoFolder = 'm2repo' + File.separator + 'repository'
tempSettings.text = '<settings><localRepository>' +
ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + m2repoFolder).getAbsolutePath() + '</localRepository></settings>'
System.setProperty("org.apache.maven.user-settings", tempSettings.getAbsolutePath())
RepositorySystemSession repositorySystemSession =
AetherFactories.newSession(AetherFactories.newRepositorySystem(), true);
@RestoreSystemProperties
def 'Should not throw an exception when a jar is in local m2 and not in remote repo and system property disabled snapshot check'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.build()
System.properties.setProperty("stubrunner.snapshot-check-skip", "true")
and:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.LOCAL)
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
and:
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(
new StubConfiguration("org.springframework.cloud.contract.verifier.stubs",
"bootService", "0.0.1-SNAPSHOT"))
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
jar != null
repositorySystemSession.getLocalRepository().getBasedir().getAbsolutePath().endsWith(m2repoFolder)
}
then:
jar != null
}
@RestoreSystemProperties
def 'Should throw an exception when a jar is in local m2 and not in remote repo and system property disabled takes precedence over env'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.build()
System.properties.setProperty("stubrunner.snapshot-check-skip", "false")
and:
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions) {
@Override
String getSkipSnapEnvProp() {
return "true"
}
}
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("The artifact was found in the local repository but you have explicitly stated that it should be downloaded from a remote one")
}
def 'Should not throw an exception when a jar is in local m2 and not in remote repo and env property disabled snapshot check'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions) {
@Override
String getSkipSnapEnvProp() {
return "true"
}
}
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
jar != null
}
def 'Should not throw an exception when a jar is in local m2 and not in remote repo and option disabled snapshot check'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("https://test.jfrog.io/test/libs-snapshot-local")
.withSnapshotCheckSkip(true)
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
jar != null
}
@RestoreSystemProperties
def 'Should use local repository from settings.xml'() {
given:
File tempSettings = File.createTempFile("settings", ".xml")
def m2repoFolder = 'm2repo' + File.separator + 'repository'
tempSettings.text = '<settings><localRepository>' +
ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + m2repoFolder).getAbsolutePath() + '</localRepository></settings>'
System.setProperty("org.apache.maven.user-settings", tempSettings.getAbsolutePath())
RepositorySystemSession repositorySystemSession =
AetherFactories.newSession(AetherFactories.newRepositorySystem(), true);
and:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.LOCAL)
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(
new StubConfiguration("org.springframework.cloud.contract.verifier.stubs",
"bootService", "0.0.1-SNAPSHOT"))
then:
jar != null
repositorySystemSession.getLocalRepository().getBasedir().getAbsolutePath().endsWith(m2repoFolder)
}
}

View File

@@ -143,7 +143,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
given:
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, "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"))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", true))
builder.withStubs("foo:bar:baz")
when:
StubRunnerOptions options = builder.build()
@@ -162,6 +162,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
options.stubsPerConsumer == true
options.consumerName == "consumer"
options.mappingsOutputFolder == "folder"
options.snapshotCheckSkip == true
}
def shouldNotPrintUsernameAndPassword() {
@@ -169,7 +170,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, "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"))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", true))
builder.withStubs("foo:bar:baz")
when:
String options = builder.build().toString()
@@ -196,6 +197,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
System.setProperty("stubrunner.proxy.host", "host")
System.setProperty("stubrunner.proxy.port", "4")
System.setProperty("stubrunner.mappings-output-folder", "folder")
System.setProperty("stubrunner.snapshot-check-skip", "true")
when:
StubRunnerOptions options = StubRunnerOptions.fromSystemProps()
then:
@@ -212,5 +214,6 @@ class StubRunnerOptionsBuilderSpec extends Specification {
options.stubsPerConsumer == true
options.consumerName == "consumer"
options.mappingsOutputFolder == "folder"
options.snapshotCheckSkip == true
}
}

View File

@@ -125,7 +125,7 @@ class ContractVerifierExtension {
/**
* Picks the mode in which stubs will be found and registered
*/
StubRunnerProperties.StubsMode stubsMode = StubRunnerProperties.StubsMode.CLASSPATH
StubRunnerProperties.StubsMode contractsMode = StubRunnerProperties.StubsMode.CLASSPATH
/**
* A package that contains all the base clases for generated tests. If your contract resides in a location
@@ -154,7 +154,13 @@ class ContractVerifierExtension {
* excluded from any operations. This is used out of the box when working with
* common repo with contracts.
*/
boolean excludeBuildFolders
boolean excludeBuildFolders = false
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one
*/
boolean contractsSnapshotCheckSkip = false
void contractDependency(@DelegatesTo(Dependency) Closure closure) {
closure.delegate = contractDependency

View File

@@ -65,7 +65,7 @@ class GradleContractsDownloader {
private boolean shouldDownloadContracts(ContractVerifierExtension extension) {
return [StubRunnerProperties.StubsMode.LOCAL, StubRunnerProperties.StubsMode.REMOTE].any {
it == extension.stubsMode } && (StringUtils.hasText(extension.contractDependency.artifactId) ||
it == extension.contractsMode } && (StringUtils.hasText(extension.contractDependency.artifactId) ||
StringUtils.hasText(extension.contractDependency.stringNotation))
}
@@ -79,9 +79,10 @@ class GradleContractsDownloader {
StubRunnerOptionsBuilder options = new StubRunnerOptionsBuilder()
.withOptions(StubRunnerOptions.fromSystemProps())
.withStubRepositoryRoot(extension.contractRepository.repositoryUrl)
.withStubsMode(extension.stubsMode)
.withStubsMode(extension.contractsMode)
.withUsername(extension.contractRepository.username)
.withPassword(extension.contractRepository.password)
.withSnapshotCheckSkip(extension.contractsSnapshotCheckSkip)
if (extension.contractRepository.proxyPort) {
options = options.withProxy(extension.contractRepository.proxyHost, extension.contractRepository.proxyPort)
}

View File

@@ -81,7 +81,7 @@ class GradleContractsDownloaderSpec extends Specification {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
stubsMode = StubRunnerProperties.StubsMode.REMOTE
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
@@ -109,7 +109,7 @@ class GradleContractsDownloaderSpec extends Specification {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
stubsMode = StubRunnerProperties.StubsMode.REMOTE
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
@@ -139,7 +139,7 @@ class GradleContractsDownloaderSpec extends Specification {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
stubsMode = StubRunnerProperties.StubsMode.REMOTE
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
@@ -174,7 +174,7 @@ class GradleContractsDownloaderSpec extends Specification {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
stubsMode = StubRunnerProperties.StubsMode.REMOTE
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")

View File

@@ -44,7 +44,7 @@ dependencies {
}
contracts {
stubsMode = "REMOTE"
contractsMode = "REMOTE"
baseClassForTests = 'org.springframework.cloud.contract.verifier.twitter.places.BaseMockMvcSpec'
basePackageForTests = 'contracts'
contractsDslDir = contractsDir

View File

@@ -116,7 +116,7 @@ configure(project(':fraudDetectionService')) {
}
contracts {
stubsMode = "REMOTE"
contractsMode = "REMOTE"
targetFramework = 'Spock'
testMode = 'JaxRsClient'
baseClassForTests = 'org.springframework.cloud.MvcSpec'

View File

@@ -69,7 +69,7 @@ configure([project(':fraudDetectionService'), project(':loanApplicationService')
}
contracts {
stubsMode = "REMOTE"
contractsMode = "REMOTE"
targetFramework = 'Spock'
testMode = 'MockMvc'
baseClassForTests = 'org.springframework.cloud.MvcSpec'

View File

@@ -86,7 +86,7 @@ configure([project(':fraudDetectionService'), project(':loanApplicationService')
// end::jar_setup[]
contracts {
stubsMode = "REMOTE"
contractsMode = "REMOTE"
// tag::target_framework[]
targetFramework = 'Spock'
// end::target_framework[]

View File

@@ -107,8 +107,8 @@ public class ConvertMojo extends AbstractMojo {
/**
* Picks the mode in which stubs will be found and registered
*/
@Parameter(property = "stubsMode", defaultValue = "CLASSPATH")
private StubRunnerProperties.StubsMode stubsMode;
@Parameter(property = "contractsMode", defaultValue = "CLASSPATH")
private StubRunnerProperties.StubsMode contractsMode;
/**
* If {@code true} then any file laying in a path that contains {@code build} or {@code target}
@@ -141,6 +141,13 @@ public class ConvertMojo extends AbstractMojo {
@Parameter(property = "contractsRepositoryProxyPort")
private Integer contractsRepositoryProxyPort;
/**
* If {@code true} then will not assert whether a stub / contract
* JAR was downloaded from local or remote location
*/
@Parameter(property = "contractsSnapshotCheckSkip", defaultValue = "false")
private boolean contractsSnapshotCheckSkip;
@Component(role = MavenResourcesFiltering.class, hint = "default")
private MavenResourcesFiltering mavenResourcesFiltering;
@@ -166,9 +173,11 @@ public class ConvertMojo extends AbstractMojo {
ContractVerifierConfigProperties config = new ContractVerifierConfigProperties();
config.setExcludeBuildFolders(this.excludeBuildFolders);
File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency,
this.contractsPath, this.contractsRepositoryUrl, this.stubsMode, getLog(),
this.contractsPath, this.contractsRepositoryUrl, this.contractsMode, getLog(),
this.aetherStubDownloaderFactory, this.repoSession, this.contractsRepositoryUsername,
this.contractsRepositoryPassword, this.contractsRepositoryProxyHost, this.contractsRepositoryProxyPort).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory);
this.contractsRepositoryPassword, this.contractsRepositoryProxyHost, this.contractsRepositoryProxyPort,
this.contractsSnapshotCheckSkip)
.downloadAndUnpackContractsIfRequired(config, this.contractsDirectory);
getLog().info("Directory with contract is present at [" + contractsDirectory + "]");
new CopyContracts(this.project, this.mavenSession, this.mavenResourcesFiltering, config)

View File

@@ -145,8 +145,8 @@ public class GenerateTestsMojo extends AbstractMojo {
/**
* Picks the mode in which stubs will be found and registered
*/
@Parameter(property = "stubsMode", defaultValue = "CLASSPATH")
private StubRunnerProperties.StubsMode stubsMode;
@Parameter(property = "contractsMode", defaultValue = "CLASSPATH")
private StubRunnerProperties.StubsMode contractsMode;
/**
* A package that contains all the base clases for generated tests. If your contract resides in a location
@@ -196,6 +196,13 @@ public class GenerateTestsMojo extends AbstractMojo {
@Parameter(property = "contractsRepositoryProxyPort")
private Integer contractsRepositoryProxyPort;
/**
* If {@code true} then will not assert whether a stub / contract
* JAR was downloaded from local or remote location
*/
@Parameter(property = "contractsSnapshotCheckSkip", defaultValue = "false")
private boolean contractsSnapshotCheckSkip;
private final AetherStubDownloaderFactory aetherStubDownloaderFactory;
@Inject
@@ -215,10 +222,11 @@ public class GenerateTestsMojo extends AbstractMojo {
final ContractVerifierConfigProperties config = new ContractVerifierConfigProperties();
// download contracts, unzip them and pass as output directory
File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency,
this.contractsPath, this.contractsRepositoryUrl, this.stubsMode, getLog(),
this.contractsPath, this.contractsRepositoryUrl, this.contractsMode, getLog(),
this.aetherStubDownloaderFactory, this.repoSession,
this.contractsRepositoryUsername, this.contractsRepositoryPassword,
this.contractsRepositoryProxyHost, this.contractsRepositoryProxyPort).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory);
this.contractsRepositoryProxyHost, this.contractsRepositoryProxyPort,
this.contractsSnapshotCheckSkip).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory);
getLog().info("Directory with contract is present at [" + contractsDirectory + "]");
setupConfig(config, contractsDirectory);
this.project.addTestCompileSourceRoot(this.generatedTestSourcesDir.getAbsolutePath());

View File

@@ -44,6 +44,7 @@ class MavenContractsDownloader {
private final String repositoryPassword;
private final String repositoryProxyHost;
private final Integer repositoryProxyPort;
private final boolean contractsSnapshotCheckSkip;
MavenContractsDownloader(MavenProject project, Dependency contractDependency,
String contractsPath, String contractsRepositoryUrl,
@@ -51,7 +52,7 @@ class MavenContractsDownloader {
AetherStubDownloaderFactory aetherStubDownloaderFactory,
RepositorySystemSession repoSession, String repositoryUsername,
String repositoryPassword, String repositoryProxyHost,
Integer repositoryProxyPort) {
Integer repositoryProxyPort, boolean contractsSnapshotCheckSkip) {
this.project = project;
this.contractDependency = contractDependency;
this.contractsPath = contractsPath;
@@ -65,6 +66,7 @@ class MavenContractsDownloader {
this.repositoryProxyHost = repositoryProxyHost;
this.repositoryProxyPort = repositoryProxyPort;
this.stubDownloaderBuilderProvider = new StubDownloaderBuilderProvider();
this.contractsSnapshotCheckSkip = contractsSnapshotCheckSkip;
}
File downloadAndUnpackContractsIfRequired(ContractVerifierConfigProperties config, File defaultContractsDir) {
@@ -139,7 +141,8 @@ class MavenContractsDownloader {
.withStubRepositoryRoot(this.contractsRepositoryUrl)
.withStubsMode(this.stubsMode)
.withUsername(this.repositoryUsername)
.withPassword(this.repositoryPassword);
.withPassword(this.repositoryPassword)
.withSnapshotCheckSkip(this.contractsSnapshotCheckSkip);
if (this.repositoryProxyPort != null) {
builder.withProxy(this.repositoryProxyHost, this.repositoryProxyPort);
}

View File

@@ -32,7 +32,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<stubsMode>REMOTE</stubsMode>
<contractsMode>REMOTE</contractsMode>
<contractsRepositoryUrl>http://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
<contractDependency>
<groupId>com.example.standalone</groupId>

View File

@@ -31,7 +31,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<stubsMode>REMOTE</stubsMode>
<contractsMode>REMOTE</contractsMode>
<contractDependency>
<groupId>com.example</groupId>
<artifactId>contracts</artifactId>

View File

@@ -31,7 +31,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<stubsMode>REMOTE</stubsMode>
<contractsMode>REMOTE</contractsMode>
<contractsPath>com/example/server</contractsPath>
<contractDependency>
<groupId>com.example</groupId>