Added stub-runner, stub-runner-spring, stub-runner-junit
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
language: java
|
||||
sudo: false
|
||||
|
||||
before_install:
|
||||
- "export JAVA_OPTS='-Xmx1024m -XX:MaxPermSize=384m'"
|
||||
- "rm -rf $HOME/.m2/repository/io/codearte/accurest/stubs"
|
||||
- "mkdir $HOME/.m2/repository/io/codearte/accurest/ --parents"
|
||||
- "cp -r stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs $HOME/.m2/repository/io/codearte/accurest/"
|
||||
|
||||
jdk:
|
||||
- oraclejdk7
|
||||
- openjdk7
|
||||
@@ -12,4 +18,4 @@ cache:
|
||||
- $HOME/.m2
|
||||
|
||||
install: ./gradlew assemble
|
||||
script: ./gradlew clean check funcTest --stacktrace --info --continue
|
||||
script: ./gradlew clean check funcTest --stacktrace --info --continue --parallel
|
||||
|
||||
14
README.md
14
README.md
@@ -19,3 +19,17 @@ For more information please go to the [Wiki](https://github.com/Codearte/accures
|
||||
### Wiremock
|
||||
|
||||
In order to use Accurest with Wiremock you have to have __Wiremock in version at least 2.0.0-beta__ . Of course the higher the better :)
|
||||
|
||||
## Additional projects
|
||||
|
||||
### Stub Runner
|
||||
|
||||
Allows you to download WireMock stubs from the provided Maven repository and runs them in WireMock servers.
|
||||
|
||||
### Stub Runner JUnit
|
||||
|
||||
Stub Runner with JUnit rules
|
||||
|
||||
### Stub Runner Spring
|
||||
|
||||
Spring Configuration that automatically starts stubs upon Spring Context build up
|
||||
@@ -1,3 +1,8 @@
|
||||
include "accurest-core", "accurest-gradle-plugin", 'accurest-converters', 'accurest-testing-utils'
|
||||
include ':stub-runner:stub-runner'
|
||||
include ':stub-runner:stub-runner-spring'
|
||||
include ':stub-runner:stub-runner-junit'
|
||||
rootProject.name = "accurest"
|
||||
|
||||
//to prevent StackOverflow in Sonar
|
||||
project(":stub-runner").name = "stub-runner-root"
|
||||
42
stub-runner/stub-runner-junit/README.md
Normal file
42
stub-runner/stub-runner-junit/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
stub-runner-junit
|
||||
=================
|
||||
|
||||
Contains a JUnit Rule for Stub Runner.
|
||||
|
||||
Example of usage:
|
||||
|
||||
```
|
||||
class AccurestRuleSpec extends Specification {
|
||||
|
||||
@ClassRule @Shared AccurestRule rule = new AccurestRule()
|
||||
.repoRoot(AccurestRuleSpec.getResource("/m2repo").path)
|
||||
.downloadStub("io.codearte.accurest.stubs", "loanIssuance")
|
||||
.downloadStub("io.codearte.accurest.stubs:fraudDetectionServer")
|
||||
|
||||
def 'should start WireMock servers'() {
|
||||
expect:
|
||||
rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') == rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
|
||||
rule.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can set the default value of the Maven repository by means of system property:
|
||||
|
||||
```
|
||||
-Dstubrunner.stubs.repository.root=http://your.maven.repo.com
|
||||
```
|
||||
|
||||
The list of configurable properties contains:
|
||||
|
||||
| Name | Default value | Description |
|
||||
|------|---------------|-------------|
|
||||
| stubrunner.port.range.min | 10000 | Minimal value of a port for a WireMock server |
|
||||
| stubrunner.port.range.max | 15000 | Maximum value of a port for a WireMock server |
|
||||
| stubrunner.stubs.repository.root | | Address to your M2 repo (will point to local M2 repo if none is provided) |
|
||||
| stubrunner.stubs.repository.root | | Address to your M2 repo (will point to local M2 repo if none is provided) |
|
||||
| stubrunner.work-offline | false | Should try to connect to any repo to download stubs (especially good if there's no internet) |
|
||||
| stubrunner.stubs | | Default comma separated list of stubs to download |
|
||||
|
||||
15
stub-runner/stub-runner-junit/build.gradle
Normal file
15
stub-runner/stub-runner-junit/build.gradle
Normal file
@@ -0,0 +1,15 @@
|
||||
description = 'JUnit rule for stub-runner'
|
||||
|
||||
dependencies {
|
||||
compile project(':stub-runner-root:stub-runner')
|
||||
|
||||
compile localGroovy()
|
||||
compile 'junit:junit:4.12'
|
||||
|
||||
testCompile('org.spockframework:spock-core:1.0-groovy-2.3') {
|
||||
exclude(group: 'org.codehaus.groovy')
|
||||
}
|
||||
testCompile 'cglib:cglib-nodep:2.2'
|
||||
testCompile 'org.objenesis:objenesis:2.1'
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.3'
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package io.codearte.accurest.stubrunner.junit;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import io.codearte.accurest.stubrunner.BatchStubRunner;
|
||||
import io.codearte.accurest.stubrunner.BatchStubRunnerFactory;
|
||||
import io.codearte.accurest.stubrunner.StubConfiguration;
|
||||
import io.codearte.accurest.stubrunner.StubFinder;
|
||||
import io.codearte.accurest.stubrunner.StubRunnerOptions;
|
||||
import io.codearte.accurest.stubrunner.util.StringUtils;
|
||||
import io.codearte.accurest.stubrunner.util.StubsParser;
|
||||
|
||||
/**
|
||||
* JUnit class rule that allows you to download the provided stubs.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class AccurestRule implements TestRule, StubFinder {
|
||||
private static final String DELIMITER = ":";
|
||||
|
||||
private Set<String> stubs = new HashSet<String>();
|
||||
private StubRunnerOptions stubRunnerOptions = defaultStubRunnerOptions();
|
||||
private BatchStubRunner stubFinder;
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
before();
|
||||
base.evaluate();
|
||||
stubFinder.close();
|
||||
}
|
||||
|
||||
private void before() {
|
||||
Collection<StubConfiguration> dependencies = StubsParser.fromString(stubs, stubRunnerOptions.getStubsClassifier());
|
||||
stubFinder = new BatchStubRunnerFactory(stubRunnerOptions, dependencies)
|
||||
.buildBatchStubRunner();
|
||||
stubFinder.runStubs();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private StubRunnerOptions defaultStubRunnerOptions() {
|
||||
Integer minPort = Integer.valueOf(System.getProperty("stubrunner.port.range.min", "10000"));
|
||||
Integer maxPort = Integer.valueOf(System.getProperty("stubrunner.port.range.max", "15000"));
|
||||
String repoRoot = System.getProperty("stubrunner.stubs.repository.root", "");
|
||||
String stubSuffix = System.getProperty("stubrunner.stubs.classifier", "stubs");
|
||||
Boolean workOffline = Boolean.parseBoolean(System.getProperty("stubrunner.work-offline", "false"));
|
||||
String stubsToDownload = System.getProperty("stubrunner.stubs", "");
|
||||
if (StringUtils.hasText(stubsToDownload)) {
|
||||
Collections.addAll(stubs, stubsToDownload.split(","));
|
||||
}
|
||||
return new StubRunnerOptions(minPort, maxPort, repoRoot, workOffline, stubSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override all options
|
||||
*
|
||||
* @see StubRunnerOptions
|
||||
*/
|
||||
public AccurestRule options(StubRunnerOptions stubRunnerOptions) {
|
||||
this.stubRunnerOptions = stubRunnerOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Min value of port for WireMock server
|
||||
*/
|
||||
public AccurestRule minPort(int minPort) {
|
||||
this.stubRunnerOptions.setMinPortValue(minPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max value of port for WireMock server
|
||||
*/
|
||||
public AccurestRule maxPort(int maxPort) {
|
||||
this.stubRunnerOptions.setMaxPortValue(maxPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* String URI of repository containing stubs
|
||||
*/
|
||||
public AccurestRule repoRoot(String repoRoot) {
|
||||
this.stubRunnerOptions.setStubRepositoryRoot(repoRoot);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should download stubs or use only the local repository
|
||||
*/
|
||||
public AccurestRule workOffline(boolean workOffline) {
|
||||
this.stubRunnerOptions.setWorkOffline(workOffline);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group Id, artifact Id and classifier of a single stub to download
|
||||
*/
|
||||
public AccurestRule downloadStub(String groupId, String artifactId, String classifier) {
|
||||
stubs.add(groupId + DELIMITER + artifactId + DELIMITER + classifier);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group Id, artifact Id of a single stub to download. Default classifier will be picked.
|
||||
*/
|
||||
public AccurestRule downloadStub(String groupId, String artifactId) {
|
||||
stubs.add(groupId + DELIMITER + artifactId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ivy notation of a single stub to download.
|
||||
*/
|
||||
public AccurestRule downloadStub(String ivyNotation) {
|
||||
stubs.add(ivyNotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs to download in Ivy notations
|
||||
*/
|
||||
public AccurestRule downloadStubs(String... ivyNotations) {
|
||||
stubs.addAll(Arrays.asList(ivyNotations));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs to download in Ivy notations
|
||||
*/
|
||||
public AccurestRule downloadStubs(List<String> ivyNotations) {
|
||||
stubs.addAll(ivyNotations);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findStubUrl(String groupId, String artifactId) {
|
||||
return stubFinder.findStubUrl(groupId, artifactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findStubUrl(String ivyNotation) {
|
||||
return stubFinder.findStubUrl(ivyNotation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.codearte.accurest.stubrunner.junit
|
||||
|
||||
import org.junit.ClassRule
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class AccurestRuleSpec extends Specification {
|
||||
|
||||
@ClassRule @Shared AccurestRule rule = new AccurestRule()
|
||||
.repoRoot(AccurestRuleSpec.getResource("/m2repo").path)
|
||||
.downloadStub("io.codearte.accurest.stubs", "loanIssuance")
|
||||
.downloadStub("io.codearte.accurest.stubs:fraudDetectionServer")
|
||||
|
||||
def 'should start WireMock servers'() {
|
||||
expect: 'WireMocks are running'
|
||||
rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') == rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
|
||||
rule.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
|
||||
and: 'Stubs were registered'
|
||||
"${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
|
||||
"${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.codearte.accurest.stubrunner.junit
|
||||
|
||||
import org.junit.ClassRule
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
import spock.util.environment.RestoreSystemProperties
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
@RestoreSystemProperties
|
||||
class AccurestRuleSysPropsSpec extends Specification {
|
||||
|
||||
static {
|
||||
System.properties.setProperty("stubrunner.stubs.repository.root", AccurestRuleSysPropsSpec.getResource("/m2repo").path)
|
||||
System.properties.setProperty("stubrunner.stubs.classifier", 'classifier that will be overridden')
|
||||
}
|
||||
|
||||
@ClassRule @Shared AccurestRule rule = new AccurestRule()
|
||||
.downloadStub("io.codearte.accurest.stubs", "loanIssuance", "stubs")
|
||||
.downloadStub("io.codearte.accurest.stubs:fraudDetectionServer:stubs")
|
||||
|
||||
def 'should start WireMock servers'() {
|
||||
expect: 'WireMocks are running'
|
||||
rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') != null
|
||||
rule.findStubUrl('loanIssuance') == rule.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
|
||||
rule.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
|
||||
and: 'Stubs were registered'
|
||||
"${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
|
||||
"${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
|
||||
}
|
||||
}
|
||||
14
stub-runner/stub-runner-junit/src/test/resources/logback.xml
Normal file
14
stub-runner/stub-runner-junit/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<versioning>
|
||||
<snapshot>
|
||||
<localCopy>true</localCopy>
|
||||
</snapshot>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<versioning>
|
||||
<versions>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</versions>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<versioning>
|
||||
<snapshot>
|
||||
<localCopy>true</localCopy>
|
||||
</snapshot>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<versioning>
|
||||
<versions>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</versions>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
32
stub-runner/stub-runner-spring/README.md
Normal file
32
stub-runner/stub-runner-spring/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
stub-runner-spring
|
||||
=======================
|
||||
|
||||
Sets up Spring configuration of the Stub Runner project.
|
||||
|
||||
By providing a list of stubs inside your configuration file the Stub Runner automatically downloads
|
||||
and registers in WireMock the selected stubs.
|
||||
|
||||
If you want to find the URL of your stubbed dependency you can autowire the `StubFinder` interface and use
|
||||
its methods as presented below:
|
||||
|
||||
```
|
||||
@Autowired StubFinder stubFinder
|
||||
|
||||
def 'should start WireMock servers'() {
|
||||
expect: 'WireMocks are running'
|
||||
stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
|
||||
stubFinder.findStubUrl('loanIssuance') != null
|
||||
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
|
||||
stubFinder.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
|
||||
and: 'Stubs were registered'
|
||||
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
|
||||
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
|
||||
}
|
||||
```
|
||||
|
||||
for the following configuration file:
|
||||
|
||||
```
|
||||
stubrunner.stubs.repository.root: classpath:m2repo
|
||||
stubrunner.stubs: io.codearte.accurest.stubs:loanIssuance,io.codearte.accurest.stubs:fraudDetectionServer
|
||||
```
|
||||
18
stub-runner/stub-runner-spring/build.gradle
Normal file
18
stub-runner/stub-runner-spring/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
description = 'Spring configuration for stub-runner'
|
||||
|
||||
dependencies {
|
||||
compile project(':stub-runner-root:stub-runner')
|
||||
|
||||
compile localGroovy()
|
||||
compile 'org.springframework:spring-context:[3.0.0.RELEASE,)'
|
||||
|
||||
testCompile('org.spockframework:spock-core:1.0-groovy-2.3') {
|
||||
exclude(group: 'org.codehaus.groovy')
|
||||
}
|
||||
testCompile 'cglib:cglib-nodep:2.2'
|
||||
testCompile 'org.objenesis:objenesis:2.1'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter:1.3.3.RELEASE'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
|
||||
testCompile 'org.spockframework:spock-spring:1.0-groovy-2.3'
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.3'
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.codearte.accurest.stubrunner.spring;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import io.codearte.accurest.stubrunner.BatchStubRunner;
|
||||
import io.codearte.accurest.stubrunner.BatchStubRunnerFactory;
|
||||
import io.codearte.accurest.stubrunner.StubConfiguration;
|
||||
import io.codearte.accurest.stubrunner.StubRunner;
|
||||
import io.codearte.accurest.stubrunner.StubRunnerOptions;
|
||||
import io.codearte.accurest.stubrunner.StubRunning;
|
||||
import io.codearte.accurest.stubrunner.util.StubsParser;
|
||||
|
||||
/**
|
||||
* Configuration that initializes a {@link BatchStubRunner} that runs {@link StubRunner} instance for each stub
|
||||
*/
|
||||
@Configuration
|
||||
public class StubRunnerConfiguration {
|
||||
|
||||
/**
|
||||
* Bean that initializes stub runners, runs them and on shutdown closes them. Upon its instantiation
|
||||
* JAR with stubs is downloaded and unpacked to a temporary folder and WireMock server are started
|
||||
* for each of those stubs
|
||||
*
|
||||
* @param minPortValue min port value of the WireMock instance for stubs
|
||||
* @param maxPortValue max port value of the WireMock instance for stubs
|
||||
* @param stubRepositoryRoot root URL from where the JAR with stub mappings will be downloaded
|
||||
* @param stubsSuffix classifier for the jar containing stubs
|
||||
* @param workOffline forces offline work
|
||||
* @param stubs comma separated list of stubs presented in Ivy notation
|
||||
*/
|
||||
@Bean(initMethod = "runStubs", destroyMethod = "close")
|
||||
public StubRunning batchStubRunner(
|
||||
@Value("${stubrunner.port.range.min:10000}") Integer minPortValue,
|
||||
@Value("${stubrunner.port.range.max:15000}") Integer maxPortValue,
|
||||
@Value("${stubrunner.stubs.repository.root:}") Resource stubRepositoryRoot,
|
||||
@Value("${stubrunner.stubs.classifier:stubs}") String stubsSuffix,
|
||||
@Value("${stubrunner.work-offline:false}") boolean workOffline,
|
||||
@Value("${stubrunner.stubs:}") String stubs) throws IOException {
|
||||
StubRunnerOptions stubRunnerOptions = new StubRunnerOptions(minPortValue,
|
||||
maxPortValue, uriStringOrEmpty(stubRepositoryRoot),
|
||||
stubRepositoryRoot == null || workOffline, stubsSuffix);
|
||||
Set<StubConfiguration> dependencies = StubsParser.fromString(stubs, stubsSuffix);
|
||||
return new BatchStubRunnerFactory(stubRunnerOptions, dependencies)
|
||||
.buildBatchStubRunner();
|
||||
}
|
||||
|
||||
private String uriStringOrEmpty(Resource stubRepositoryRoot) throws IOException {
|
||||
return stubRepositoryRoot != null ? stubRepositoryRoot.getURI().toString() : "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.codearte.accurest.stubrunner.spring
|
||||
|
||||
import io.codearte.accurest.stubrunner.StubFinder
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
import org.springframework.boot.test.SpringApplicationContextLoader
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Import
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
@ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader)
|
||||
class StubRunnerConfigurationSpec extends Specification {
|
||||
|
||||
@Autowired StubFinder stubFinder
|
||||
|
||||
def 'should start WireMock servers'() {
|
||||
expect: 'WireMocks are running'
|
||||
stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance') != null
|
||||
stubFinder.findStubUrl('loanIssuance') != null
|
||||
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('io.codearte.accurest.stubs', 'loanIssuance')
|
||||
stubFinder.findStubUrl('io.codearte.accurest.stubs:fraudDetectionServer') != null
|
||||
and: 'Stubs were registered'
|
||||
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
|
||||
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(StubRunnerConfiguration)
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
stubrunner.stubs.repository.root: classpath:m2repo
|
||||
stubrunner.stubs: io.codearte.accurest.stubs:loanIssuance,io.codearte.accurest.stubs:fraudDetectionServer
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<versioning>
|
||||
<snapshot>
|
||||
<localCopy>true</localCopy>
|
||||
</snapshot>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>fraudDetectionServer-stubs</artifactId>
|
||||
<versioning>
|
||||
<versions>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</versions>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<versioning>
|
||||
<snapshot>
|
||||
<localCopy>true</localCopy>
|
||||
</snapshot>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>io.codearte.accurest.stubs</groupId>
|
||||
<artifactId>loanIssuance-stubs</artifactId>
|
||||
<versioning>
|
||||
<versions>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</versions>
|
||||
<lastUpdated>20160326150924</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
88
stub-runner/stub-runner/README.md
Normal file
88
stub-runner/stub-runner/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
Stub-runner
|
||||
===========
|
||||
|
||||
Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of
|
||||
[Consumer Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html).
|
||||
|
||||
### Running stubs
|
||||
|
||||
#### Running using main app
|
||||
|
||||
You can set the following options to the main class:
|
||||
|
||||
```
|
||||
java -jar stub-runner.jar [options...]
|
||||
-maxp (--maxPort) N : Maximum port value to be assigned to the
|
||||
Wiremock instance. Defaults to 15000
|
||||
(default: 15000)
|
||||
-minp (--minPort) N : Minimal port value to be assigned to the
|
||||
Wiremock instance. Defaults to 10000
|
||||
(default: 10000)
|
||||
-s (--stubs) VAL : Comma separated list of Ivy representation of
|
||||
jars with stubs. Eg. groupid:artifactid1,group
|
||||
id2:artifactid2:classifier
|
||||
-sr (--stubRepositoryRoot) VAL : Location of a Jar containing server where you
|
||||
keep your stubs (e.g. http://nexus.net/content
|
||||
/repositories/repository)
|
||||
-ss (--stubsSuffix) VAL : Suffix for the jar containing stubs (e.g.
|
||||
'stubs' if the stub jar would have a 'stubs'
|
||||
classifier for stubs: foobar-stubs ).
|
||||
Defaults to 'stubs' (default: stubs)
|
||||
-wo (--workOffline) : Switch to work offline. Defaults to 'false'
|
||||
(default: false)
|
||||
|
||||
```
|
||||
|
||||
### Stub runner configuration
|
||||
|
||||
You can configure the stub runner by either passing the full arguments list with the `-Pargs` like this:
|
||||
|
||||
```
|
||||
./gradlew stub-runner-root:stub-runner:run -Pargs="-c pl -minp 10000 -maxp 10005 -s a:b:c,d:e,f:g:h"
|
||||
```
|
||||
|
||||
or each parameter separately with a `-P` prefix and without the hyphen `-` in the name of the param
|
||||
|
||||
```
|
||||
./gradlew stub-runner-root:stub-runner:run -Pc=pl -Pminp=10000 -Pmaxp=10005
|
||||
```
|
||||
|
||||
### Defining collaborators' stubs
|
||||
|
||||
You can define global stubs under folder corresponding to groupid/artifactid of your collaborator
|
||||
|
||||
```
|
||||
com/ofg/foo
|
||||
```
|
||||
|
||||
By default stub definitions are stored in `mappings` directory inside stub repository.
|
||||
|
||||
#### Stubbing collaborators
|
||||
|
||||
For each collaborator defined in project metadata all collaborator mappings (stubs) available in repository are loaded.
|
||||
Stubs are defined in JSON documents, whose syntax is defined in [WireMock documentation](http://wiremock.org/stubbing.html)
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/ping"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "pong",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Stub definitions are stored in stub repository under the same path as collaborator fully qualified name.
|
||||
Paths (as long it's inside the directory mentioned above) and names of documents containing stub definitions not play any
|
||||
other role than describing stubs' role / purpose.
|
||||
|
||||
#### Viewing registered mappings
|
||||
|
||||
Every stubbed collaborator exposes list of defined mappings under `__/admin/` endpoint.
|
||||
73
stub-runner/stub-runner/build.gradle
Normal file
73
stub-runner/stub-runner/build.gradle
Normal file
@@ -0,0 +1,73 @@
|
||||
description = 'Runs stubs for service collaborators'
|
||||
|
||||
apply plugin: 'application'
|
||||
mainClassName = 'io.codearte.accurest.stubrunner.StubRunnerMain'
|
||||
|
||||
dependencies {
|
||||
compile 'org.apache.ivy:ivy:2.4.0'
|
||||
compile localGroovy()
|
||||
compile "com.github.tomakehurst:wiremock:$wiremockVersion"
|
||||
compile 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
compile 'args4j:args4j:2.32'
|
||||
compile 'com.nurkiewicz.asyncretry:asyncretry-jdk7:0.0.6'
|
||||
|
||||
testCompile('org.spockframework:spock-core:1.0-groovy-2.3') {
|
||||
exclude(group: 'org.codehaus.groovy')
|
||||
}
|
||||
testCompile 'cglib:cglib-nodep:2.2'
|
||||
testCompile 'org.objenesis:objenesis:2.1'
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.3'
|
||||
}
|
||||
|
||||
ext {
|
||||
stubRepositoryRoot = getPropertyByEither('sr', 'stubRepositoryRoot')
|
||||
stubsSuffix = getPropertyByEither('ss', 'stubsSuffix')
|
||||
minPortValue = getPropertyByEither('minp', 'minPort')
|
||||
maxPortValue = getPropertyByEither('maxp', 'maxPort')
|
||||
skipLocalRepo = getPropertyByEither('wo', 'workOffline')
|
||||
stubs = getPropertyByEither('s', 'stubs')
|
||||
arguments = project.hasProperty('args') ? project.property('args') : []
|
||||
/* example of args:
|
||||
'-minp 10000 -maxp 10005 -s ""groupid:artifactid1,groupid2:artifactid2" -sr http://dl.bintray.com/somelink/micro'
|
||||
*/
|
||||
}
|
||||
|
||||
Object getPropertyByEither(String paramName1, String paramName2, Object defaultValue = null) {
|
||||
return project.hasProperty(paramName1) ? project.property(paramName1) :
|
||||
project.hasProperty(paramName2) ? project.property(paramName2) : defaultValue
|
||||
}
|
||||
|
||||
run {
|
||||
main = 'io.codearte.accurest.stubrunner.StubRunnerMain'
|
||||
List argumentList
|
||||
if (arguments) {
|
||||
argumentList = (arguments.split(' ') as List).findAll { it != null }
|
||||
} else {
|
||||
argumentList = parseArguments()
|
||||
}
|
||||
args = argumentList
|
||||
if (args) {
|
||||
println "Running task with args $args"
|
||||
}
|
||||
}
|
||||
|
||||
List parseArguments() {
|
||||
println "Trying to parse the project arguments"
|
||||
List arguments = []
|
||||
appendToListIfNotNull(stubRepositoryRoot, '-sr', arguments)
|
||||
appendToListIfNotNull(stubsSuffix, '-ss', arguments)
|
||||
appendToListIfNotNull(minPortValue, '-minp', arguments)
|
||||
appendToListIfNotNull(maxPortValue, '-maxp', arguments)
|
||||
appendToListIfNotNull(skipLocalRepo, '-wo', arguments)
|
||||
appendToListIfNotNull(stubs, '-s', arguments)
|
||||
return arguments
|
||||
}
|
||||
|
||||
Object appendToListIfNotNull(String argument, String prefix, List list) {
|
||||
if (argument != null) {
|
||||
list << "$prefix"
|
||||
if (!argument.isAllWhitespace()) {
|
||||
list << argument
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Arguments passed to the {@link StubRunner} application
|
||||
*
|
||||
* @see StubRunner
|
||||
*/
|
||||
@CompileStatic
|
||||
@ToString(includeNames = true)
|
||||
@PackageScope
|
||||
class Arguments {
|
||||
final StubRunnerOptions stubRunnerOptions
|
||||
final String context
|
||||
final String repositoryPath
|
||||
final StubConfiguration stub
|
||||
|
||||
Arguments(StubRunnerOptions stubRunnerOptions, String repositoryPath = "", StubConfiguration stub = null) {
|
||||
this.stubRunnerOptions = stubRunnerOptions
|
||||
this.context = context
|
||||
this.repositoryPath = repositoryPath
|
||||
this.stub = stub
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
/**
|
||||
* Tries to execute a closure with an available port from the given range
|
||||
*/
|
||||
@CompileStatic
|
||||
@Slf4j
|
||||
@PackageScope
|
||||
class AvailablePortScanner {
|
||||
|
||||
private static final int MAX_RETRY_COUNT = 1000
|
||||
|
||||
private final int minPortNumber
|
||||
private final int maxPortNumber
|
||||
private final int maxRetryCount
|
||||
|
||||
AvailablePortScanner(int minPortNumber, int maxPortNumber, int maxRetryCount = MAX_RETRY_COUNT) {
|
||||
checkPortRanges(minPortNumber, maxPortNumber)
|
||||
this.minPortNumber = minPortNumber
|
||||
this.maxPortNumber = maxPortNumber
|
||||
this.maxRetryCount = maxRetryCount
|
||||
}
|
||||
|
||||
private void checkPortRanges(int minPortNumber, int maxPortNumber) {
|
||||
if (minPortNumber >= maxPortNumber) {
|
||||
throw new InvalidPortRange(minPortNumber, maxPortNumber)
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T tryToExecuteWithFreePort(Closure<T> closure) {
|
||||
for (i in (1..maxRetryCount)) {
|
||||
try {
|
||||
int numberOfPortsToBind = maxPortNumber - minPortNumber
|
||||
int portToScan = new Random().nextInt(numberOfPortsToBind) + minPortNumber
|
||||
checkIfPortIsAvailable(portToScan)
|
||||
return executeLogicForAvailablePort(portToScan, closure)
|
||||
} catch (BindException exception) {
|
||||
log.debug("Failed to execute closure (try: $i/$maxRetryCount)", exception)
|
||||
}
|
||||
}
|
||||
throw new NoPortAvailableException(minPortNumber, maxPortNumber)
|
||||
}
|
||||
|
||||
private <T> T executeLogicForAvailablePort(int portToScan, Closure<T> closure) {
|
||||
log.debug("Trying to execute closure with port [$portToScan]")
|
||||
return closure(portToScan)
|
||||
}
|
||||
|
||||
private void checkIfPortIsAvailable(int portToScan) {
|
||||
ServerSocket socket = null
|
||||
try {
|
||||
socket = new ServerSocket(portToScan)
|
||||
} finally {
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
static class NoPortAvailableException extends RuntimeException {
|
||||
protected NoPortAvailableException(int lowerBound, int upperBound) {
|
||||
super("Could not find available port in range $lowerBound:$upperBound")
|
||||
}
|
||||
}
|
||||
|
||||
static class InvalidPortRange extends RuntimeException {
|
||||
protected InvalidPortRange(int lowerBound, int upperBound) {
|
||||
super("Invalid bounds exceptions, min port [$lowerBound] is greater or equal to max port [$upperBound]")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
/**
|
||||
* Manages lifecycle of multiple {@link StubRunner} instances.
|
||||
*
|
||||
* @see StubRunner
|
||||
*/
|
||||
@CompileStatic
|
||||
class BatchStubRunner implements StubRunning {
|
||||
|
||||
private final Iterable<StubRunner> stubRunners
|
||||
|
||||
BatchStubRunner(Iterable<StubRunner> stubRunners) {
|
||||
this.stubRunners = stubRunners
|
||||
}
|
||||
|
||||
@Override
|
||||
RunningStubs runStubs() {
|
||||
Map<StubConfiguration, Integer> appsAndPorts = stubRunners.inject([:]) { Map<StubConfiguration, Integer> acc, StubRunner value ->
|
||||
acc.putAll(value.runStubs().namesAndPorts)
|
||||
return acc
|
||||
} as Map<StubConfiguration, Integer>
|
||||
return new RunningStubs(appsAndPorts)
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String groupId, String artifactId) {
|
||||
return stubRunners.findResult(null) { StubRunner stubRunner ->
|
||||
return stubRunner.findStubUrl(groupId, artifactId)
|
||||
} as URL
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String ivyNotation) {
|
||||
String[] splitString = ivyNotation.split(":")
|
||||
if (splitString.length > 3) {
|
||||
throw new IllegalArgumentException("$ivyNotation is invalid")
|
||||
} else if (splitString.length == 2) {
|
||||
return findStubUrl(splitString[0], splitString[1])
|
||||
}
|
||||
return findStubUrl(null, splitString[0])
|
||||
}
|
||||
|
||||
@Override
|
||||
void close() throws IOException {
|
||||
stubRunners.each {
|
||||
it.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
/**
|
||||
* Manages lifecycle of multiple {@link StubRunner} instances.
|
||||
*
|
||||
* @see StubRunner
|
||||
* @see BatchStubRunner
|
||||
*/
|
||||
@CompileStatic
|
||||
class BatchStubRunnerFactory {
|
||||
|
||||
private final StubRunnerOptions stubRunnerOptions
|
||||
private final Collection<StubConfiguration> dependencies
|
||||
|
||||
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection<StubConfiguration> dependencies) {
|
||||
this.stubRunnerOptions = stubRunnerOptions
|
||||
this.dependencies = dependencies
|
||||
}
|
||||
|
||||
BatchStubRunner buildBatchStubRunner() {
|
||||
StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, dependencies)
|
||||
return new BatchStubRunner(stubRunnerFactory.createStubsFromServiceConfiguration())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Represents a single JSON file that was found in the folder with
|
||||
* potential WireMock stubs
|
||||
*/
|
||||
@CompileStatic
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false)
|
||||
@PackageScope
|
||||
class MappingDescriptor {
|
||||
final File descriptor
|
||||
|
||||
MappingDescriptor(File mappingDescriptor) {
|
||||
this.descriptor = mappingDescriptor
|
||||
}
|
||||
|
||||
StubMapping getMapping() {
|
||||
return StubMapping.buildFrom(descriptor.getText('UTF-8'))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
/**
|
||||
* Structure representing executed stubs. Contains the configuration of each stub
|
||||
* together with the port on which its executed.
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@CompileStatic
|
||||
class RunningStubs {
|
||||
final Map<StubConfiguration, Integer> namesAndPorts
|
||||
|
||||
RunningStubs(Map<StubConfiguration, Integer> map) {
|
||||
this.namesAndPorts = map
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return namesAndPorts.collect {
|
||||
"Stub [${it.key.toColonSeparatedDependencyNotation()}] is running on port [${it.value}]"
|
||||
}.join("\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import io.codearte.accurest.stubrunner.util.StringUtils
|
||||
|
||||
/**
|
||||
* Represents a configuration of a single stub. The stub can be described
|
||||
* by groupId:artifactId:classifier notation
|
||||
*/
|
||||
@CompileStatic
|
||||
@EqualsAndHashCode
|
||||
public class StubConfiguration {
|
||||
private static final String STUB_COLON_DELIMITER = ":"
|
||||
|
||||
final String groupId
|
||||
final String artifactId
|
||||
final String classifier
|
||||
|
||||
public StubConfiguration(String groupId, String artifactId, String classifier) {
|
||||
this.groupId = groupId
|
||||
this.artifactId = artifactId
|
||||
this.classifier = classifier
|
||||
}
|
||||
|
||||
public StubConfiguration(String stubPath, String defaultClassifier = "stubs") {
|
||||
String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER, defaultClassifier)
|
||||
this.groupId = parsedPath[0]
|
||||
this.artifactId = parsedPath[1]
|
||||
this.classifier = parsedPath[2]
|
||||
}
|
||||
|
||||
private List<String> parsedPathEmptyByDefault(String path, String delimiter, String defaultClassifier) {
|
||||
String[] splitPath = path.split(delimiter)
|
||||
String stubsGroupId = ""
|
||||
String stubsArtifactId = ""
|
||||
String stubsClassifier = ""
|
||||
if (splitPath.length >= 2) {
|
||||
stubsGroupId = splitPath[0]
|
||||
stubsArtifactId = splitPath[1]
|
||||
stubsClassifier = splitPath.length == 3 ? splitPath[2] : defaultClassifier
|
||||
}
|
||||
return [stubsGroupId, stubsArtifactId, stubsClassifier]
|
||||
}
|
||||
|
||||
private boolean isDefined() {
|
||||
return StringUtils.hasText(groupId) && StringUtils.hasText(this.artifactId)
|
||||
}
|
||||
|
||||
boolean hasClassifier() {
|
||||
return StringUtils.hasText(classifier)
|
||||
}
|
||||
|
||||
public String toColonSeparatedDependencyNotation() {
|
||||
if(!isDefined()) {
|
||||
return ""
|
||||
}
|
||||
return [groupId, artifactId, classifier].join(STUB_COLON_DELIMITER)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.grape.Grape
|
||||
import groovy.util.logging.Slf4j
|
||||
import io.codearte.accurest.stubrunner.util.ZipCategory
|
||||
|
||||
import static java.nio.file.Files.createTempDirectory
|
||||
|
||||
/**
|
||||
* Downloads stubs from an external repository and unpacks them locally
|
||||
*/
|
||||
@Slf4j
|
||||
class StubDownloader {
|
||||
|
||||
private static final String LATEST_MODULE = '*'
|
||||
private static final String REPOSITORY_NAME = 'dependency-repository'
|
||||
private static final String STUB_RUNNER_TEMP_DIR_PREFIX = 'stub-runner'
|
||||
private static final String GRAPE_CONFIG = 'grape.config'
|
||||
private static final String STUB_RUNNER_GRAPE_CONFIG = "accurest.stubrunner.grape.config"
|
||||
|
||||
/**
|
||||
* Downloads stubs from an external repository and unpacks them locally.
|
||||
* Depending on the switch either uses only local repository to check for
|
||||
* stub presence.
|
||||
*
|
||||
* @param workOffline -flag that defines whether only local cache should be used
|
||||
* @param stubRepositoryRoot - address of the repo from which deps should be grabbed
|
||||
* @param stubsGroup - group name of the jar containing stubs
|
||||
* @param stubsModule - artifact id with a classifier name of the jar containing stubs
|
||||
* @return file where the stubs where unpacked
|
||||
*/
|
||||
File downloadAndUnpackStubJar(boolean workOffline, String stubRepositoryRoot, String stubsGroup, String
|
||||
stubsModule) {
|
||||
log.warn("Downloading stubs for group [$stubsGroup] and module [$stubsModule] from repository [$stubRepositoryRoot]")
|
||||
URI stubJarUri = findGrabbedStubJars(workOffline, stubRepositoryRoot, stubsGroup, stubsModule)
|
||||
if (!stubJarUri) {
|
||||
log.warn("Failed to download stubs for group [$stubsGroup] and module [$stubsModule] from repository [$stubRepositoryRoot]")
|
||||
return null
|
||||
}
|
||||
File unzippedStubsDir = unpackStubJarToATemporaryFolder(stubJarUri)
|
||||
unzippedStubsDir.deleteOnExit()
|
||||
Thread.addShutdownHook {
|
||||
unzippedStubsDir.deleteDir()
|
||||
}
|
||||
return unzippedStubsDir
|
||||
}
|
||||
|
||||
private File unpackStubJarToATemporaryFolder(URI stubJarUri) {
|
||||
File tmpDirWhereStubsWillBeUnzipped = createTempDirectory(STUB_RUNNER_TEMP_DIR_PREFIX).toFile()
|
||||
tmpDirWhereStubsWillBeUnzipped.deleteOnExit()
|
||||
log.info("Unpacking stub from JAR [URI: ${stubJarUri}]")
|
||||
use(ZipCategory) {
|
||||
new File(stubJarUri).unzipTo(tmpDirWhereStubsWillBeUnzipped)
|
||||
}
|
||||
return tmpDirWhereStubsWillBeUnzipped
|
||||
}
|
||||
|
||||
private URI findGrabbedStubJars(boolean workOffline, String stubRepositoryRoot, String stubsGroup, String stubsModule) {
|
||||
Map depToGrab = [group: stubsGroup, module: stubsModule, version: LATEST_MODULE, transitive: false]
|
||||
String accurestStubrunnerGrapePath = System.getProperty(STUB_RUNNER_GRAPE_CONFIG, getDefaultAccurestGrapeConfigPath())
|
||||
initializeAccurestGrapeIfAbsent(accurestStubrunnerGrapePath)
|
||||
String oldGrapeConfig = System.getProperty(GRAPE_CONFIG)
|
||||
try {
|
||||
System.setProperty(GRAPE_CONFIG, accurestStubrunnerGrapePath)
|
||||
log.info("Setting default grapes path to [$accurestStubrunnerGrapePath]")
|
||||
return buildResolver(workOffline).resolveDependency(stubRepositoryRoot, depToGrab)
|
||||
} finally {
|
||||
restoreOldGrapeConfigIfApplicable(oldGrapeConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private DependencyResolver buildResolver(boolean workOffline) {
|
||||
return workOffline ? new LocalOnlyDependencyResolver() : new RemoteDependencyResolver()
|
||||
}
|
||||
|
||||
private void initializeAccurestGrapeIfAbsent(String accurestGrapePath) {
|
||||
File accurestGrape = new File(accurestGrapePath)
|
||||
if (!accurestGrape.exists()) {
|
||||
accurestGrape.parentFile.mkdirs()
|
||||
accurestGrape.createNewFile()
|
||||
accurestGrape.text = StubDownloader.class.getResource('/accurestStubrunnerGrapeConfig.xml').text
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreOldGrapeConfigIfApplicable(String oldGrapeConfig) {
|
||||
if (oldGrapeConfig) {
|
||||
System.setProperty(GRAPE_CONFIG, oldGrapeConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private String getDefaultAccurestGrapeConfigPath() {
|
||||
return "${System.getProperty('user.home')}/.accurest/accurestStubrunnerGrapeConfig.xml"
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency resolver providing {@link URI} to remote dependencies.
|
||||
*/
|
||||
@Slf4j
|
||||
private class RemoteDependencyResolver extends DependencyResolver {
|
||||
|
||||
URI resolveDependency(String stubRepositoryRoot, Map depToGrab) {
|
||||
try {
|
||||
return doResolveRemoteDependency(stubRepositoryRoot, depToGrab)
|
||||
} catch (UnknownHostException e) {
|
||||
failureHandler(stubRepositoryRoot, "unknown host error -> ${e.message}", e)
|
||||
} catch (Exception e) {
|
||||
failureHandler(stubRepositoryRoot, "connection error -> ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private URI doResolveRemoteDependency(String stubRepositoryRoot, Map depToGrab) {
|
||||
Grape.addResolver(name: REPOSITORY_NAME, root: stubRepositoryRoot)
|
||||
log.info("Resolving dependency ${depToGrab} location in remote repository...")
|
||||
return resolveDependencyLocation(depToGrab)
|
||||
}
|
||||
|
||||
private void failureHandler(String stubRepository, String reason, Exception cause) {
|
||||
log.warn("Unable to resolve dependency in stub repository [$stubRepository]. Reason: [$reason]", cause)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency resolver that first checks if a dependency is available in the local repository.
|
||||
* If not, it will try to provide {@link URI} from the remote repository.
|
||||
*
|
||||
* @see RemoteDependencyResolver
|
||||
*/
|
||||
@Slf4j
|
||||
private class LocalOnlyDependencyResolver extends DependencyResolver {
|
||||
|
||||
URI resolveDependency(String stubRepositoryRoot, Map depToGrab) {
|
||||
try {
|
||||
log.info("Resolving dependency ${depToGrab} location in local repository...")
|
||||
return resolveDependencyLocation(depToGrab)
|
||||
} catch (Exception e) { //Grape throws ordinary RuntimeException
|
||||
log.warn("Unable to find dependency $depToGrab in local repository, trying $stubRepositoryRoot")
|
||||
log.debug("Unable to find dependency $depToGrab in local repository: ${e.getClass()}: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class of dependency resolvers providing {@link URI} to required dependency.
|
||||
*/
|
||||
abstract class DependencyResolver {
|
||||
|
||||
/**
|
||||
* Returns {@link URI} to a dependency.
|
||||
*
|
||||
* @param stubRepositoryRoot root of the repository where the dependency should be found
|
||||
* @param depToGrab parameters describing dependency to search for
|
||||
*
|
||||
* @return {@link URI} to dependency
|
||||
*/
|
||||
abstract URI resolveDependency(String stubRepositoryRoot, Map depToGrab)
|
||||
|
||||
URI resolveDependencyLocation(Map depToGrab) {
|
||||
return Grape.resolve([classLoader: new GroovyClassLoader()], depToGrab).first()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
interface StubFinder {
|
||||
/**
|
||||
* For the given groupId and artifactId tries to find the matching
|
||||
* URL of the running stub.
|
||||
*
|
||||
* @param groupId - might be null. In that case a search only via artifactId takes place
|
||||
* @return URL of a running stub or null if not found
|
||||
*/
|
||||
URL findStubUrl(String groupId, String artifactId)
|
||||
|
||||
/**
|
||||
* For the given Ivy notation {@code groupId:artifactId} tries to find the matching
|
||||
* URL of the running stub. You can also pass only {@code artifactId}.
|
||||
*
|
||||
* @param ivyNotation - Ivy representation of the Maven artifact
|
||||
* @return URL of a running stub or null if not found
|
||||
*/
|
||||
URL findStubUrl(String ivyNotation)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
|
||||
/**
|
||||
* Wraps the folder with WireMock mappings.
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class StubRepository {
|
||||
|
||||
private final File path
|
||||
|
||||
StubRepository(File repository) {
|
||||
if (!repository.isDirectory()) {
|
||||
throw new FileNotFoundException('Missing descriptor repository')
|
||||
}
|
||||
this.path = repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of WireMock JSON files wrapped in {@link MappingDescriptor}
|
||||
*/
|
||||
List<MappingDescriptor> getProjectDescriptors() {
|
||||
List<MappingDescriptor> mappingDescriptors = []
|
||||
mappingDescriptors.addAll(contextDescriptors())
|
||||
return mappingDescriptors
|
||||
}
|
||||
|
||||
private List<MappingDescriptor> contextDescriptors() {
|
||||
return path.exists() ? collectMappingDescriptors(path) : []
|
||||
}
|
||||
|
||||
private List<MappingDescriptor> collectMappingDescriptors(File descriptorsDirectory) {
|
||||
List<MappingDescriptor> mappingDescriptors = []
|
||||
descriptorsDirectory.eachFileRecurse { File file ->
|
||||
if (isMappingDescriptor(file)) {
|
||||
mappingDescriptors << new MappingDescriptor(file)
|
||||
}
|
||||
}
|
||||
return mappingDescriptors
|
||||
}
|
||||
|
||||
private static boolean isMappingDescriptor(File file) {
|
||||
return file.isFile() && file.name.endsWith('.json')
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
/**
|
||||
* Represents a single instance of ready-to-run stubs.
|
||||
* Can run the stubs and then will return the name of the collaborator together with
|
||||
* its URI.
|
||||
* Can also be queried if the current groupid and artifactid are matching the
|
||||
* corresponding running stub.
|
||||
*/
|
||||
@Slf4j
|
||||
@CompileStatic
|
||||
class StubRunner implements StubRunning {
|
||||
|
||||
private StubRunnerExecutor localStubRunner
|
||||
private final Arguments arguments
|
||||
private final StubRepository stubRepository
|
||||
|
||||
StubRunner(Arguments arguments) {
|
||||
this.arguments = arguments
|
||||
this.stubRepository = new StubRepository(new File(arguments.repositoryPath))
|
||||
}
|
||||
|
||||
@Override
|
||||
RunningStubs runStubs() {
|
||||
AvailablePortScanner portScanner = new AvailablePortScanner(arguments.stubRunnerOptions.minPortValue,
|
||||
arguments.stubRunnerOptions.maxPortValue)
|
||||
localStubRunner = new StubRunnerExecutor(portScanner)
|
||||
registerShutdownHook()
|
||||
return localStubRunner.runStubs(stubRepository, arguments.stub)
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String groupId, String artifactId) {
|
||||
return localStubRunner.findStubUrl(groupId, artifactId)
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String ivyNotation) {
|
||||
String[] splitString = ivyNotation.split(":")
|
||||
if (splitString.length == 1) {
|
||||
throw new IllegalArgumentException("$ivyNotation is invalid")
|
||||
}
|
||||
return findStubUrl(splitString[0], splitString[1])
|
||||
}
|
||||
|
||||
private void registerShutdownHook() {
|
||||
Runnable stopAllServers = { this.close() }
|
||||
Runtime.runtime.addShutdownHook(new Thread(stopAllServers))
|
||||
}
|
||||
|
||||
@Override
|
||||
void close() throws IOException {
|
||||
localStubRunner?.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
/**
|
||||
* Runs stubs for a particular {@link StubServer}
|
||||
*/
|
||||
@CompileStatic
|
||||
@Slf4j
|
||||
class StubRunnerExecutor implements StubFinder {
|
||||
|
||||
private final AvailablePortScanner portScanner
|
||||
private StubServer stubServer
|
||||
|
||||
StubRunnerExecutor(AvailablePortScanner portScanner) {
|
||||
this.portScanner = portScanner
|
||||
}
|
||||
|
||||
RunningStubs runStubs(StubRepository repository, StubConfiguration stubConfiguration) {
|
||||
startStubServers(stubConfiguration, repository)
|
||||
RunningStubs runningCollaborators =
|
||||
new RunningStubs([(stubServer.stubConfiguration): stubServer.port])
|
||||
log.info("All stubs are now running [${runningCollaborators.toString()}")
|
||||
return runningCollaborators
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
stubServer?.stop()
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String groupId, String artifactId) {
|
||||
if(!groupId) {
|
||||
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId)
|
||||
}
|
||||
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId &&
|
||||
stubServer.stubConfiguration.groupId == groupId)
|
||||
}
|
||||
|
||||
@Override
|
||||
URL findStubUrl(String ivyNotation) {
|
||||
String[] splitString = ivyNotation.split(":")
|
||||
if (splitString.length == 1) {
|
||||
throw new IllegalArgumentException("$ivyNotation is invalid")
|
||||
}
|
||||
return findStubUrl(splitString[0], splitString[1])
|
||||
}
|
||||
|
||||
private URL returnStubUrlIfMatches(boolean condition) {
|
||||
return condition ? stubServer.stubUrl : null
|
||||
}
|
||||
|
||||
private void startStubServers(StubConfiguration stubConfiguration, StubRepository repository) {
|
||||
List<MappingDescriptor> mappings = repository.getProjectDescriptors()
|
||||
stubServer = portScanner.tryToExecuteWithFreePort { int availablePort ->
|
||||
return new StubServer(availablePort, stubConfiguration, mappings).start()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.logging.Slf4j
|
||||
/**
|
||||
* Factory of StubRunners. Basing on the options and passed collaborators
|
||||
* downloads the stubs and returns a list of corresponding stub runners.
|
||||
*/
|
||||
@Slf4j
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class StubRunnerFactory {
|
||||
|
||||
private final StubRunnerOptions stubRunnerOptions
|
||||
private final Collection<StubConfiguration> collaborators
|
||||
private final StubDownloader stubDownloader
|
||||
|
||||
StubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection<StubConfiguration> collaborators) {
|
||||
this(stubRunnerOptions, collaborators, new StubDownloader())
|
||||
}
|
||||
|
||||
protected StubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection<StubConfiguration> collaborators,
|
||||
StubDownloader stubDownloader) {
|
||||
this.stubRunnerOptions = stubRunnerOptions
|
||||
this.collaborators = collaborators
|
||||
this.stubDownloader = stubDownloader
|
||||
}
|
||||
|
||||
Collection<StubRunner> createStubsFromServiceConfiguration() {
|
||||
return collaborators.collect { StubConfiguration stubsConfiguration ->
|
||||
final File unzipedStubDir = stubDownloader.downloadAndUnpackStubJar(stubRunnerOptions.workOffline,
|
||||
stubRunnerOptions.stubRepositoryRoot,
|
||||
stubsConfiguration.groupId, "$stubsConfiguration.artifactId${getStubDefinitionSuffix(stubsConfiguration)}")
|
||||
return createStubRunner(unzipedStubDir, stubsConfiguration)
|
||||
}.findAll { it != null }
|
||||
}
|
||||
|
||||
private String getStubDefinitionSuffix(StubConfiguration stubsConfiguration) {
|
||||
return stubsConfiguration.hasClassifier() ? "-${stubsConfiguration.classifier}" : ""
|
||||
}
|
||||
|
||||
private StubRunner createStubRunner(File unzipedStubDir, StubConfiguration stubsConfiguration) {
|
||||
if (!unzipedStubDir) {
|
||||
return null
|
||||
}
|
||||
return createStubRunner(unzipedStubDir, stubsConfiguration, stubRunnerOptions)
|
||||
}
|
||||
|
||||
private StubRunner createStubRunner(File unzippedStubsDir, StubConfiguration stubsConfiguration,
|
||||
StubRunnerOptions stubRunnerOptions) {
|
||||
return new StubRunner(new Arguments(stubRunnerOptions, unzippedStubsDir.path, stubsConfiguration))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Slf4j
|
||||
import io.codearte.accurest.stubrunner.util.StubsParser
|
||||
import org.kohsuke.args4j.CmdLineException
|
||||
import org.kohsuke.args4j.CmdLineParser
|
||||
import org.kohsuke.args4j.Option
|
||||
|
||||
import static org.kohsuke.args4j.OptionHandlerFilter.ALL
|
||||
|
||||
@Slf4j
|
||||
@CompileStatic
|
||||
class StubRunnerMain {
|
||||
|
||||
@Option(name = "-sr", aliases = ['--stubRepositoryRoot'], usage = "Location of a Jar containing server where you keep your stubs (e.g. http://nexus.net/content/repositories/repository)", required = true)
|
||||
private String stubRepositoryRoot
|
||||
|
||||
@Option(name = "-ss", aliases = ['--stubsSuffix'], usage = "Suffix for the jar containing stubs (e.g. 'stubs' if the stub jar would have a 'stubs' classifier for stubs: foobar-stubs ). Defaults to 'stubs'")
|
||||
private String stubsSuffix = 'stubs'
|
||||
|
||||
@Option(name = "-minp", aliases = ['--minPort'], usage = "Minimal port value to be assigned to the WireMock instance. Defaults to 10000")
|
||||
private Integer minPortValue = 10000
|
||||
|
||||
@Option(name = "-maxp", aliases = ['--maxPort'], usage = "Maximum port value to be assigned to the WireMock instance. Defaults to 15000")
|
||||
private Integer maxPortValue = 15000
|
||||
|
||||
@Option(name = "-wo", aliases = ['--workOffline'], usage = "Switch to work offline. Defaults to 'false'")
|
||||
private Boolean workOffline = Boolean.FALSE
|
||||
|
||||
@Option(name = "-s", aliases = ['--stubs'], usage = 'Comma separated list of Ivy representation of jars with stubs. Eg. groupid:artifactid1,groupid2:artifactid2:classifier')
|
||||
private String stubs
|
||||
|
||||
private final Arguments arguments
|
||||
|
||||
StubRunnerMain(String[] args) {
|
||||
CmdLineParser parser = new CmdLineParser(this)
|
||||
try {
|
||||
parser.parseArgument(args)
|
||||
this.arguments = new Arguments(new StubRunnerOptions(minPortValue, maxPortValue, stubRepositoryRoot,
|
||||
workOffline, stubsSuffix))
|
||||
} catch (CmdLineException e) {
|
||||
printErrorMessage(e, parser)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private void printErrorMessage(CmdLineException e, CmdLineParser parser) {
|
||||
System.err.println(e.getMessage())
|
||||
System.err.println("java -jar stub-runner.jar [options...] ")
|
||||
parser.printUsage(System.err)
|
||||
System.err.println()
|
||||
System.err.println("Example: java -jar stub-runner.jar ${parser.printExample(ALL)}")
|
||||
}
|
||||
|
||||
static void main(String[] args) {
|
||||
new StubRunnerMain(args).execute()
|
||||
}
|
||||
|
||||
private void execute() {
|
||||
try {
|
||||
log.debug("Launching StubRunner with args: $arguments")
|
||||
// TODO: Pass StubsToRun either from String or File
|
||||
Collection<StubConfiguration> collaborators = StubsParser.fromString(stubs, stubsSuffix)
|
||||
BatchStubRunner stubRunner = new BatchStubRunnerFactory(arguments.stubRunnerOptions, collaborators).buildBatchStubRunner()
|
||||
RunningStubs runningCollaborators = stubRunner.runStubs()
|
||||
log.info(runningCollaborators.toString())
|
||||
} catch (Exception e) {
|
||||
log.error("An exception occurred while trying to execute the stubs", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.ToString
|
||||
|
||||
/**
|
||||
* Technical options related to running StubRunner
|
||||
*/
|
||||
@ToString(includeNames = true)
|
||||
@CompileStatic
|
||||
class StubRunnerOptions {
|
||||
|
||||
/**
|
||||
* min port value of the WireMock instance for the given collaborator
|
||||
*/
|
||||
Integer minPortValue = 10000
|
||||
|
||||
/**
|
||||
* max port value of the WireMock instance for the given collaborator
|
||||
*/
|
||||
Integer maxPortValue = 15000
|
||||
|
||||
/**
|
||||
* root URL from where the JAR with stub mappings will be downloaded
|
||||
*/
|
||||
String stubRepositoryRoot
|
||||
|
||||
/**
|
||||
* avoids local repository in dependency resolution
|
||||
*/
|
||||
boolean workOffline = false
|
||||
|
||||
/**
|
||||
* stub definition suffix
|
||||
*/
|
||||
String stubsClassifier = "stubs"
|
||||
|
||||
StubRunnerOptions(Integer minPortValue, Integer maxPortValue, String stubRepositoryRoot,
|
||||
boolean workOffline, String stubsClassifier) {
|
||||
this.minPortValue = minPortValue
|
||||
this.maxPortValue = maxPortValue
|
||||
this.stubRepositoryRoot = stubRepositoryRoot
|
||||
this.workOffline = workOffline
|
||||
this.stubsClassifier = stubsClassifier
|
||||
}
|
||||
|
||||
StubRunnerOptions(String stubRepositoryRoot) {
|
||||
this.stubRepositoryRoot = stubRepositoryRoot
|
||||
}
|
||||
|
||||
StubRunnerOptions() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
interface StubRunning extends Closeable, StubFinder {
|
||||
/**
|
||||
* Runs the stubs and returns the {@link RunningStubs}
|
||||
*/
|
||||
RunningStubs runStubs()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer
|
||||
import com.github.tomakehurst.wiremock.client.WireMock
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
@CompileStatic
|
||||
@Slf4j
|
||||
@PackageScope
|
||||
class StubServer {
|
||||
private WireMockServer wireMockServer
|
||||
final StubConfiguration stubConfiguration
|
||||
final Collection<MappingDescriptor> mappings
|
||||
|
||||
StubServer(int port, StubConfiguration stubConfiguration, Collection<MappingDescriptor> mappings) {
|
||||
this.stubConfiguration = stubConfiguration
|
||||
this.mappings = mappings
|
||||
this.wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().port(port))
|
||||
}
|
||||
|
||||
StubServer start() {
|
||||
wireMockServer.start()
|
||||
log.info("Started stub server for project ${stubConfiguration.toColonSeparatedDependencyNotation()} on port ${wireMockServer.port()}")
|
||||
registerStubMappings()
|
||||
return this
|
||||
}
|
||||
|
||||
void stop() {
|
||||
wireMockServer.stop()
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return wireMockServer.port()
|
||||
}
|
||||
|
||||
URL getStubUrl() {
|
||||
return new URL("http://localhost:$port")
|
||||
}
|
||||
|
||||
private void registerStubMappings() {
|
||||
WireMock wireMock = new WireMock('localhost', wireMockServer.port())
|
||||
registerDefaultHealthChecks(wireMock)
|
||||
registerStubs(mappings, wireMock)
|
||||
}
|
||||
|
||||
private void registerDefaultHealthChecks(WireMock wireMock) {
|
||||
registerHealthCheck(wireMock, '/ping')
|
||||
registerHealthCheck(wireMock, '/health')
|
||||
}
|
||||
|
||||
private void registerStubs(Collection<MappingDescriptor> sortedMappings, WireMock wireMock) {
|
||||
sortedMappings.each { MappingDescriptor mappingDescriptor ->
|
||||
try {
|
||||
wireMock.register(mappingDescriptor.mapping)
|
||||
log.debug("Registered stub mappings from $mappingDescriptor.descriptor")
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to register the stub mapping [$mappingDescriptor]", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHealthCheck(WireMock wireMock, String url, String body = 'OK') {
|
||||
wireMock.register(WireMock.get(WireMock.urlEqualTo(url)).willReturn(WireMock.aResponse().withBody(body).withStatus(200)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package io.codearte.accurest.stubrunner.util
|
||||
|
||||
/**
|
||||
* Utils ported from Apache Commons
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class StringUtils {
|
||||
public static String EMPTY = ""
|
||||
private static int INDEX_NOT_FOUND = -1
|
||||
|
||||
// Empty checks
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Checks if a String is empty ("") or null.</p>
|
||||
*
|
||||
* <pre>
|
||||
* StringUtils.isEmpty(null) = true
|
||||
* StringUtils.isEmpty("") = true
|
||||
* StringUtils.isEmpty(" ") = false
|
||||
* StringUtils.isEmpty("bob") = false
|
||||
* StringUtils.isEmpty(" bob ") = false
|
||||
* </pre>
|
||||
*
|
||||
* <p>NOTE: This method changed in Lang version 2.0.
|
||||
* It no longer trims the String.
|
||||
* That functionality is available in isBlank().</p>
|
||||
*
|
||||
* @param str the String to check, may be null
|
||||
* @return <code>true</code> if the String is empty or null
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.length() == 0;
|
||||
}
|
||||
|
||||
static boolean isNotEmpty(String string) {
|
||||
return string != null && !string.empty
|
||||
}
|
||||
|
||||
static boolean hasText(String string) {
|
||||
return isNotEmpty(string) && !string.allWhitespace
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the substring before the last occurrence of a separator.
|
||||
* The separator is not returned.</p>
|
||||
*
|
||||
* <p>A <code>null</code> string input will return <code>null</code>.
|
||||
* An empty ("") string input will return the empty string.
|
||||
* An empty or <code>null</code> separator will return the input string.</p>
|
||||
*
|
||||
* <p>If nothing is found, the string input is returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* StringUtils.substringBeforeLast(null, *) = null
|
||||
* StringUtils.substringBeforeLast("", *) = ""
|
||||
* StringUtils.substringBeforeLast("abcba", "b") = "abc"
|
||||
* StringUtils.substringBeforeLast("abc", "c") = "ab"
|
||||
* StringUtils.substringBeforeLast("a", "a") = ""
|
||||
* StringUtils.substringBeforeLast("a", "z") = "a"
|
||||
* StringUtils.substringBeforeLast("a", null) = "a"
|
||||
* StringUtils.substringBeforeLast("a", "") = "a"
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to get a substring from, may be null
|
||||
* @param separator the String to search for, may be null
|
||||
* @return the substring before the last occurrence of the separator,
|
||||
* <code>null</code> if null String input
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String substringBeforeLast(String str, String separator) {
|
||||
if (isEmpty(str) || isEmpty(separator)) {
|
||||
return str;
|
||||
}
|
||||
int pos = str.lastIndexOf(separator);
|
||||
if (pos == INDEX_NOT_FOUND) {
|
||||
return str;
|
||||
}
|
||||
return str.substring(0, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the substring after the last occurrence of a separator.
|
||||
* The separator is not returned.</p>
|
||||
*
|
||||
* <p>A <code>null</code> string input will return <code>null</code>.
|
||||
* An empty ("") string input will return the empty string.
|
||||
* An empty or <code>null</code> separator will return the empty string if
|
||||
* the input string is not <code>null</code>.</p>
|
||||
*
|
||||
* <p>If nothing is found, the empty string is returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* StringUtils.substringAfterLast(null, *) = null
|
||||
* StringUtils.substringAfterLast("", *) = ""
|
||||
* StringUtils.substringAfterLast(*, "") = ""
|
||||
* StringUtils.substringAfterLast(*, null) = ""
|
||||
* StringUtils.substringAfterLast("abc", "a") = "bc"
|
||||
* StringUtils.substringAfterLast("abcba", "b") = "a"
|
||||
* StringUtils.substringAfterLast("abc", "c") = ""
|
||||
* StringUtils.substringAfterLast("a", "a") = ""
|
||||
* StringUtils.substringAfterLast("a", "z") = ""
|
||||
* </pre>
|
||||
*
|
||||
* @param str the String to get a substring from, may be null
|
||||
* @param separator the String to search for, may be null
|
||||
* @return the substring after the last occurrence of the separator,
|
||||
* <code>null</code> if null String input
|
||||
* @since 2.0
|
||||
*/
|
||||
public static String substringAfterLast(String str, String separator) {
|
||||
if (isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
if (isEmpty(separator)) {
|
||||
return EMPTY;
|
||||
}
|
||||
int pos = str.lastIndexOf(separator);
|
||||
if (pos == INDEX_NOT_FOUND || pos == (str.length() - separator.length())) {
|
||||
return EMPTY;
|
||||
}
|
||||
return str.substring(pos + separator.length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.codearte.accurest.stubrunner.util
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import io.codearte.accurest.stubrunner.StubConfiguration
|
||||
|
||||
/**
|
||||
* Utility to parse string into a list of configuration of stubs
|
||||
*/
|
||||
@CompileStatic
|
||||
class StubsParser {
|
||||
|
||||
/**
|
||||
* The string is expected to be a map with entry called "stubs"
|
||||
* that contains a list of Strings in the format
|
||||
*
|
||||
* <ul>
|
||||
* <li>groupid:artifactid:classifier</li>
|
||||
* <li>groupid:artifactid</li>
|
||||
* </ul>
|
||||
*
|
||||
* In the latter case the provided default stub classifier will be passed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* "a:b,c:d:e"
|
||||
*/
|
||||
static Set<StubConfiguration> fromString(String list, String defaultClassifier) {
|
||||
return list.split(',').findAll { it }.collect { String string ->
|
||||
new StubConfiguration(string, defaultClassifier)
|
||||
} as Set
|
||||
}
|
||||
|
||||
static Set<StubConfiguration> fromString(Collection<String> collection, String defaultClassifier) {
|
||||
return collection.findAll { it }.collect { String string ->
|
||||
new StubConfiguration(string, defaultClassifier)
|
||||
} as Set
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.codearte.accurest.stubrunner.util
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/**
|
||||
* Based on <a href="https://github.com/timyates/groovy-common-extensions">https://github.com/timyates/groovy-common-extensions</a>.
|
||||
*
|
||||
* Category for {@link File} that adds a method that allows you to unzip
|
||||
* a given file to a specified location
|
||||
*
|
||||
*/
|
||||
@CompileStatic
|
||||
class ZipCategory {
|
||||
|
||||
/**
|
||||
* Unzips this file. If the <tt>destination</tt>
|
||||
* directory is not provided, it will fall back to this file's parent directory.
|
||||
*
|
||||
* @param self
|
||||
* @param destination (optional), the destination directory where this file's content will be unzipped to.
|
||||
* @return a {@link Collection} of unzipped {@link File} objects.
|
||||
*/
|
||||
static Collection<File> unzipTo(File self, File destination) {
|
||||
checkUnzipDestination(destination)
|
||||
// if destination directory is not given, we'll fall back to the parent directory of 'self'
|
||||
if (destination == null) destination = new File(self.parent)
|
||||
List<File> unzippedFiles = []
|
||||
final ZipInputStream zipInput = new ZipInputStream(new FileInputStream(self))
|
||||
zipInput.withStream {
|
||||
ZipEntry entry
|
||||
while (entry = zipInput.nextEntry) {
|
||||
if (!entry.isDirectory()) {
|
||||
final File file = new File(destination, entry.name)
|
||||
file.parentFile?.mkdirs()
|
||||
FileOutputStream output = new FileOutputStream(file)
|
||||
output.withStream {
|
||||
output << zipInput
|
||||
}
|
||||
unzippedFiles << file
|
||||
} else {
|
||||
final File dir = new File(destination, entry.name)
|
||||
dir.mkdirs()
|
||||
unzippedFiles << dir
|
||||
}
|
||||
}
|
||||
}
|
||||
return unzippedFiles
|
||||
}
|
||||
|
||||
private static void checkUnzipDestination(File file) {
|
||||
if (file && !file.isDirectory()) throw new IllegalArgumentException("'destination' has to be a directory.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<ivysettings>
|
||||
<settings defaultResolver="downloadGrapes"/>
|
||||
<resolvers>
|
||||
<chain name="downloadGrapes">
|
||||
<ibiblio name="localm2" root="file:${user.home}/.m2/repository/" checkmodified="true" changingPattern=".*" changingMatcher="regexp" m2compatible="true"/>
|
||||
<filesystem name="cachedGrapes">
|
||||
<ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
|
||||
<artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]"/>
|
||||
</filesystem>
|
||||
</chain>
|
||||
</resolvers>
|
||||
</ivysettings>
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class AvailablePortScannerSpec extends Specification {
|
||||
|
||||
private static final int MIN_PORT = 8989
|
||||
private static final int MAX_PORT = 8990
|
||||
private static final int MAX_RETRY_COUNT_FOR_NEGATIVE_SCENARIOS = 2
|
||||
|
||||
def 'should execute given closure with the next available port number'() {
|
||||
given:
|
||||
AvailablePortScanner portScanner = new AvailablePortScanner(MIN_PORT, MAX_PORT)
|
||||
when:
|
||||
int usedPort = portScanner.tryToExecuteWithFreePort { int port -> port }
|
||||
then:
|
||||
noExceptionThrown()
|
||||
usedPort == MIN_PORT
|
||||
}
|
||||
|
||||
def 'should throw exception when improper range has been provided'() {
|
||||
when:
|
||||
new AvailablePortScanner(minPort, maxPort, MAX_RETRY_COUNT_FOR_NEGATIVE_SCENARIOS)
|
||||
then:
|
||||
def ex = thrown(AvailablePortScanner.InvalidPortRange)
|
||||
ex.message == "Invalid bounds exceptions, min port [$minPort] is greater or equal to max port [$maxPort]"
|
||||
where:
|
||||
minPort | maxPort
|
||||
MIN_PORT | MIN_PORT
|
||||
MAX_PORT | MIN_PORT
|
||||
|
||||
}
|
||||
|
||||
def 'should throw exception when there is no available port'() {
|
||||
given:
|
||||
AvailablePortScanner portScanner = new AvailablePortScanner(MIN_PORT, MAX_PORT, MAX_RETRY_COUNT_FOR_NEGATIVE_SCENARIOS)
|
||||
when:
|
||||
portScanner.tryToExecuteWithFreePort {
|
||||
throw new BindException('Bind exception from closure')
|
||||
}
|
||||
then:
|
||||
def ex = thrown(AvailablePortScanner.NoPortAvailableException)
|
||||
ex.message == "Could not find available port in range $MIN_PORT:$MAX_PORT"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class BatchStubRunnerSpec extends Specification {
|
||||
|
||||
static final String KNOWN_STUB_PATH = 'group:knownArtifact'
|
||||
static final String UNKNOWN_STUB_PATH = 'group:unknownArtifact'
|
||||
static final URL KNOWN_STUB_URL = new URL('http://localhost:8080')
|
||||
|
||||
def 'should provide stub URL from enclosed stub runner'() {
|
||||
given:
|
||||
BatchStubRunner batchStubRunner = new BatchStubRunner(runners())
|
||||
expect:
|
||||
batchStubRunner.findStubUrl(KNOWN_STUB_PATH) == KNOWN_STUB_URL
|
||||
}
|
||||
|
||||
def 'should return empty optional for unknown stub path'() {
|
||||
given:
|
||||
BatchStubRunner batchStubRunner = new BatchStubRunner(runners())
|
||||
expect:
|
||||
!batchStubRunner.findStubUrl(UNKNOWN_STUB_PATH)
|
||||
}
|
||||
|
||||
Collection<StubRunner> runners() {
|
||||
StubRunner runner = Mock(StubRunner)
|
||||
runner.findStubUrl("group", "knownArtifact") >> KNOWN_STUB_URL
|
||||
runner.findStubUrl("group", "unknownArtifact") >> null
|
||||
return [runner]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import com.github.tomakehurst.wiremock.http.RequestMethod
|
||||
import spock.lang.Specification
|
||||
|
||||
class MappingDescriptorSpec extends Specification {
|
||||
public static
|
||||
final File MAPPING_DESCRIPTOR = new File('src/test/resources/repository/mappings/com/ofg/ping/ping.json')
|
||||
|
||||
def 'should describe stub mapping'() {
|
||||
given:
|
||||
MappingDescriptor mappingDescriptor = new MappingDescriptor(MAPPING_DESCRIPTOR)
|
||||
|
||||
expect:
|
||||
with(mappingDescriptor.mapping) {
|
||||
request.method == RequestMethod.GET
|
||||
request.url == '/ping'
|
||||
response.status == 200
|
||||
response.body == 'pong'
|
||||
response.headers.contentTypeHeader.mimeTypePart() == 'text/plain'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class StubRepositorySpec extends Specification {
|
||||
public static
|
||||
final File REPOSITORY_LOCATION = new File('src/test/resources/repository')
|
||||
|
||||
def 'should retrieve all descriptors for given project'() {
|
||||
given:
|
||||
StubRepository repository = new StubRepository(REPOSITORY_LOCATION)
|
||||
int expectedDescriptorsSize = 8
|
||||
when:
|
||||
List<MappingDescriptor> descriptors = repository.getProjectDescriptors()
|
||||
then:
|
||||
descriptors.size() == expectedDescriptorsSize
|
||||
}
|
||||
|
||||
def 'should return empty list if files are missing'() {
|
||||
given:
|
||||
StubRepository repository = new StubRepository(new File('src/test/resources/emptyrepo'))
|
||||
when:
|
||||
List<MappingDescriptor> descriptors = repository.getProjectDescriptors()
|
||||
then:
|
||||
descriptors.empty
|
||||
}
|
||||
|
||||
def 'should throw an exception if directory with mappings is missing'() {
|
||||
when:
|
||||
new StubRepository(new File('src/test/resources/nonexistingrepo'))
|
||||
then:
|
||||
thrown(FileNotFoundException)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class StubRunnerExecutorSpec extends Specification {
|
||||
|
||||
static final URL EXPECTED_STUB_URL = new URL('http://localhost:8999')
|
||||
static final int MIN_PORT = 8999
|
||||
static final int MAX_PORT = 9000
|
||||
|
||||
private AvailablePortScanner portScanner
|
||||
private StubRepository repository
|
||||
private StubConfiguration stub = new StubConfiguration("group:artifact", "stubs")
|
||||
|
||||
def setup() {
|
||||
portScanner = new AvailablePortScanner(MIN_PORT, MAX_PORT)
|
||||
repository = new StubRepository(new File('src/test/resources/repository'))
|
||||
}
|
||||
|
||||
def 'should provide URL for given relative path of stub'() {
|
||||
given:
|
||||
StubRunnerExecutor executor = new StubRunnerExecutor(portScanner)
|
||||
when:
|
||||
executor.runStubs(repository, stub)
|
||||
then:
|
||||
executor.findStubUrl("group", "artifact") == EXPECTED_STUB_URL
|
||||
cleanup:
|
||||
executor.shutdown()
|
||||
}
|
||||
|
||||
def 'should provide no URL for unknown dependency path'() {
|
||||
given:
|
||||
StubRunnerExecutor executor = new StubRunnerExecutor(portScanner)
|
||||
when:
|
||||
executor.runStubs(repository, stub)
|
||||
then:
|
||||
!executor.findStubUrl("unkowngroup", "unknownartifact")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import spock.lang.Specification
|
||||
|
||||
class StubRunnerFactorySpec extends Specification {
|
||||
|
||||
@Rule
|
||||
TemporaryFolder folder = new TemporaryFolder()
|
||||
|
||||
Collection<StubConfiguration> collaborators = [new StubConfiguration("a:b"), new StubConfiguration("c:d")]
|
||||
StubDownloader downloader = Mock(StubDownloader)
|
||||
StubRunnerOptions stubRunnerOptions = new StubRunnerOptions(stubRepositoryRoot: 'http://sth.net')
|
||||
StubRunnerFactory factory = new StubRunnerFactory(stubRunnerOptions, collaborators, downloader)
|
||||
|
||||
def "Should download stub definitions many times"() {
|
||||
given:
|
||||
folder.newFolder("mappings")
|
||||
2 * downloader.downloadAndUnpackStubJar(_, _, _, _) >> folder.root
|
||||
stubRunnerOptions.stubRepositoryRoot = folder.root.absolutePath
|
||||
when:
|
||||
Collection<StubRunner> stubRunners = collectOnlyPresentValues(factory.createStubsFromServiceConfiguration())
|
||||
then:
|
||||
stubRunners.size() == 2
|
||||
}
|
||||
|
||||
private List<StubRunner> collectOnlyPresentValues(Collection<StubRunner> stubRunners) {
|
||||
return stubRunners.findAll { it != null }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class StubRunnerSpec extends Specification {
|
||||
|
||||
private static final int MIN_PORT = 8111
|
||||
private static final int MAX_PORT = 8112
|
||||
private static final URL EXPECTED_STUB_URL = new URL("http://localhost:$MIN_PORT")
|
||||
|
||||
def 'should provide stub URL for provided groupid and artifactId'() {
|
||||
given:
|
||||
StubRunner runner = new StubRunner(argumentsWithProjectDefinition())
|
||||
when:
|
||||
runner.runStubs()
|
||||
then:
|
||||
runner.findStubUrl("groupId", "artifactId") == EXPECTED_STUB_URL
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
def 'should provide stub URL if only artifactId was passed'() {
|
||||
given:
|
||||
StubRunner runner = new StubRunner(argumentsWithProjectDefinition())
|
||||
when:
|
||||
runner.runStubs()
|
||||
then:
|
||||
runner.findStubUrl(null, "artifactId") == EXPECTED_STUB_URL
|
||||
cleanup:
|
||||
runner.close()
|
||||
}
|
||||
|
||||
Arguments argumentsWithProjectDefinition() {
|
||||
StubConfiguration stubConfiguration = new StubConfiguration("groupId", "artifactId", "classifier")
|
||||
StubRunnerOptions stubRunnerOptions = new StubRunnerOptions(minPortValue: MIN_PORT, maxPortValue: MAX_PORT)
|
||||
return new Arguments(stubRunnerOptions, 'src/test/resources/repository', stubConfiguration)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.codearte.accurest.stubrunner
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class StubServerSpec extends Specification {
|
||||
static final int STUB_SERVER_PORT = 12180
|
||||
static final URL EXPECTED_URL = new URL("http://localhost:$STUB_SERVER_PORT")
|
||||
|
||||
File repository = new File('src/test/resources/repository/mappings/com/ofg/bye')
|
||||
StubConfiguration stubConfiguration = new StubConfiguration("a:b")
|
||||
|
||||
def 'should register stub mappings upon server start'() {
|
||||
given:
|
||||
List<MappingDescriptor> mappingDescriptors = new StubRepository(repository).getProjectDescriptors()
|
||||
StubServer pingStubServer = new StubServer(STUB_SERVER_PORT, stubConfiguration, mappingDescriptors)
|
||||
when:
|
||||
pingStubServer.start()
|
||||
then:
|
||||
"http://localhost:$pingStubServer.port/bye".toURL().text == 'Goodbye world!'
|
||||
cleanup:
|
||||
pingStubServer.stop()
|
||||
}
|
||||
|
||||
def 'should provide stub server URL'() {
|
||||
given:
|
||||
List<MappingDescriptor> mappingDescriptors = new StubRepository(repository).getProjectDescriptors()
|
||||
StubServer pingStubServer = new StubServer(STUB_SERVER_PORT, stubConfiguration, mappingDescriptors)
|
||||
when:
|
||||
pingStubServer.start()
|
||||
then:
|
||||
pingStubServer.stubUrl == EXPECTED_URL
|
||||
cleanup:
|
||||
pingStubServer.stop()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.codearte.accurest.stubrunner.util
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
import spock.lang.Specification
|
||||
|
||||
@Slf4j
|
||||
class ZipCategorySpec extends Specification {
|
||||
|
||||
def 'should unzip a file to the specified location'() {
|
||||
given:
|
||||
File zipFile = new File(ZipCategorySpec.classLoader.getResource('file.zip').toURI())
|
||||
File tempDir = File.createTempDir()
|
||||
tempDir.deleteOnExit()
|
||||
when:
|
||||
use(ZipCategory) {
|
||||
zipFile.unzipTo(tempDir)
|
||||
}
|
||||
then:
|
||||
tempDir.listFiles().find {
|
||||
it.name == 'file.txt'
|
||||
}?.text?.trim() == 'test'
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/bar"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "bar",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/foobar"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "foobar",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/foo"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "foo",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"pl": [
|
||||
"com/ofg/bar"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"pl": [
|
||||
"com/ofg/foo/bar"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"pl": [
|
||||
"com/ofg/foo"
|
||||
]
|
||||
}
|
||||
BIN
stub-runner/stub-runner/src/test/resources/file.zip
Normal file
BIN
stub-runner/stub-runner/src/test/resources/file.zip
Normal file
Binary file not shown.
14
stub-runner/stub-runner/src/test/resources/logback.xml
Normal file
14
stub-runner/stub-runner/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/admin"
|
||||
},
|
||||
"response": {
|
||||
"status": 401
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/bye"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "Goodbye world!",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
### Hello service stub
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/admin"
|
||||
},
|
||||
"response": {
|
||||
"status": 401
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/hello"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "Hello world!",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/ping"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "pong",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/lv/bye"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "Another goodbye world!",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/pl/bye"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "pl-bye",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/bye"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "overridden-bye",
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user