diff --git a/.travis.yml b/.travis.yml index 220272e2ee..14f8d62652 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 923a398c25..a29b3523e7 100644 --- a/README.md +++ b/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 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7a36841ba8..762abc0c49 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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" \ No newline at end of file diff --git a/stub-runner/stub-runner-junit/README.md b/stub-runner/stub-runner-junit/README.md new file mode 100644 index 0000000000..32c824c182 --- /dev/null +++ b/stub-runner/stub-runner-junit/README.md @@ -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 | + diff --git a/stub-runner/stub-runner-junit/build.gradle b/stub-runner/stub-runner-junit/build.gradle new file mode 100644 index 0000000000..d7156d14b9 --- /dev/null +++ b/stub-runner/stub-runner-junit/build.gradle @@ -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' +} diff --git a/stub-runner/stub-runner-junit/src/main/groovy/io/codearte/accurest/stubrunner/junit/AccurestRule.java b/stub-runner/stub-runner-junit/src/main/groovy/io/codearte/accurest/stubrunner/junit/AccurestRule.java new file mode 100644 index 0000000000..b584b8ca7f --- /dev/null +++ b/stub-runner/stub-runner-junit/src/main/groovy/io/codearte/accurest/stubrunner/junit/AccurestRule.java @@ -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 stubs = new HashSet(); + 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 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 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); + } +} diff --git a/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSpec.groovy b/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSpec.groovy new file mode 100644 index 0000000000..755f8857c7 --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSpec.groovy @@ -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' + } +} diff --git a/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSysPropsSpec.groovy b/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSysPropsSpec.groovy new file mode 100644 index 0000000000..77ab729acc --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/groovy/io/codearte/accurest/stubrunner/junit/AccurestRuleSysPropsSpec.groovy @@ -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' + } +} diff --git a/stub-runner/stub-runner-junit/src/test/resources/logback.xml b/stub-runner/stub-runner-junit/src/test/resources/logback.xml new file mode 100644 index 0000000000..0cfb35f4cd --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..a29a631802 Binary files /dev/null and b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar differ diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..2a8b40b197 --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + io.codearte.accurest.stubs + fraudDetectionServer-stubs + 0.0.1-SNAPSHOT + diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..5e2fc1d528 --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + fraudDetectionServer-stubs + 0.0.1-SNAPSHOT + + + true + + 20160326150924 + + diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml new file mode 100644 index 0000000000..46fa17218f --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml @@ -0,0 +1,11 @@ + + + io.codearte.accurest.stubs + fraudDetectionServer-stubs + + + 0.0.1-SNAPSHOT + + 20160326150924 + + diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..12d6b10f29 Binary files /dev/null and b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar differ diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..a296158345 --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + io.codearte.accurest.stubs + loanIssuance-stubs + 0.0.1-SNAPSHOT + diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..f33521f12d --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + loanIssuance-stubs + 0.0.1-SNAPSHOT + + + true + + 20160326150924 + + diff --git a/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml new file mode 100644 index 0000000000..e990fad0bc --- /dev/null +++ b/stub-runner/stub-runner-junit/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml @@ -0,0 +1,11 @@ + + + io.codearte.accurest.stubs + loanIssuance-stubs + + + 0.0.1-SNAPSHOT + + 20160326150924 + + diff --git a/stub-runner/stub-runner-spring/README.md b/stub-runner/stub-runner-spring/README.md new file mode 100644 index 0000000000..2229814c9e --- /dev/null +++ b/stub-runner/stub-runner-spring/README.md @@ -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 +``` \ No newline at end of file diff --git a/stub-runner/stub-runner-spring/build.gradle b/stub-runner/stub-runner-spring/build.gradle new file mode 100644 index 0000000000..3df19ddb06 --- /dev/null +++ b/stub-runner/stub-runner-spring/build.gradle @@ -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' +} diff --git a/stub-runner/stub-runner-spring/src/main/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfiguration.java b/stub-runner/stub-runner-spring/src/main/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfiguration.java new file mode 100644 index 0000000000..0f1c8d6939 --- /dev/null +++ b/stub-runner/stub-runner-spring/src/main/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfiguration.java @@ -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 dependencies = StubsParser.fromString(stubs, stubsSuffix); + return new BatchStubRunnerFactory(stubRunnerOptions, dependencies) + .buildBatchStubRunner(); + } + + private String uriStringOrEmpty(Resource stubRepositoryRoot) throws IOException { + return stubRepositoryRoot != null ? stubRepositoryRoot.getURI().toString() : ""; + } + +} diff --git a/stub-runner/stub-runner-spring/src/test/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfigurationSpec.groovy b/stub-runner/stub-runner-spring/src/test/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfigurationSpec.groovy new file mode 100644 index 0000000000..2e3a749ecd --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/groovy/io/codearte/accurest/stubrunner/spring/StubRunnerConfigurationSpec.groovy @@ -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 { + + } +} diff --git a/stub-runner/stub-runner-spring/src/test/resources/application.yml b/stub-runner/stub-runner-spring/src/test/resources/application.yml new file mode 100644 index 0000000000..4b48d59b25 --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/application.yml @@ -0,0 +1,2 @@ +stubrunner.stubs.repository.root: classpath:m2repo +stubrunner.stubs: io.codearte.accurest.stubs:loanIssuance,io.codearte.accurest.stubs:fraudDetectionServer \ No newline at end of file diff --git a/stub-runner/stub-runner-spring/src/test/resources/logback.xml b/stub-runner/stub-runner-spring/src/test/resources/logback.xml new file mode 100644 index 0000000000..0cfb35f4cd --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..a29a631802 Binary files /dev/null and b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.jar differ diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..2a8b40b197 --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/fraudDetectionServer-stubs-0.0.1-SNAPSHOT.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + io.codearte.accurest.stubs + fraudDetectionServer-stubs + 0.0.1-SNAPSHOT + diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..5e2fc1d528 --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + fraudDetectionServer-stubs + 0.0.1-SNAPSHOT + + + true + + 20160326150924 + + diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml new file mode 100644 index 0000000000..46fa17218f --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/fraudDetectionServer-stubs/maven-metadata-local.xml @@ -0,0 +1,11 @@ + + + io.codearte.accurest.stubs + fraudDetectionServer-stubs + + + 0.0.1-SNAPSHOT + + 20160326150924 + + diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..12d6b10f29 Binary files /dev/null and b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.jar differ diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..a296158345 --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/loanIssuance-stubs-0.0.1-SNAPSHOT.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + io.codearte.accurest.stubs + loanIssuance-stubs + 0.0.1-SNAPSHOT + diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..f33521f12d --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + loanIssuance-stubs + 0.0.1-SNAPSHOT + + + true + + 20160326150924 + + diff --git a/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml new file mode 100644 index 0000000000..e990fad0bc --- /dev/null +++ b/stub-runner/stub-runner-spring/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/loanIssuance-stubs/maven-metadata-local.xml @@ -0,0 +1,11 @@ + + + io.codearte.accurest.stubs + loanIssuance-stubs + + + 0.0.1-SNAPSHOT + + 20160326150924 + + diff --git a/stub-runner/stub-runner/README.md b/stub-runner/stub-runner/README.md new file mode 100644 index 0000000000..c52bdf7b9e --- /dev/null +++ b/stub-runner/stub-runner/README.md @@ -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. \ No newline at end of file diff --git a/stub-runner/stub-runner/build.gradle b/stub-runner/stub-runner/build.gradle new file mode 100644 index 0000000000..63c8faa6ff --- /dev/null +++ b/stub-runner/stub-runner/build.gradle @@ -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 + } + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/Arguments.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/Arguments.groovy new file mode 100644 index 0000000000..cccf0f91aa --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/Arguments.groovy @@ -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 + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/AvailablePortScanner.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/AvailablePortScanner.groovy new file mode 100644 index 0000000000..3b2df9f9b5 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/AvailablePortScanner.groovy @@ -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 tryToExecuteWithFreePort(Closure 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 executeLogicForAvailablePort(int portToScan, Closure 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]") + } + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunner.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunner.groovy new file mode 100644 index 0000000000..3281ca38a7 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunner.groovy @@ -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 stubRunners + + BatchStubRunner(Iterable stubRunners) { + this.stubRunners = stubRunners + } + + @Override + RunningStubs runStubs() { + Map appsAndPorts = stubRunners.inject([:]) { Map acc, StubRunner value -> + acc.putAll(value.runStubs().namesAndPorts) + return acc + } as Map + 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() + } + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerFactory.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerFactory.groovy new file mode 100644 index 0000000000..18652eb767 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerFactory.groovy @@ -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 dependencies + + BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection dependencies) { + this.stubRunnerOptions = stubRunnerOptions + this.dependencies = dependencies + } + + BatchStubRunner buildBatchStubRunner() { + StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, dependencies) + return new BatchStubRunner(stubRunnerFactory.createStubsFromServiceConfiguration()) + } + +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/MappingDescriptor.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/MappingDescriptor.groovy new file mode 100644 index 0000000000..18357b82a9 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/MappingDescriptor.groovy @@ -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')) + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/RunningStubs.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/RunningStubs.groovy new file mode 100644 index 0000000000..c2f0572d3f --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/RunningStubs.groovy @@ -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 namesAndPorts + + RunningStubs(Map map) { + this.namesAndPorts = map + } + + @Override + String toString() { + return namesAndPorts.collect { + "Stub [${it.key.toColonSeparatedDependencyNotation()}] is running on port [${it.value}]" + }.join("\n") + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubConfiguration.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubConfiguration.groovy new file mode 100644 index 0000000000..1ab0c82e0c --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubConfiguration.groovy @@ -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 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) + } + +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubDownloader.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubDownloader.groovy new file mode 100644 index 0000000000..94171feb7e --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubDownloader.groovy @@ -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() + } + + } + +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubFinder.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubFinder.groovy new file mode 100644 index 0000000000..f608436c2a --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubFinder.groovy @@ -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) +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRepository.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRepository.groovy new file mode 100644 index 0000000000..147b24b390 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRepository.groovy @@ -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 getProjectDescriptors() { + List mappingDescriptors = [] + mappingDescriptors.addAll(contextDescriptors()) + return mappingDescriptors + } + + private List contextDescriptors() { + return path.exists() ? collectMappingDescriptors(path) : [] + } + + private List collectMappingDescriptors(File descriptorsDirectory) { + List 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') + } + +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunner.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunner.groovy new file mode 100644 index 0000000000..fd580d3e99 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunner.groovy @@ -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() + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutor.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutor.groovy new file mode 100644 index 0000000000..a12707c12b --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutor.groovy @@ -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 mappings = repository.getProjectDescriptors() + stubServer = portScanner.tryToExecuteWithFreePort { int availablePort -> + return new StubServer(availablePort, stubConfiguration, mappings).start() + } + } + +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerFactory.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerFactory.groovy new file mode 100644 index 0000000000..d62b876a26 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerFactory.groovy @@ -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 collaborators + private final StubDownloader stubDownloader + + StubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection collaborators) { + this(stubRunnerOptions, collaborators, new StubDownloader()) + } + + protected StubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection collaborators, + StubDownloader stubDownloader) { + this.stubRunnerOptions = stubRunnerOptions + this.collaborators = collaborators + this.stubDownloader = stubDownloader + } + + Collection 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)) + } + +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerMain.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerMain.groovy new file mode 100644 index 0000000000..86002bf2db --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerMain.groovy @@ -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 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 + } + } + +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerOptions.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerOptions.groovy new file mode 100644 index 0000000000..8e6b3d7cd0 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunnerOptions.groovy @@ -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() {} +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunning.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunning.groovy new file mode 100644 index 0000000000..15941448b4 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubRunning.groovy @@ -0,0 +1,9 @@ +package io.codearte.accurest.stubrunner + +interface StubRunning extends Closeable, StubFinder { + /** + * Runs the stubs and returns the {@link RunningStubs} + */ + RunningStubs runStubs() + +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubServer.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubServer.groovy new file mode 100644 index 0000000000..830a3a22f5 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubServer.groovy @@ -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 mappings + + StubServer(int port, StubConfiguration stubConfiguration, Collection 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 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))) + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StringUtils.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StringUtils.groovy new file mode 100644 index 0000000000..16b8f35db1 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StringUtils.groovy @@ -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 + //----------------------------------------------------------------------- + /** + *

