Added stub-runner, stub-runner-spring, stub-runner-junit

This commit is contained in:
Marcin Grzejszczak
2016-03-26 16:46:47 +01:00
parent cd955b1d21
commit 8f1a3ea17b
81 changed files with 2363 additions and 1 deletions

View File

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

View File

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

View File

@@ -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"

View 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 |

View 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'
}

View File

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

View File

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

View File

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

View 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
```

View 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'
}

View File

@@ -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() : "";
}
}

View File

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

View File

@@ -0,0 +1,2 @@
stubrunner.stubs.repository.root: classpath:m2repo
stubrunner.stubs: io.codearte.accurest.stubs:loanIssuance,io.codearte.accurest.stubs:fraudDetectionServer

View 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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.

View 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
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
package io.codearte.accurest.stubrunner
interface StubRunning extends Closeable, StubFinder {
/**
* Runs the stubs and returns the {@link RunningStubs}
*/
RunningStubs runStubs()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/bar"
},
"response": {
"status": 200,
"body": "bar",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/foobar"
},
"response": {
"status": 200,
"body": "foobar",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/foo"
},
"response": {
"status": 200,
"body": "foo",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,5 @@
{
"pl": [
"com/ofg/bar"
]
}

View File

@@ -0,0 +1,5 @@
{
"pl": [
"com/ofg/foo/bar"
]
}

View File

@@ -0,0 +1,5 @@
{
"pl": [
"com/ofg/foo"
]
}

Binary file not shown.

View 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>

View File

@@ -0,0 +1,9 @@
{
"request": {
"method": "GET",
"url": "/admin"
},
"response": {
"status": 401
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/bye"
},
"response": {
"status": 200,
"body": "Goodbye world!",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1 @@
### Hello service stub

View File

@@ -0,0 +1,9 @@
{
"request": {
"method": "GET",
"url": "/admin"
},
"response": {
"status": 401
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/hello"
},
"response": {
"status": 200,
"body": "Hello world!",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/ping"
},
"response": {
"status": 200,
"body": "pong",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/lv/bye"
},
"response": {
"status": 200,
"body": "Another goodbye world!",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"url": "/pl/bye"
},
"response": {
"status": 200,
"body": "pl-bye",
"headers": {
"Content-Type": "text/plain"
}
}
}

View File

@@ -0,0 +1,14 @@
{
"request": {
"method": "GET",
"url": "/bye"
},
"response": {
"status": 200,
"body": "overridden-bye",
"headers": {
"Content-Type": "text/plain"
}
}
}