Added support for Camel messaging

This commit is contained in:
Marcin Grzejszczak
2016-04-22 23:01:48 +02:00
parent 577619cb2c
commit f6cfd90d3d
38 changed files with 671 additions and 47 deletions

View File

@@ -58,7 +58,7 @@ class SingleTestGenerator {
File stubsFile = it.path.toFile()
log.debug("Stub content from file [${stubsFile.text}]")
GroovyDsl stubContent = new GroovyShell(delegate.class.classLoader, new Binding(), new CompilerConfiguration(sourceEncoding:'UTF-8')).evaluate(stubsFile)
TestType testType = (stubContent.inputMessage || stubContent.outputMessage) ? TestType.MESSAGING : TestType.HTTP
TestType testType = (stubContent.input || stubContent.outputMessage) ? TestType.MESSAGING : TestType.HTTP
return [(new ParsedDsl(it, stubContent, stubsFile)) : testType]
}

View File

@@ -0,0 +1,52 @@
package io.codearte.accurest.builder
import groovy.json.JsonOutput
import groovy.json.StringEscapeUtils
import io.codearte.accurest.dsl.internal.DslProperty
import io.codearte.accurest.util.MapConverter
import static io.codearte.accurest.util.ContentUtils.extractValue
/**
* Class that constructs a String from Body
*
* @author Marcin Grzejszczak
*/
class BodyAsString {
static String extractServerValueFrom(Object body) {
Object bodyValue = extractServerValueFromBody(body)
String json = new JsonOutput().toJson(bodyValue)
json = StringEscapeUtils.unescapeJavaScript(json)
return trimRepeatedQuotes(json)
}
static String extractClientValueFrom(Object body) {
Object bodyValue = extractClientValueFromBody(body);
String json = new JsonOutput().toJson(bodyValue)
json = StringEscapeUtils.unescapeJavaScript(json)
return trimRepeatedQuotes(json)
}
private static String trimRepeatedQuotes(String toTrim) {
return toTrim.startsWith('"') ? toTrim.replaceAll('"', '') : toTrim
}
private static Object extractServerValueFromBody(bodyValue) {
if (bodyValue instanceof GString) {
bodyValue = extractValue(bodyValue, { DslProperty dslProperty -> dslProperty.serverValue })
} else {
bodyValue = MapConverter.transformValues(bodyValue, { it instanceof DslProperty ? it.serverValue : it })
}
return bodyValue
}
private static Object extractClientValueFromBody(bodyValue) {
if (bodyValue instanceof GString) {
bodyValue = extractValue(bodyValue, { DslProperty dslProperty -> dslProperty.clientValue })
} else {
bodyValue = MapConverter.transformValues(bodyValue, { it instanceof DslProperty ? it.clientValue : it })
}
return bodyValue
}
}

View File