Checks if a String is empty ("") or null.

+ * + *
+	 * StringUtils.isEmpty(null)      = true
+	 * StringUtils.isEmpty("")        = true
+	 * StringUtils.isEmpty(" ")       = false
+	 * StringUtils.isEmpty("bob")     = false
+	 * StringUtils.isEmpty("  bob  ") = false
+	 * 
+ * + *

NOTE: This method changed in Lang version 2.0. + * It no longer trims the String. + * That functionality is available in isBlank().

+ * + * @param str the String to check, may be null + * @return true 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 + } + + /** + *

Gets the substring before the last occurrence of a separator. + * The separator is not returned.

+ * + *

A null string input will return null. + * An empty ("") string input will return the empty string. + * An empty or null separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+	 * 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"
+	 * 
+ * + * @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, + * null 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); + } + + /** + *

Gets the substring after the last occurrence of a separator. + * The separator is not returned.

+ * + *

A null string input will return null. + * An empty ("") string input will return the empty string. + * An empty or null separator will return the empty string if + * the input string is not null.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+	 * 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")     = ""
+	 * 
+ * + * @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, + * null 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()); + } +} diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StubsParser.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StubsParser.groovy new file mode 100644 index 0000000000..9749dec904 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/StubsParser.groovy @@ -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 + * + *
    + *
  • groupid:artifactid:classifier
  • + *
  • groupid:artifactid
  • + *
+ * + * In the latter case the provided default stub classifier will be passed. + * + * Example: + * + * "a:b,c:d:e" + */ + static Set fromString(String list, String defaultClassifier) { + return list.split(',').findAll { it }.collect { String string -> + new StubConfiguration(string, defaultClassifier) + } as Set + } + + static Set fromString(Collection collection, String defaultClassifier) { + return collection.findAll { it }.collect { String string -> + new StubConfiguration(string, defaultClassifier) + } as Set + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/ZipCategory.groovy b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/ZipCategory.groovy new file mode 100644 index 0000000000..1877da70d4 --- /dev/null +++ b/stub-runner/stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/util/ZipCategory.groovy @@ -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 https://github.com/timyates/groovy-common-extensions. + * + * 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 destination + * 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 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 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.") + } +} diff --git a/stub-runner/stub-runner/src/main/resources/accurestStubrunnerGrapeConfig.xml b/stub-runner/stub-runner/src/main/resources/accurestStubrunnerGrapeConfig.xml new file mode 100644 index 0000000000..2054715610 --- /dev/null +++ b/stub-runner/stub-runner/src/main/resources/accurestStubrunnerGrapeConfig.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/AvailablePortScannerSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/AvailablePortScannerSpec.groovy new file mode 100644 index 0000000000..7e7aee8b69 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/AvailablePortScannerSpec.groovy @@ -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" + } +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerSpec.groovy new file mode 100644 index 0000000000..71935f62f9 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/BatchStubRunnerSpec.groovy @@ -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 runners() { + StubRunner runner = Mock(StubRunner) + runner.findStubUrl("group", "knownArtifact") >> KNOWN_STUB_URL + runner.findStubUrl("group", "unknownArtifact") >> null + return [runner] + } + +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/MappingDescriptorSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/MappingDescriptorSpec.groovy new file mode 100644 index 0000000000..394cabf2d8 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/MappingDescriptorSpec.groovy @@ -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' + } + } +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRepositorySpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRepositorySpec.groovy new file mode 100644 index 0000000000..337508fce0 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRepositorySpec.groovy @@ -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 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 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) + } +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutorSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutorSpec.groovy new file mode 100644 index 0000000000..7b086f7458 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerExecutorSpec.groovy @@ -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") + } + +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerFactorySpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerFactorySpec.groovy new file mode 100644 index 0000000000..d7cf552c8c --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerFactorySpec.groovy @@ -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 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 stubRunners = collectOnlyPresentValues(factory.createStubsFromServiceConfiguration()) + then: + stubRunners.size() == 2 + } + + private List collectOnlyPresentValues(Collection stubRunners) { + return stubRunners.findAll { it != null } + } +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerSpec.groovy new file mode 100644 index 0000000000..1d63e28bfd --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubRunnerSpec.groovy @@ -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) + } + +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubServerSpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubServerSpec.groovy new file mode 100644 index 0000000000..14d71ac9e3 --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/StubServerSpec.groovy @@ -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 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 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() + } +} diff --git a/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/util/ZipCategorySpec.groovy b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/util/ZipCategorySpec.groovy new file mode 100644 index 0000000000..2a3f5e16ef --- /dev/null +++ b/stub-runner/stub-runner/src/test/groovy/io/codearte/accurest/stubrunner/util/ZipCategorySpec.groovy @@ -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' + } + +} diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/bar/bar.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/bar/bar.json new file mode 100644 index 0000000000..0b71cbf35e --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/bar/bar.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/bar" + }, + "response": { + "status": 200, + "body": "bar", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/bar/foobar.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/bar/foobar.json new file mode 100644 index 0000000000..e231b2c0c5 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/bar/foobar.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/foobar" + }, + "response": { + "status": 200, + "body": "foobar", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/foo.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/foo.json new file mode 100644 index 0000000000..209061ef5e --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/mappings/com/ofg/foo/foo.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/foo" + }, + "response": { + "status": 200, + "body": "foo", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/brokers.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/brokers.json new file mode 100644 index 0000000000..bfc64f392c --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/brokers.json @@ -0,0 +1,5 @@ +{ + "pl": [ + "com/ofg/bar" + ] +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/nested/anotherDescriptor.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/nested/anotherDescriptor.json new file mode 100644 index 0000000000..fe635a1b3a --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/brokers/nested/anotherDescriptor.json @@ -0,0 +1,5 @@ +{ + "pl": [ + "com/ofg/foo/bar" + ] +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/descriptor.json b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/descriptor.json new file mode 100644 index 0000000000..7ecd1e1ed6 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/anotherRepository/projects/descriptor.json @@ -0,0 +1,5 @@ +{ + "pl": [ + "com/ofg/foo" + ] +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/emptyrepo/.gitkeep b/stub-runner/stub-runner/src/test/resources/emptyrepo/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/stub-runner/stub-runner/src/test/resources/file.zip b/stub-runner/stub-runner/src/test/resources/file.zip new file mode 100644 index 0000000000..3da3a91240 Binary files /dev/null and b/stub-runner/stub-runner/src/test/resources/file.zip differ diff --git a/stub-runner/stub-runner/src/test/resources/logback.xml b/stub-runner/stub-runner/src/test/resources/logback.xml new file mode 100644 index 0000000000..0cfb35f4cd --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/admin/admin.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/admin/admin.json new file mode 100644 index 0000000000..f730b7e3cf --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/admin/admin.json @@ -0,0 +1,9 @@ +{ + "request": { + "method": "GET", + "url": "/admin" + }, + "response": { + "status": 401 + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/bye.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/bye.json new file mode 100644 index 0000000000..1a65830d4a --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/bye/bye.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/bye" + }, + "response": { + "status": 200, + "body": "Goodbye world!", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/README.md b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/README.md new file mode 100644 index 0000000000..484f8efe77 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/README.md @@ -0,0 +1 @@ +### Hello service stub \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/admin/admin.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/admin/admin.json new file mode 100644 index 0000000000..f730b7e3cf --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/admin/admin.json @@ -0,0 +1,9 @@ +{ + "request": { + "method": "GET", + "url": "/admin" + }, + "response": { + "status": 401 + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/hello.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/hello.json new file mode 100644 index 0000000000..3cd9f7374b --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/hello/hello.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/hello" + }, + "response": { + "status": 200, + "body": "Hello world!", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/ping/ping.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/ping/ping.json new file mode 100644 index 0000000000..db01229c52 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/com/ofg/ping/ping.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/ping" + }, + "response": { + "status": 200, + "body": "pong", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/lv/com/ofg/bye/lv_bye.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/lv/com/ofg/bye/lv_bye.json new file mode 100644 index 0000000000..a062cd4674 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/lv/com/ofg/bye/lv_bye.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/lv/bye" + }, + "response": { + "status": 200, + "body": "Another goodbye world!", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_bye.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_bye.json new file mode 100644 index 0000000000..b7c5e4ebc9 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_bye.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "GET", + "url": "/pl/bye" + }, + "response": { + "status": 200, + "body": "pl-bye", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_overridden_bye.json b/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_overridden_bye.json new file mode 100644 index 0000000000..9c54c98914 --- /dev/null +++ b/stub-runner/stub-runner/src/test/resources/repository/mappings/pl/com/ofg/bye/pl_overridden_bye.json @@ -0,0 +1,14 @@ +{ + + "request": { + "method": "GET", + "url": "/bye" + }, + "response": { + "status": 200, + "body": "overridden-bye", + "headers": { + "Content-Type": "text/plain" + } + } +} \ No newline at end of file