Stub Runner Spring Boot version (#248)

fixes #247
This commit is contained in:
Marcin Grzejszczak
2016-04-27 16:39:11 +02:00
parent 92f3eafa0e
commit 2696569a85
34 changed files with 391 additions and 31 deletions

View File

@@ -1,4 +1,6 @@
groupId=com.ofg
jacksonMapper=1.9.13
restAssuredVersion=2.4.0
springVersion=4.1.7.RELEASE
springVersion=4.1.7.RELEASE
springBootVersion=1.3.3.RELEASE

View File

@@ -46,14 +46,14 @@ repositories {
dependencies {
compile "org.codehaus.groovy:groovy-all:2.4.5"
compile 'org.springframework.boot:spring-boot-starter-web:1.3.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-integration:1.3.3.RELEASE'
compile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-integration:${springBootVersion}"
compile fileTree(dir: messagingLibsDir, include: '*.jar')
testCompile "org.spockframework:spock-spring:1.0-groovy-2.4"
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile "ch.qos.logback:logback-classic:1.1.2"
}

View File

@@ -1,4 +1,6 @@
groupId=io.codearte
jacksonMapper=1.9.13
restAssuredVersion=2.9.0
springVersion=4.2.3.RELEASE
springVersion=4.2.3.RELEASE
springBootVersion=1.3.3.RELEASE

View File

@@ -4,4 +4,5 @@ nexusPassword =
wiremockVersion = 2.0.10-beta
jsonassertVersion = 0.2.2
BOM_VERSION=Brixton-1.0.0.RC1
BOM_VERSION=Brixton-1.0.0.RC1
springBootVersion=1.3.3.RELEASE

View File

@@ -11,7 +11,7 @@ repositories {
dependencies {
compile project(':accurest-core')
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
compile 'org.apache.camel:camel-spring-boot-starter:2.17.0'
compile 'org.apache.camel:camel-jms:2.17.0'
compile 'org.apache.activemq:activemq-camel:5.12.1'
@@ -19,7 +19,7 @@ dependencies {
compile 'org.apache.camel:camel-jackson:2.17.0'
testCompile project(':accurest-messaging-root:accurest-messaging-camel')
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -11,11 +11,11 @@ repositories {
dependencies {
compile project(':accurest-core')
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-integration:1.3.3.RELEASE'
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-integration:${springBootVersion}"
testCompile project(':accurest-messaging-root:accurest-messaging-integration')
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -11,14 +11,14 @@ repositories {
dependencies {
compile project(':accurest-core')
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
compile 'org.springframework:spring-jms:4.2.3.RELEASE'
compile 'org.apache.activemq:activemq-broker:5.12.1'
compile 'org.apache.activemq:activemq-pool:5.12.1'
testCompile project(':accurest-messaging-root:accurest-messaging-core')
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -11,7 +11,7 @@ repositories {
dependencies {
compile project(':accurest-core')
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
compile 'org.springframework.cloud:spring-cloud-stream-binder-rabbit:1.0.0.RC2'
testCompile project(':accurest-messaging-root:accurest-messaging-stream')

View File

@@ -8,6 +8,7 @@ include ':accurest-messaging:accurest-messaging-camel'
include ':stub-runner:stub-runner'
include ':stub-runner:stub-runner-spring'
include ':stub-runner:stub-runner-boot'
include ':stub-runner:stub-runner-spring-cloud'
include ':stub-runner:stub-runner-junit'

View File

@@ -0,0 +1,51 @@
=== Stub Runner Boot
Accurest Stub Runner Boot is a Spring Boot application that exposes REST endpoints to
trigger the messaging labels and to access started WireMock servers.
==== How to use it?
Just add the
[source,groovy,indent=0]
----
compile "io.codearte.accurest:stub-runner-boot:${accurestVersion}"
----
and a messaging implementation:
[source,groovy,indent=0]
----
// for Apache Camel
compile "io.codearte.accurest:accurest-messaging-camel:${accurestVersion}"
// for Spring Integration
compile "io.codearte.accurest:accurest-messaging-integration:${accurestVersion}"
// for Spring Cloud Stream
compile "io.codearte.accurest:accurest-messaging-stream:${accurestVersion}"
----
Build a fat-jar and you're ready to go!
For the properties check the *Stub Runner Spring* section.
==== Endpoints
===== HTTP
- GET `/stubs` - returns a list of all running stubs in `ivy:integer` notation
- GET `/stubs/{ivy}` - returns a port for the given `ivy` notation (when calling the endpoint `ivy` can also be `artifactId` only)
===== Messaging
For Messaging
- GET `/triggers` - returns a list of all running labels in `ivy : [ label1, label2 ...]` notation
- POST `/triggers/{label}` - executes a trigger with `label`
- POST `/triggers/{ivy}/{label}` - executes a trigger with `label` for the given `ivy` notation (when calling the endpoint `ivy` can also be `artifactId` only)
==== Example
[source,groovy,indent=0]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/boot/StubRunnerBootSpec.groovy[tags=boot_usage]
----

View File

@@ -0,0 +1,22 @@
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 "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile project(':stub-runner-root:stub-runner-messaging-root:stub-runner-messaging-stream')
testCompile 'com.jayway.restassured:spring-mock-mvc:2.9.0'
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}
}

View File

@@ -0,0 +1,38 @@
package io.codearte.accurest.stubrunner.boot
import io.codearte.accurest.stubrunner.StubRunning
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
/**
* @author Marcin Grzejszczak
*/
@RestController
@RequestMapping("/stubs")
class HttpStubsController {
private final StubRunning stubRunning
@Autowired
HttpStubsController(StubRunning stubRunning) {
this.stubRunning = stubRunning
}
@RequestMapping
Map<String, Integer> stubs() {
return stubRunning.runStubs().toIvyToPortMapping()
}
@RequestMapping(path = "/{ivy:.*}")
ResponseEntity<Integer> stub(@PathVariable String ivy) {
Integer port = stubRunning.runStubs().getPort(ivy)
if (port) {
return ResponseEntity.ok(port)
}
return new ResponseEntity(HttpStatus.NOT_FOUND)
}
}

View File

@@ -0,0 +1,15 @@
package io.codearte.accurest.stubrunner.boot
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
/**
* @author Marcin Grzejszczak
*/
@SpringBootApplication
class StubRunnerBoot {
static void main(String[] args) {
SpringApplication.run(StubRunnerBoot.class, args);
}
}

View File

@@ -0,0 +1,59 @@
package io.codearte.accurest.stubrunner.boot
import groovy.util.logging.Slf4j
import io.codearte.accurest.stubrunner.StubFinder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import static org.springframework.web.bind.annotation.RequestMethod.GET
import static org.springframework.web.bind.annotation.RequestMethod.POST
/**
* @author Marcin Grzejszczak
*/
@RestController
@RequestMapping("/triggers")
@Slf4j
class TriggerController {
private final StubFinder stubFinder
@Autowired
TriggerController(StubFinder stubFinder) {
this.stubFinder = stubFinder
}
@RequestMapping(method = POST, path = "/{label:.*}")
ResponseEntity<Map<String, Collection<String>>> trigger(@PathVariable String label) {
return respond(label) {
stubFinder.trigger(label)
}
}
@RequestMapping(method = POST, path = "/{ivyNotation:.*}/{label:.*}")
ResponseEntity<Map<String, Collection<String>>> triggerByArtifact(@PathVariable String ivyNotation, @PathVariable String label) {
return respond(label) {
stubFinder.trigger(ivyNotation, label)
}
}
@RequestMapping(method = GET)
Map<String, Collection<String>> labels() {
return stubFinder.labels()
}
private ResponseEntity<Map<String, Collection<String>>> respond(String label, Closure closure) {
try {
closure()
return ResponseEntity.ok().body()
} catch (Exception e) {
log.debug("Exception occurred while trying to return [$label] label", e)
return new ResponseEntity<Map<String,Collection<String>>>(stubFinder.labels(), HttpStatus.NOT_FOUND)
}
}
}

View File

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

View File

@@ -0,0 +1,92 @@
package io.codearte.accurest.stubrunner.boot
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
import groovy.json.JsonSlurper
import io.codearte.accurest.stubrunner.StubRunning
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
// tag::boot_usage[]
@ContextConfiguration(classes = StubRunnerBoot, loader = SpringApplicationContextLoader)
class StubRunnerBootSpec extends Specification {
@Autowired StubRunning stubRunning
def setup() {
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
new TriggerController(stubRunning))
}
def 'should return a list of running stub servers in "ivy:port" notation'() {
when:
String response = RestAssuredMockMvc.get('/stubs').body.asString()
then:
def root = new JsonSlurper().parseText(response)
root.'io.codearte.accurest.stubs:streamService:stubs' instanceof Integer
}
def 'should return a port on which a [#stubId] stub is running'() {
when:
def response = RestAssuredMockMvc.get("/stubs/${stubId}")
then:
response.statusCode == 200
response.body.as(Integer) > 0
where:
stubId << ['io.codearte.accurest.stubs:streamService:stubs', 'io.codearte.accurest.stubs:streamService', 'streamService']
}
def 'should return 404 when missing stub was called'() {
when:
def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
then:
response.statusCode == 404
}
def 'should return a list of messaging labels that can be triggered'() {
when:
String response = RestAssuredMockMvc.get('/triggers').body.asString()
then:
response == '''{"io.codearte.accurest.stubs:streamService:stubs":["delete_book","return_book_1","return_book_2"]}'''
}
def 'should trigger a messaging label'() {
given:
StubRunning stubRunning = Mock()
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
when:
def response = RestAssuredMockMvc.post("/triggers/delete_book")
then:
response.statusCode == 200
and:
1 * stubRunning.trigger('delete_book')
}
def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() {
given:
StubRunning stubRunning = Mock()
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
when:
def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book")
then:
response.statusCode == 200
and:
1 * stubRunning.trigger(stubId, 'delete_book')
where:
stubId << ['io.codearte.accurest.stubs:streamService:stubs', 'io.codearte.accurest.stubs:streamService', 'streamService']
}
def 'should return when trigger is missing'() {
when:
def response = RestAssuredMockMvc.post("/triggers/missing_label")
then:
response.statusCode == 404
response.body.asString() == '''{"io.codearte.accurest.stubs:streamService:stubs":["delete_book","return_book_1","return_book_2"]}'''
}
// end::boot_usage[]
}

View File

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

View File

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

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>streamService</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>streamService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -182,4 +182,9 @@ public class AccurestRule implements TestRule, StubFinder {
public boolean trigger() {
return stubFinder.trigger();
}
@Override
public Map<String, Collection<String>> labels() {
return stubFinder.labels();
}
}

View File

@@ -18,7 +18,7 @@ dependencies {
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.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -14,8 +14,8 @@ dependencies {
compile project(':accurest-messaging-root:accurest-messaging-integration')
compile 'org.springframework.integration:spring-integration-java-dsl:[1.1.2.RELEASE,)'
testCompile 'org.springframework.boot:spring-boot-starter-integration:1.3.3.RELEASE'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-integration:${springBootVersion}"
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -15,7 +15,7 @@ dependencies {
compile 'org.springframework.integration:spring-integration-java-dsl:[1.1.2.RELEASE,)'
testCompile 'org.springframework.cloud:spring-cloud-stream-test-support:1.0.0.RC2'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -14,7 +14,7 @@ dependencies {
testCompile 'org.springframework.cloud:spring-cloud-starter-zookeeper-discovery:1.0.0.RC1'
testCompile 'org.apache.curator:curator-test:2.9.1'
testCompile 'io.reactivex:rxjava:1.1.1'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile "org.springframework:spring-web:4.2.5.RELEASE"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')

View File

@@ -11,8 +11,8 @@ dependencies {
}
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.springframework.boot:spring-boot-starter:${springBootVersion}"
testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}

View File

@@ -51,7 +51,7 @@ class BatchStubRunner implements StubRunning {
@Override
Map<StubConfiguration, Collection<GroovyDsl>> getAccurestContracts() {
return stubRunners.inject([:]) { Map<StubConfiguration, Collection<GroovyDsl>> map, StubRunner stubRunner ->
map.putAll(stubRunner.accurestContracts)
map.putAll(stubRunner.accurestContracts ?: [:])
return map
} as Map<StubConfiguration, Collection<GroovyDsl>>
}
@@ -74,9 +74,7 @@ class BatchStubRunner implements StubRunning {
}
private String ivyToLabels() {
return (getAccurestContracts().collectEntries {
[(it.key.toColonSeparatedDependencyNotation()) : it.value.collect { it.label }]
} as Map<String, List<String>>).entrySet().collect {
return labels().entrySet().collect {
"Dependency [${it.key}] has labels ${it.value}]"
}.join('\n')
}
@@ -108,6 +106,16 @@ class BatchStubRunner implements StubRunning {
}
}
@Override
Map<String, Collection<String>> labels() {
return stubRunners.inject([:]) { Map<String, Collection<String>> map, StubRunner stubRunner ->
if (stubRunner) {
map.putAll(stubRunner.labels() ?: [:])
}
return map
} as Map<String, Collection<String>>
}
@Override
void close() throws IOException {
stubRunners.each {

View File

@@ -2,7 +2,6 @@ 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.
@@ -68,6 +67,10 @@ class RunningStubs {
return namesAndPorts.keySet().collect { it.artifactId } as Set
}
Map<String, Integer> toIvyToPortMapping() {
return namesAndPorts.collectEntries { [(it.key.toColonSeparatedDependencyNotation()) : it.value] } as Map<String, Integer>
}
@Override
String toString() {
return namesAndPorts.collect {

View File

@@ -4,7 +4,6 @@ import groovy.transform.CompileDynamic
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
@@ -76,4 +75,10 @@ public class StubConfiguration {
}
return [splitString[0], splitString[1]] as String[]
}
@Override
public String toString() {
return toColonSeparatedDependencyNotation()
}
}

View File

@@ -89,6 +89,11 @@ class StubRunner implements StubRunning {
return localStubRunner.trigger()
}
@Override
Map<String, Collection<String>> labels() {
return localStubRunner.labels()
}
private void registerShutdownHook() {
Runnable stopAllServers = { this.close() }
Runtime.runtime.addShutdownHook(new Thread(stopAllServers))

View File

@@ -82,7 +82,7 @@ class StubRunnerExecutor implements StubFinder {
}
private boolean triggerForDsls(Collection<GroovyDsl> dsls, String labelName) {
Collection<GroovyDsl> matchingDsls = dsls.findAll { it.label == labelName}
Collection<GroovyDsl> matchingDsls = dsls.findAll { it.label == labelName }
if (matchingDsls.empty) {
return false
}
@@ -100,6 +100,13 @@ class StubRunnerExecutor implements StubFinder {
return true
}
@Override
Map<String, Collection<String>> labels() {
return getAccurestContracts().collectEntries {
[(it.key.toColonSeparatedDependencyNotation()) : it.value.collect { it.label }]
} as Map<String, List<String>>
}
private void sendMessageIfApplicable(GroovyDsl groovyDsl) {
if (!groovyDsl.outputMessage) {
return

View File

@@ -28,4 +28,11 @@ interface StubTrigger {
* @return true - if managed to run a trigger
*/
boolean trigger()
/**
* Returns a mapping of ivy notation of a dependency to all the labels it has.
*
* Feature related to messaging.
*/
Map<String, Collection<String>> labels()
}

View File

@@ -1,6 +1,5 @@
package io.codearte.accurest.stubrunner
import io.codearte.accurest.dsl.GroovyDsl
import spock.lang.Specification
class BatchStubRunnerSpec extends Specification {
@@ -37,7 +36,7 @@ class BatchStubRunnerSpec extends Specification {
StubRunner runner = Mock(StubRunner)
runner.findStubUrl("group", "knownArtifact") >> KNOWN_STUB_URL
runner.findStubUrl("group", "unknownArtifact") >> null
runner.accurestContracts >> [(new StubConfiguration('a', 'b', 'c')): [GroovyDsl.make { outputMessage { label 'foo' } }]]
runner.labels() >> ['a:b:c' : ['foo']]
return [runner]
}