@@ -22,7 +22,7 @@ abstract class MessagingMethodBodyBuilder extends MethodBodyBuilder {
protected final OutputMessage outputMessage
MessagingMethodBodyBuilder(GroovyDsl stubDefinition) {
this.inputMessage = stubDefinition.inputMessage
this.inputMessage = stubDefinition.input
this.outputMessage = stubDefinition.outputMessage
}

View File

@@ -44,7 +44,7 @@ class MethodBuilder {
}
private MethodBodyBuilder getMethodBodyBuilder() {
if (stubContent.inputMessage || stubContent.outputMessage) {
if (stubContent.input || stubContent.outputMessage) {
if (configProperties.targetFramework == TestFramework.JUNIT){
return new JUnitMessagingMethodBodyBuilder(stubContent)
}

View File

@@ -18,7 +18,7 @@ class GroovyDsl {
Response response
String label
String description
Input inputMessage
Input input
OutputMessage outputMessage
static GroovyDsl make(Closure closure) {
@@ -53,8 +53,8 @@ class GroovyDsl {
}
void input(@DelegatesTo(Input) Closure closure) {
this.inputMessage = new Input()
closure.delegate = inputMessage
this.input = new Input()
closure.delegate = input
closure()
}

View File

@@ -26,6 +26,14 @@ class Headers {
}
}
Map<String , Object> asStubSideMap() {
def acc = [:].withDefault { [] as Collection<Object> }
return entries.inject(acc as Map<String, Object>) { Map<String, Object> map, Header header ->
map[header.name] = header.clientValue
return map
} as Map<String , Object>
}
boolean equals(o) {
if (this.is(o)) return true
if (getClass() != o.class) return false

View File

@@ -0,0 +1,14 @@
package io.codearte.accurest.messaging;
/**
* Contract for filtering out messages that do not match the structure in the Accurest DSL
*
* @author Marcin Grzejszczak
*/
public interface AccurestFilter<PAYLOAD, TYPE_TO_CONVERT_INTO> {
/**
* @return @{code true} if the message should be passed through, @{code false} if the message should be filtered out,
*/
boolean matches(AccurestMessage<PAYLOAD, TYPE_TO_CONVERT_INTO> message);
}

View File

@@ -1,14 +1,20 @@
include 'docs'
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-spring-cloud'
include ':stub-runner:stub-runner-junit'
include ':accurest-messaging:accurest-messaging-core'
include ':accurest-messaging:accurest-messaging-integration'
include ':accurest-messaging:accurest-messaging-stream'
include ':accurest-messaging:accurest-messaging-camel'
include ':stub-runner:stub-runner'
include ':stub-runner:stub-runner-spring'
include ':stub-runner:stub-runner-spring-cloud'
include ':stub-runner:stub-runner-junit'
include ':stub-runner:stub-runner-messaging:stub-runner-messaging-integration'
include ':stub-runner:stub-runner-messaging:stub-runner-messaging-stream'
include ':stub-runner:stub-runner-messaging:stub-runner-messaging-camel'
include ':samples:messaging-stream'
include ':samples:messaging-integration'
include ':samples:messaging-camel'
@@ -18,4 +24,5 @@ rootProject.name = "accurest"
//to prevent StackOverflow in Sonar
project(":stub-runner").name = "stub-runner-root"
project(":accurest-messaging").name = "accurest-messaging-root"
project(":accurest-messaging").name = "accurest-messaging-root"
project(":stub-runner:stub-runner-messaging").name = "stub-runner-messaging-root"

View File

@@ -6,12 +6,14 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import io.codearte.accurest.dsl.GroovyDsl;
import io.codearte.accurest.stubrunner.BatchStubRunner;
import io.codearte.accurest.stubrunner.BatchStubRunnerFactory;
import io.codearte.accurest.stubrunner.RunningStubs;
@@ -161,4 +163,24 @@ public class AccurestRule implements TestRule, StubFinder {
public RunningStubs findAllRunningStubs() {
return stubFinder.findAllRunningStubs();
}
@Override
public Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts() {
return stubFinder.getAccurestContracts();
}
@Override
public void trigger(String ivyNotation, String labelName) {
throw new UnsupportedOperationException("Feature not yet supported");
}
@Override
public void trigger(String labelName) {
throw new UnsupportedOperationException("Feature not yet supported");
}
@Override
public void trigger() {
throw new UnsupportedOperationException("Feature not yet supported");
}
}

View File

@@ -0,0 +1,25 @@
repositories {
mavenLocal()
jcenter()
maven {
url "http://repo.spring.io/snapshot"
}
maven {
url "http://repo.spring.io/milestone"
}
}
dependencies {
compile project(':stub-runner-root:stub-runner-spring')
compile project(':accurest-messaging-root:accurest-messaging-camel')
compile 'org.apache.camel:camel-spring-boot-starter:2.17.0'
compile 'org.apache.camel:camel-jackson:2.17.0'
testCompile 'org.apache.camel:camel-jms:2.17.0'
testCompile 'org.apache.activemq:activemq-camel:5.12.1'
testCompile 'org.apache.activemq:activemq-pool:5.12.1'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}
}

View File

@@ -0,0 +1,35 @@
package io.codearte.accurest.stubrunner.messaging.camel
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.stubrunner.BatchStubRunner
import io.codearte.accurest.stubrunner.StubConfiguration
import org.apache.camel.RoutesBuilder
import org.apache.camel.spring.SpringRouteBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Camel configuration that iterates over the downloaded Groovy DSLs
* and registers a route for each DSL.
*
* @author Marcin Grzejszczak
*/
@Configuration
class StubRunnerCamelConfiguration {
@Bean
RoutesBuilder myRouter(BatchStubRunner batchStubRunner) {
return new SpringRouteBuilder() {
@Override
public void configure() throws Exception {
Map<StubConfiguration, Collection<GroovyDsl>> accurestContracts = batchStubRunner.accurestContracts
(accurestContracts.values().flatten() as Collection<GroovyDsl>).findAll { it?.input?.messageFrom && it?.outputMessage?.sentTo }.each {
from(it.input.messageFrom)
.filter(new StubRunnerCamelPredicate(it))
.process(new StubRunnerCamelProcessor(it))
.to(it.outputMessage.sentTo)
}
}
};
}
}

View File

@@ -0,0 +1,58 @@
package io.codearte.accurest.stubrunner.messaging.camel
import com.fasterxml.jackson.databind.ObjectMapper
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import com.toomuchcoding.jsonassert.JsonVerifiable
import groovy.transform.PackageScope
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.util.JsonPaths
import io.codearte.accurest.util.JsonToJsonPathsConverter
import org.apache.camel.Exchange
import org.apache.camel.Predicate
/**
* Passes through a message that matches the one defined in the DSL
*
* @author Marcin Grzejszczak
*/
@PackageScope
class StubRunnerCamelPredicate implements Predicate {
private final GroovyDsl groovyDsl
private final ObjectMapper objectMapper = new ObjectMapper()
StubRunnerCamelPredicate(GroovyDsl groovyDsl) {
this.groovyDsl = groovyDsl
}
@Override
boolean matches(Exchange exchange) {
if(!headersMatch(exchange)){
return false
}
Object inputMessage = exchange.in.body
JsonPaths jsonPaths = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValues(groovyDsl.input.messageBody)
DocumentContext parsedJson = JsonPath.parse(objectMapper.writeValueAsString(inputMessage))
return jsonPaths.every { matchesJsonPath(parsedJson, it) }
}
private boolean matchesJsonPath(DocumentContext parsedJson, JsonVerifiable jsonVerifiable) {
try {
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonVerifiable.jsonPath())
return true
} catch (Exception e) {
return false
}
}
private boolean headersMatch(Exchange exchange) {
Map<String, Object> headers = exchange.getIn().getHeaders()
return groovyDsl.input.messageHeaders.entries.every {
String name = it.name
Object value = it.clientValue
return headers.get(name) == value
}
}
}

View File

@@ -0,0 +1,36 @@
package io.codearte.accurest.stubrunner.messaging.camel
import groovy.transform.PackageScope
import io.codearte.accurest.builder.BodyAsString
import io.codearte.accurest.dsl.GroovyDsl
import org.apache.camel.Exchange
import org.apache.camel.Message
import org.apache.camel.Processor
/**
* Sends forward a message defined in the DSL. Also removes headers from the
* input message and provides the headers from the DSL.
*
* @author Marcin Grzejszczak
*/
@PackageScope
class StubRunnerCamelProcessor implements Processor {
private final GroovyDsl groovyDsl
StubRunnerCamelProcessor(GroovyDsl groovyDsl) {
this.groovyDsl = groovyDsl
}
@Override
void process(Exchange exchange) throws Exception {
Message input = exchange.in
input.body = BodyAsString.extractClientValueFrom(groovyDsl.outputMessage.body)
groovyDsl.input.messageHeaders.entries.each {
input.removeHeader(it.name)
}
groovyDsl.outputMessage.headers.entries.each {
input.setHeader(it.name, it.clientValue)
}
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.codearte.accurest.stubrunner.messaging.camel.StubRunnerCamelConfiguration

View File

@@ -0,0 +1,16 @@
package io.codearte.accurest.stubrunner.messaging.camel
import com.fasterxml.jackson.annotation.JsonCreator
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
@CompileStatic
@EqualsAndHashCode
class BookReturned implements Serializable {
final String bookName
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
BookReturned(String bookName) {
this.bookName = bookName
}
}

View File

@@ -0,0 +1,108 @@
package io.codearte.accurest.stubrunner.messaging.camel
import com.fasterxml.jackson.databind.ObjectMapper
import io.codearte.accurest.stubrunner.StubFinder
import org.apache.activemq.camel.component.ActiveMQComponent
import org.apache.camel.CamelContext
import org.apache.camel.Exchange
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
@ContextConfiguration(classes = CamelStubRunnerSpec, loader = SpringApplicationContextLoader)
class CamelStubRunnerSpec extends Specification {
@Autowired StubFinder stubFinder
@Autowired CamelContext camelContext
def 'should download the stub and register a route for it'() {
when:
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{"bookName":"foo"}'
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a message by label'() {
when:
stubFinder.trigger('return_book_1')
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing groupId:artifactId'() {
when:
stubFinder.trigger('io.codearte.accurest.stubs:camelService', 'return_book_1')
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing artifactId'() {
when:
stubFinder.trigger('camelService', 'return_book_1')
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
def 'should not run any wrong trigger when missing label is passed'() {
when:
stubFinder.trigger('missing label')
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 100)
and:
receivedMessage == null
}
def 'should not run any wrong trigger when missing label and artifactid is passed'() {
when:
stubFinder.trigger('some:service', 'return_book_1')
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 100)
and:
receivedMessage == null
}
def 'should trigger messages by running all triggers'() {
given:
ObjectMapper objectMapper = new ObjectMapper()
when:
stubFinder.trigger()
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
objectMapper.writeValueAsString(receivedMessage.in.body) == '{"bookName":"foo"}'
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@Bean
ActiveMQComponent activeMQComponent(@Value('${activemq.url:vm://localhost?broker.persistent=false}') String url) {
return new ActiveMQComponent(brokerURL: url)
}
}

View File

@@ -0,0 +1,2 @@
stubrunner.stubs.repository.root: classpath:m2repo/repository/
stubrunner.stubs.ids: io.codearte.accurest.stubs:camelService

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>camelService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>camelService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>camelService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -3,11 +3,14 @@ package io.codearte.accurest.stubrunner.spring;
import java.io.IOException;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
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.messaging.AccurestMessaging;
import io.codearte.accurest.messaging.noop.NoOpAccurestMessaging;
import io.codearte.accurest.stubrunner.BatchStubRunner;
import io.codearte.accurest.stubrunner.BatchStubRunnerFactory;
import io.codearte.accurest.stubrunner.StubConfiguration;
@@ -22,6 +25,8 @@ import io.codearte.accurest.stubrunner.util.StubsParser;
@Configuration
public class StubRunnerConfiguration {
@Autowired(required = false) AccurestMessaging accurestMessaging;
/**
* 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
@@ -45,7 +50,8 @@ public class StubRunnerConfiguration {
StubRunnerOptions stubRunnerOptions = new StubRunnerOptions(minPortValue, maxPortValue, uriStringOrEmpty(stubRepositoryRoot),
stubRepositoryRoot == null || workOffline, stubsSuffix);
Set<StubConfiguration> dependencies = StubsParser.fromString(stubs, stubsSuffix);
BatchStubRunner batchStubRunner = new BatchStubRunnerFactory(stubRunnerOptions, dependencies).buildBatchStubRunner();
BatchStubRunner batchStubRunner = new BatchStubRunnerFactory(stubRunnerOptions, dependencies,
accurestMessaging != null ? accurestMessaging : new NoOpAccurestMessaging()).buildBatchStubRunner();
// TODO: Consider running it in a separate thread
batchStubRunner.runStubs();
return batchStubRunner;

View File

@@ -0,0 +1,3 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.codearte.accurest.stubrunner.spring.StubRunnerConfiguration

View File

@@ -5,6 +5,7 @@ mainClassName = 'io.codearte.accurest.stubrunner.StubRunnerMain'
dependencies {
compile project(':accurest-core')
compile project(':accurest-messaging-root:accurest-messaging-core')
compile 'org.apache.ivy:ivy:2.4.0'
compile localGroovy()
compile "com.github.tomakehurst:wiremock:$wiremockVersion"

View File

@@ -1,7 +1,7 @@
package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import io.codearte.accurest.dsl.GroovyDsl
/**
* Manages lifecycle of multiple {@link StubRunner} instances.
*
@@ -48,6 +48,29 @@ class BatchStubRunner implements StubRunning {
return new RunningStubs(stubRunners.collect { StubRunner runner -> runner.findAllRunningStubs() })
}
@Override
Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts() {
return stubRunners.inject([:]) { Map<StubConfiguration, Collection<GroovyDsl>> map, StubRunner stubRunner ->
map.putAll(stubRunner.accurestContracts)
return map
} as Map<StubConfiguration, Collection<GroovyDsl>>
}
@Override
void trigger(String ivyNotation, String labelName) {
stubRunners.each { it.trigger(ivyNotation, labelName) }
}
@Override
void trigger(String labelName) {
stubRunners.each { it.trigger(labelName) }
}
@Override
void trigger() {
stubRunners.each { it.trigger() }
}
@Override
void close() throws IOException {
stubRunners.each {

View File

@@ -1,6 +1,8 @@
package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import io.codearte.accurest.messaging.AccurestMessaging
import io.codearte.accurest.messaging.noop.NoOpAccurestMessaging
/**
* Manages lifecycle of multiple {@link StubRunner} instances.
@@ -14,21 +16,29 @@ class BatchStubRunnerFactory {
private final StubRunnerOptions stubRunnerOptions
private final Collection<StubConfiguration> dependencies
private final StubDownloader stubDownloader
private final AccurestMessaging accurestMessaging
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection<StubConfiguration> dependencies) {
this(stubRunnerOptions, dependencies, new GrapeStubDownloader())
this(stubRunnerOptions, dependencies, new GrapeStubDownloader(), new NoOpAccurestMessaging())
}
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, Collection<StubConfiguration> dependencies,
AccurestMessaging accurestMessaging) {
this(stubRunnerOptions, dependencies, new GrapeStubDownloader(), accurestMessaging)
}
BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions,
Collection<StubConfiguration> dependencies,
StubDownloader stubDownloader) {
StubDownloader stubDownloader,
AccurestMessaging accurestMessaging) {
this.stubRunnerOptions = stubRunnerOptions
this.dependencies = dependencies
this.stubDownloader = stubDownloader
this.accurestMessaging = accurestMessaging
}
BatchStubRunner buildBatchStubRunner() {
StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, dependencies, stubDownloader)
StubRunnerFactory stubRunnerFactory = new StubRunnerFactory(stubRunnerOptions, dependencies, stubDownloader, accurestMessaging)
return new BatchStubRunner(stubRunnerFactory.createStubsFromServiceConfiguration())
}

View File

@@ -11,7 +11,7 @@ import io.codearte.accurest.dsl.GroovyDsl
@CompileStatic
class GroovyDslWrapper {
@Delegate private final GroovyDsl groovyDsl
@Delegate final GroovyDsl groovyDsl
GroovyDslWrapper(GroovyDsl groovyDsl) {
this.groovyDsl = groovyDsl

View File

@@ -1,5 +1,6 @@
package io.codearte.accurest.stubrunner
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.codearte.accurest.stubrunner.util.StringUtils
@@ -51,11 +52,28 @@ public class StubConfiguration {
return StringUtils.hasText(classifier)
}
public String toColonSeparatedDependencyNotation() {
String toColonSeparatedDependencyNotation() {
if(!isDefined()) {
return ""
}
return [groupId, artifactId, classifier].join(STUB_COLON_DELIMITER)
}
@CompileDynamic
boolean matches(String ivyNotationAsString) {
def (String groupId, String artifactId) = ivyNotationFrom(ivyNotationAsString)
if (!groupId) {
return this.artifactId == artifactId
}
return this.groupId == groupId && this.artifactId == artifactId
}
private String[] ivyNotationFrom(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length == 1) {
// assuming that ivy notation represents artifactId only
return [null, splitString[0]] as String[]
}
return [splitString[0], splitString[1]] as String[]
}
}

View File

@@ -2,6 +2,8 @@ package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.codearte.accurest.dsl.GroovyDsl
/**
* @author Marcin Grzejszczak
*/
@@ -9,5 +11,5 @@ import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode
class StubData {
final Integer port
final List<GroovyDslWrapper> contracts
final List<GroovyDsl> contracts
}

View File

@@ -1,6 +1,8 @@
package io.codearte.accurest.stubrunner
interface StubFinder {
import io.codearte.accurest.dsl.GroovyDsl
interface StubFinder extends StubTriggerer {
/**
* For the given groupId and artifactId tries to find the matching
* URL of the running stub.
@@ -23,4 +25,9 @@ interface StubFinder {
* Returns all running stubs
*/
RunningStubs findAllRunningStubs()
/**
* Returns the list of Accurest contracts
*/
Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts()
}

View File

@@ -15,16 +15,23 @@ import org.codehaus.groovy.control.CompilerConfiguration
class StubRepository {
private final File path
final List<WiremockMappingDescriptor> projectDescriptors
final Collection<GroovyDsl> accurestContracts
StubRepository(File repository) {
if (!repository.isDirectory()) {
throw new FileNotFoundException('Missing descriptor repository')
throw new FileNotFoundException("Missing descriptor repository under path [$path]")
}
this.path = repository
this.projectDescriptors = projectDescriptors()
this.accurestContracts = accurestContracts()
}
Collection<GroovyDslWrapper> getAccurestContracts() {
List<GroovyDslWrapper> contracts = []
/**
* Returns a list of {@link GroovyDsl}
*/
private Collection<GroovyDsl> accurestContracts() {
List<GroovyDsl> contracts = []
contracts.addAll(accurestDescriptors())
return contracts
}
@@ -32,7 +39,7 @@ class StubRepository {
/**
* Returns the list of WireMock JSON files wrapped in {@link WiremockMappingDescriptor}
*/
List<WiremockMappingDescriptor> getProjectDescriptors() {
private List<WiremockMappingDescriptor> projectDescriptors() {
List<WiremockMappingDescriptor> mappingDescriptors = []
mappingDescriptors.addAll(contextDescriptors())
return mappingDescriptors
@@ -52,18 +59,17 @@ class StubRepository {
return mappingDescriptors
}
private Collection<GroovyDslWrapper> accurestDescriptors() {
private Collection<GroovyDsl> accurestDescriptors() {
return path.exists() ? collectAccurestDescriptors(path) : []
}
private Collection<GroovyDslWrapper> collectAccurestDescriptors(File descriptorsDirectory) {
List<GroovyDslWrapper> mappingDescriptors = []
private Collection<GroovyDsl> collectAccurestDescriptors(File descriptorsDirectory) {
List<GroovyDsl> mappingDescriptors = []
descriptorsDirectory.eachFileRecurse { File file ->
if (isAccurestDescriptor(file)) {
try {
mappingDescriptors << new GroovyDslWrapper(
mappingDescriptors <<
((new GroovyShell(delegate.class.classLoader, new Binding(), new CompilerConfiguration(sourceEncoding:'UTF-8')).evaluate(file)) as GroovyDsl)
)
} catch (Exception e) {
log.warn("Exception occurred while trying to parse file [$file]", e)
}

View File

@@ -2,6 +2,10 @@ package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.messaging.AccurestMessaging
import io.codearte.accurest.messaging.noop.NoOpAccurestMessaging
/**
* Represents a single instance of ready-to-run stubs.
* Can run the stubs and then will return the name of the collaborator together with
@@ -16,28 +20,34 @@ class StubRunner implements StubRunning {
private final StubRepository stubRepository
private final StubConfiguration stubsConfiguration
private final StubRunnerOptions stubRunnerOptions
private StubRunnerExecutor localStubRunner
private final StubRunnerExecutor localStubRunner
private final AccurestMessaging accurestMessaging
@Deprecated
StubRunner(Arguments arguments) {
stubsConfiguration = arguments.stub
stubRunnerOptions = arguments.stubRunnerOptions
this.stubsConfiguration = arguments.stub
this.stubRunnerOptions = arguments.stubRunnerOptions
this.stubRepository = new StubRepository(new File(arguments.repositoryPath))
AvailablePortScanner portScanner = new AvailablePortScanner(stubRunnerOptions.minPortValue,
stubRunnerOptions.maxPortValue)
this.accurestMessaging = new NoOpAccurestMessaging()
this.localStubRunner = new StubRunnerExecutor(portScanner, accurestMessaging)
}
StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath, StubConfiguration stubsConfiguration) {
StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath, StubConfiguration stubsConfiguration,
AccurestMessaging accurestMessaging) {
this.stubsConfiguration = stubsConfiguration
this.stubRunnerOptions = stubRunnerOptions
this.stubRepository = new StubRepository(new File(repositoryPath))
AvailablePortScanner portScanner = new AvailablePortScanner(stubRunnerOptions.minPortValue,
stubRunnerOptions.maxPortValue)
this.accurestMessaging = accurestMessaging
this.localStubRunner = new StubRunnerExecutor(portScanner, accurestMessaging)
}
@Override
RunningStubs runStubs() {
AvailablePortScanner portScanner = new AvailablePortScanner(stubRunnerOptions.minPortValue,
stubRunnerOptions.maxPortValue)
localStubRunner = new StubRunnerExecutor(portScanner)
registerShutdownHook()
return localStubRunner.runStubs(stubRepository, stubsConfiguration)
}
@@ -50,7 +60,8 @@ class StubRunner implements StubRunning {
URL findStubUrl(String ivyNotation) {
String[] splitString = ivyNotation.split(":")
if (splitString.length == 1) {
throw new IllegalArgumentException("$ivyNotation is invalid")
// assuming that ivy notation represents artifactId only
return findStubUrl(null, splitString[0])
}
return findStubUrl(splitString[0], splitString[1])
}
@@ -60,6 +71,26 @@ class StubRunner implements StubRunning {
return localStubRunner.findAllRunningStubs()
}
@Override
Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts() {
return localStubRunner.getAccurestContracts()
}
@Override
void trigger(String ivyNotation, String labelName) {
localStubRunner.trigger(ivyNotation, labelName)
}
@Override
void trigger(String labelName) {
localStubRunner.trigger(labelName)
}
@Override
void trigger() {
localStubRunner.trigger()
}
private void registerShutdownHook() {
Runnable stopAllServers = { this.close() }
Runtime.runtime.addShutdownHook(new Thread(stopAllServers))

View File

@@ -2,6 +2,11 @@ package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.messaging.AccurestMessage
import io.codearte.accurest.messaging.AccurestMessaging
import io.codearte.accurest.messaging.noop.NoOpAccurestMessaging
/**
* Runs stubs for a particular {@link StubServer}
*/
@@ -10,10 +15,17 @@ import groovy.util.logging.Slf4j
class StubRunnerExecutor implements StubFinder {
private final AvailablePortScanner portScanner
private final AccurestMessaging accurestMessaging
private StubServer stubServer
StubRunnerExecutor(AvailablePortScanner portScanner, AccurestMessaging accurestMessaging) {
this.portScanner = portScanner
this.accurestMessaging = accurestMessaging
}
StubRunnerExecutor(AvailablePortScanner portScanner) {
this.portScanner = portScanner
this.accurestMessaging = new NoOpAccurestMessaging()
}
RunningStubs runStubs(StubRepository repository, StubConfiguration stubConfiguration) {
@@ -30,7 +42,7 @@ class StubRunnerExecutor implements StubFinder {
@Override
URL findStubUrl(String groupId, String artifactId) {
if(!groupId) {
if (!groupId) {
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId)
}
return returnStubUrlIfMatches(stubServer.stubConfiguration.artifactId == artifactId &&
@@ -51,13 +63,53 @@ class StubRunnerExecutor implements StubFinder {
return new RunningStubs([(stubServer.stubConfiguration) : stubServer.port])
}
@Override
Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts() {
return [(stubServer.stubConfiguration): stubServer.contracts]
}
@Override
void trigger(String ivyNotationAsString, String labelName) {
Collection<GroovyDsl> matchingContracts = getAccurestContracts().findAll {
it.key.matches(ivyNotationAsString)
}.values().flatten() as Collection<GroovyDsl>
triggerForDsls(matchingContracts, labelName)
}
@Override
void trigger(String labelName) {
triggerForDsls(getAccurestContracts().values().flatten() as Collection<GroovyDsl>, labelName)
}
private void triggerForDsls(Collection<GroovyDsl> dsls, String labelName) {
dsls.findAll { it.label == labelName}.each {
sendMessageIfApplicable(it)
}
}
@Override
void trigger() {
(getAccurestContracts().values().flatten() as Collection<GroovyDsl>).each { GroovyDsl groovyDsl ->
sendMessageIfApplicable(groovyDsl)
}
}
private void sendMessageIfApplicable(GroovyDsl groovyDsl) {
if (!groovyDsl.outputMessage) {
return
}
AccurestMessage message = accurestMessaging.create(groovyDsl.outputMessage.body.clientValue,
groovyDsl.outputMessage.headers.asStubSideMap())
accurestMessaging.send(message, groovyDsl.outputMessage.sentTo)
}
private URL returnStubUrlIfMatches(boolean condition) {
return condition ? stubServer.stubUrl : null
}
private void startStubServers(StubConfiguration stubConfiguration, StubRepository repository) {
List<WiremockMappingDescriptor> mappings = repository.getProjectDescriptors()
Collection<GroovyDslWrapper> contracts = repository.accurestContracts
Collection<GroovyDsl> contracts = repository.accurestContracts
stubServer = portScanner.tryToExecuteWithFreePort { int availablePort ->
return new StubServer(availablePort, stubConfiguration, mappings, contracts).start()
}

View File

@@ -1,9 +1,8 @@
package io.codearte.accurest.stubrunner
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import io.codearte.accurest.messaging.AccurestMessaging
/**
* Factory of StubRunners. Basing on the options and passed collaborators
* downloads the stubs and returns a list of corresponding stub runners.
@@ -15,13 +14,15 @@ class StubRunnerFactory {
private final StubRunnerOptions stubRunnerOptions
private final Collection<StubConfiguration> collaborators
private final StubDownloader stubDownloader
private final AccurestMessaging accurestMessaging
StubRunnerFactory(StubRunnerOptions stubRunnerOptions,
Collection<StubConfiguration> collaborators,
StubDownloader stubDownloader) {
StubDownloader stubDownloader, AccurestMessaging accurestMessaging) {
this.stubRunnerOptions = stubRunnerOptions
this.collaborators = collaborators
this.stubDownloader = stubDownloader
this.accurestMessaging = accurestMessaging
}
Collection<StubRunner> createStubsFromServiceConfiguration() {
@@ -42,7 +43,7 @@ class StubRunnerFactory {
private StubRunner createStubRunner(File unzippedStubsDir, StubConfiguration stubsConfiguration,
StubRunnerOptions stubRunnerOptions) {
return new StubRunner(stubRunnerOptions, unzippedStubsDir.path, stubsConfiguration)
return new StubRunner(stubRunnerOptions, unzippedStubsDir.path, stubsConfiguration, accurestMessaging)
}
}

View File

@@ -0,0 +1,18 @@
package io.codearte.accurest.stubrunner
import groovy.transform.PackageScope
import io.codearte.accurest.messaging.AccurestMessaging
/**
* @author Marcin Grzejszczak
*/
@PackageScope
class StubRunnerMessagingTrigger {
private final AccurestMessaging accurestMessaging
StubRunnerMessagingTrigger(AccurestMessaging accurestMessaging) {
this.accurestMessaging = accurestMessaging
}
void trigger
}

View File

@@ -6,6 +6,7 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import io.codearte.accurest.dsl.GroovyDsl
@CompileStatic
@Slf4j
@@ -14,10 +15,10 @@ class StubServer {
private WireMockServer wireMockServer
final StubConfiguration stubConfiguration
final Collection<WiremockMappingDescriptor> mappings
final Collection<GroovyDslWrapper> contracts
final Collection<GroovyDsl> contracts
StubServer(int port, StubConfiguration stubConfiguration, Collection<WiremockMappingDescriptor> mappings,
Collection<GroovyDslWrapper> contracts) {
Collection<GroovyDsl> contracts) {
this.stubConfiguration = stubConfiguration
this.mappings = mappings
this.wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().port(port))

View File

@@ -0,0 +1,25 @@
package io.codearte.accurest.stubrunner
interface StubTriggerer {
/**
* Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too.
*
* Feature related to messaging.
*/
void trigger(String ivyNotation, String labelName)
/**
* Triggers an event by a given label.
*
* Feature related to messaging.
*/
void trigger(String labelName)
/**
* Triggers all possible events.
*
* Feature related to messaging.
*/
void trigger()
}

View File

@@ -1,5 +1,6 @@
package io.codearte.accurest.stubrunner
import io.codearte.accurest.messaging.noop.NoOpAccurestMessaging
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
@@ -12,7 +13,7 @@ class StubRunnerFactorySpec extends Specification {
Collection<StubConfiguration> collaborators = [new StubConfiguration("a:b"), new StubConfiguration("c:d")]
StubDownloader downloader = Mock(StubDownloader)
StubRunnerOptions stubRunnerOptions = new StubRunnerOptions(stubRepositoryRoot: 'http://sth.net')
StubRunnerFactory factory = new StubRunnerFactory(stubRunnerOptions, collaborators, downloader)
StubRunnerFactory factory = new StubRunnerFactory(stubRunnerOptions, collaborators, downloader, new NoOpAccurestMessaging())
def "Should download stub definitions many times"() {
given: