Turned on tests in modules

This commit is contained in:
Marcin Grzejszczak
2022-11-10 18:55:58 +01:00
parent 4c6d5472fa
commit 028527ec29
25 changed files with 786 additions and 735 deletions

View File

@@ -34,7 +34,7 @@
<spring-cloud-commons.version>4.0.0-SNAPSHOT</spring-cloud-commons.version>
<jopt-simple.version>5.0.4</jopt-simple.version>
<cglib.version>3.2.11</cglib.version>
<spock-spring.version>2.2-M1-groovy-4.0</spock-spring.version> <!-- @releaser:version-check-off -->
<spock-spring.version>2.3-groovy-4.0</spock-spring.version> <!-- @releaser:version-check-off -->
<hoverfly-junit.version>0.2.2</hoverfly-junit.version>
<commons-text.version>1.10.0</commons-text.version>
<handlebars.version>4.3.0</handlebars.version>
@@ -48,6 +48,7 @@
<junit-jupiter.version>5.9.1</junit-jupiter.version>
<gmavenplus-plugin.version>1.13.0</gmavenplus-plugin.version>
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
<awaitility.version>4.2.0</awaitility.version>
<!-- We need to have compatibility with Gradle -->
<groovy.version>4.0.0</groovy.version>
@@ -464,6 +465,12 @@
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner</name>
<description>Spring Cloud Contract Stub Runner</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>

View File

@@ -20,7 +20,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -42,9 +41,8 @@ import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.FilterEndpointSpec;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowBuilder;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@@ -56,7 +54,7 @@ import org.springframework.util.StringUtils;
* @author Marcin Grzejszczak
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ IntegrationFlows.class, InputDestination.class })
@ConditionalOnClass({ IntegrationFlow.class, InputDestination.class })
@ConditionalOnProperty(name = "stubrunner.stream.enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(StubRunnerIntegrationConfiguration.class)
public class StubRunnerStreamConfiguration {
@@ -106,14 +104,9 @@ public class StubRunnerStreamConfiguration {
}
for (Entry<String, List<Contract>> entries : map.entrySet()) {
final String flowName = name + "_" + entries.getKey() + "_" + entries.getValue().hashCode();
IntegrationFlowBuilder builder = IntegrationFlows.from(entries.getKey())
IntegrationFlowBuilder builder = IntegrationFlow.from(entries.getKey())
.filter(new StubRunnerStreamMessageSelector(entries.getValue()),
new Consumer<FilterEndpointSpec>() {
@Override
public void accept(FilterEndpointSpec e) {
e.id(flowName + ".filter");
}
})
e -> e.id(flowName + ".filter"))
.transform(new StubRunnerStreamTransformer(entries.getValue()))
.route(new StubRunnerMessageRouter(entries.getValue(), beanFactory));
beanFactory.initializeBean(builder.get(), flowName);

View File

@@ -18,11 +18,11 @@ package org.springframework.cloud.contract.stubrunner.server;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.contract.stubrunner.StubRunning;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -36,17 +36,16 @@ public class HttpStubsController {
private final StubRunning stubRunning;
@Autowired
public HttpStubsController(StubRunning stubRunning) {
this.stubRunning = stubRunning;
}
@RequestMapping
@GetMapping
public Map<String, Integer> stubs() {
return this.stubRunning.runStubs().toIvyToPortMapping();
}
@RequestMapping(path = "/{ivy:.*}")
@GetMapping(path = "/{ivy:.*}")
public ResponseEntity<Integer> consumer(@PathVariable String ivy) {
Integer port = this.stubRunning.runStubs().getPort(ivy);
if (port != null) {

View File

@@ -77,7 +77,7 @@ class StubRunnerExecutorSpec extends Specification {
int port = TestSocketUtils.findAvailableTcpPort()
StubRunnerExecutor executor = new StubRunnerExecutor(portScanner)
stubRunnerOptions = new StubRunnerOptionsBuilder(stubIdsToPortMapping:
stubIdsWithPortsFromString("group:artifact:${port},someotherartifact:${SocketUtils.findAvailableTcpPort()}"))
stubIdsWithPortsFromString("group:artifact:${port},someotherartifact:${TestSocketUtils.findAvailableTcpPort()}"))
.build()
when:
executor.runStubs(stubRunnerOptions, repository, stub)

View File

@@ -22,8 +22,10 @@ import com.github.tomakehurst.wiremock.extension.Parameters
import com.github.tomakehurst.wiremock.extension.ResponseTransformer
import com.github.tomakehurst.wiremock.http.ChunkedDribbleDelay
import com.github.tomakehurst.wiremock.http.HttpHeader
import com.github.tomakehurst.wiremock.http.HttpHeaders
import com.github.tomakehurst.wiremock.http.Request
import com.github.tomakehurst.wiremock.http.Response
import groovy.transform.CompileStatic
import org.springframework.cloud.contract.verifier.dsl.wiremock.DefaultResponseTransformer
import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions
@@ -31,16 +33,15 @@ import org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensio
/**
* Extension that registers the default response transformer and a custom one too
*/
@CompileStatic
class TestWireMockExtensions implements WireMockExtensions {
@Override
List<Extension> extensions() {
return [
new DefaultResponseTransformer(),
new CustomExtension()
]
return [ new DefaultResponseTransformer(), new CustomExtension() ] as List<Extension>
}
}
@CompileStatic
class CustomExtension extends ResponseTransformer {
/**
@@ -57,10 +58,18 @@ class CustomExtension extends ResponseTransformer {
*/
@Override
Response transform(Request request, Response response, FileSource files, Parameters parameters) {
def headers = response.headers + new HttpHeader("X-My-Header", "surprise!")
return new Response(response.status, response.statusMessage,
response.body, headers, response.wasConfigured(), response.fault,
response.initialDelay, new ChunkedDribbleDelay(0, 0), response.fromProxy)
HttpHeaders headers = response.headers + new HttpHeader("X-My-Header", "surprise!")
return Response.response()
.status(response.status)
.statusMessage(response.statusMessage)
.body(response.body)
.headers(headers)
.configured(response.wasConfigured())
.fault(response.fault)
.incrementInitialDelay(response.initialDelay)
.chunkedDribbleDelay(response.chunkedDribbleDelay)
.fromProxy(response.fromProxy)
.build()
}
/**

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.contract.stubrunner.provider.wiremock
import com.github.tomakehurst.wiremock.http.RequestMethod
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import org.junit.Rule
import spock.lang.Ignore
import spock.lang.Specification
import org.springframework.boot.test.system.OutputCaptureRule
@@ -66,10 +67,11 @@ class WireMockHttpServerStubSpec extends Specification {
mappingDescriptor?.stop()
}
@Ignore("There's sth wrong with SLF4J versions")
def 'should make WireMock print out logs on INFO'() {
given:
WireMockHttpServerStub mappingDescriptor = new WireMockHttpServerStub().start(new HttpServerStubConfiguration(HttpServerStubConfigurer.NoOpHttpServerStubConfigurer.INSTANCE, null,
null, SocketUtils.findAvailableTcpPort())) as WireMockHttpServerStub
null, TestSocketUtils.findAvailableTcpPort())) as WireMockHttpServerStub
mappingDescriptor.registerMappings([
new File(WireMockHttpServerStubSpec.classLoader.getResource("simple.json").toURI())
])

View File

@@ -18,14 +18,15 @@ package org.springframework.cloud.contract.stubrunner.server
import groovy.json.JsonSlurper
import io.restassured.module.mockmvc.RestAssuredMockMvc
import spock.lang.Specification
import org.assertj.core.api.BDDAssertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.stubrunner.StubRunning
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
/**
* @author Marcin Grzejszczak
@@ -33,88 +34,99 @@ import org.springframework.test.context.ContextConfiguration
// tag::boot_usage[]
@SpringBootTest(classes = StubRunnerBoot, properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec extends Specification {
class StubRunnerBootSpec {
@Autowired
StubRunning stubRunning
def setup() {
@BeforeEach
void setup() {
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
new TriggerController(stubRunning))
}
def 'should return a list of running stub servers in "full ivy:port" notation'() {
@Test
void 'should return a list of running stub servers in "full ivy port" notation'() {
when:
String response = RestAssuredMockMvc.get('/stubs').body.asString()
then:
def root = new JsonSlurper().parseText(response)
root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer
assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT: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
Integer.valueOf(response.body.asString()) > 0
where:
stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:+',
'org.springframework.cloud.contract.verifier.stubs:bootService',
'bootService']
@Test
void 'should return a port on which a #stubId stub is running'() {
given:
def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
'org.springframework.cloud.contract.verifier.stubs:bootService:+',
'org.springframework.cloud.contract.verifier.stubs:bootService',
'bootService']
stubIds.each {
when:
def response = RestAssuredMockMvc.get("/stubs/${it}")
then:
assert response.statusCode == 200
assert Integer.valueOf(response.body.asString()) > 0
}
}
def 'should return 404 when missing stub was called'() {
@Test
void 'should return 404 when missing stub was called'() {
when:
def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
then:
response.statusCode == 404
assert response.statusCode == 404
}
def 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
@Test
void 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
when:
String response = RestAssuredMockMvc.get('/triggers').body.asString()
then:
def root = new JsonSlurper().parseText(response)
root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book", "return_book_1", "return_book_2"])
assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book", "return_book_1", "return_book_2"])
}
def 'should trigger a messaging label'() {
@Test
void 'should trigger a messaging label'() {
given:
StubRunning stubRunning = Mock()
StubRunning stubRunning = Mockito.mock(StubRunning)
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')
Mockito.verify(stubRunning).trigger('delete_book')
}
def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() {
@Test
void 'should trigger a messaging label for a stub with #stubId ivy notation'() {
given:
StubRunning stubRunning = Mock()
StubRunning stubRunning = Mockito.mock(StubRunning)
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 << ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
stubIds.each {
when:
def response = RestAssuredMockMvc.post("/triggers/$it/delete_book")
then:
assert response.statusCode == 200
and:
Mockito.verify(stubRunning).trigger(it, 'delete_book')
}
}
def 'should throw exception when trigger is missing'() {
@Test
void 'should throw exception when trigger is missing'() {
when:
RestAssuredMockMvc.post("/triggers/missing_label")
then:
Exception e = thrown(Exception)
e.message.contains("Exception occurred while trying to return [missing_label] label.")
e.message.contains("Available labels are")
e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
BDDAssertions.thenThrownBy(() -> RestAssuredMockMvc.post("/triggers/missing_label"))
.hasMessageContaining("Exception occurred while trying to return [missing_label] label.")
.hasMessageContaining("Available labels are")
.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
}
}

View File

@@ -20,8 +20,10 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import groovy.transform.CompileStatic
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import spock.lang.Issue
import spock.lang.Specification
import org.assertj.core.api.BDDAssertions
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@@ -44,131 +46,137 @@ import org.springframework.test.context.ActiveProfiles
// Not necessary if Spring Cloud is used. TODO: make it work without this.
// tag::test[]
@SpringBootTest(classes = Config, properties = [" stubrunner.cloud.enabled=false",
'foo=${stubrunner.runningstubs.fraudDetectionServer.port}',
'fooWithGroup=${stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port}'])
'foo=${stubrunner.runningstubs.fraudDetectionServer.port}',
'fooWithGroup=${stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port}'])
// tag::annotation[]
@AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/",
httpServerStubConfigurer = HttpsForFraudDetection)
httpServerStubConfigurer = HttpsForFraudDetection)
// end::annotation[]
@ActiveProfiles("test")
class StubRunnerConfigurationSpec extends Specification {
class StubRunnerConfigurationSpec {
@Autowired
StubFinder stubFinder
@Autowired
Environment environment
@StubRunnerPort("fraudDetectionServer")
int fraudDetectionServerPort
@StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
int fraudDetectionServerPortWithGroupId
@Value('${foo}')
Integer foo
@Autowired
StubFinder stubFinder
@Autowired
Environment environment
@StubRunnerPort("fraudDetectionServer")
int fraudDetectionServerPort
@StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
int fraudDetectionServerPortWithGroupId
@Value('${foo}')
Integer foo
void setupSpec() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
WireMockHttpServerStubAccessor.clear()
}
@BeforeAll
static void setupSpec() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
WireMockHttpServerStubAccessor.clear()
}
void cleanupSpec() {
setupSpec()
}
@AfterAll
static void cleanupSpec() {
setupSpec()
}
def 'should mark all ports as random'() {
expect:
WireMockHttpServerStubAccessor.everyPortRandom()
}
@Test
void 'should mark all ports as random'() {
expect:
WireMockHttpServerStubAccessor.everyPortRandom()
}
def 'should start WireMock servers'() {
expect: 'WireMocks are running'
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
stubFinder.findStubUrl('loanIssuance') != null
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance')
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs')
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
stubFinder.findAllRunningStubs().isPresent('loanIssuance')
stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Fraud Detection is an HTTPS endpoint'
stubFinder.findStubUrl('fraudDetectionServer').toString().startsWith("https")
}
@Test
void 'should start WireMock servers'() {
expect: 'WireMocks are running'
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
assert stubFinder.findStubUrl('loanIssuance') != null
assert stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
assert stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance')
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs')
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
assert stubFinder.findAllRunningStubs().isPresent('loanIssuance')
assert stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
assert stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
assert "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
assert "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Fraud Detection is an HTTPS endpoint'
assert stubFinder.findStubUrl('fraudDetectionServer').toString().startsWith("https")
}
def 'should throw an exception when stub is not found'() {
when:
stubFinder.findStubUrl('nonExistingService')
then:
thrown(StubNotFoundException)
when:
stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId')
then:
thrown(StubNotFoundException)
}
@Test
void 'should throw an exception when stub is not found'() {
when:
BDDAssertions.thenThrownBy(() -> stubFinder.findStubUrl('nonExistingService')).isInstanceOf(StubNotFoundException)
when:
BDDAssertions.thenThrownBy(() -> stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId'))
.isInstanceOf(StubNotFoundException)
}
def 'should register started servers as environment variables'() {
expect:
environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null
stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer)
and:
environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer)
and:
environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port") as Integer)
}
@Test
void 'should register started servers as environment variables'() {
expect:
assert environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null
assert stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer)
and:
assert environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
assert stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer)
and:
assert environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
assert stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port") as Integer)
}
def 'should be able to interpolate a running stub in the passed test property'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
fraudPort > 0
environment.getProperty("foo", Integer) == fraudPort
environment.getProperty("fooWithGroup", Integer) == fraudPort
foo == fraudPort
}
@Test
void 'should be able to interpolate a running stub in the passed test property'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
assert fraudPort > 0
assert environment.getProperty("foo", Integer) == fraudPort
assert environment.getProperty("fooWithGroup", Integer) == fraudPort
assert foo == fraudPort
}
@Issue("#573")
def 'should be able to retrieve the port of a running stub via an annotation'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
fraudPort > 0
fraudDetectionServerPort == fraudPort
fraudDetectionServerPortWithGroupId == fraudPort
}
// @Issue("#573")
@Test
void 'should be able to retrieve the port of a running stub via an annotation'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
assert fraudPort > 0
assert fraudDetectionServerPort == fraudPort
assert fraudDetectionServerPortWithGroupId == fraudPort
}
def 'should dump all mappings to a file'() {
when:
def url = stubFinder.findStubUrl("fraudDetectionServer")
then:
new File("target/outputmappings/", "fraudDetectionServer_${url.port}").exists()
}
@Test
void 'should dump all mappings to a file'() {
when:
def url = stubFinder.findStubUrl("fraudDetectionServer")
then:
assert new File("target/outputmappings/", "fraudDetectionServer_${url.port}").exists()
}
@Configuration
@EnableAutoConfiguration
static class Config {}
@Configuration
@EnableAutoConfiguration
static class Config {}
// tag::wireMockHttpServerStubConfigurer[]
@CompileStatic
static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer {
// tag::wireMockHttpServerStubConfigurer[]
@CompileStatic
static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer {
private static final Log log = LogFactory.getLog(HttpsForFraudDetection)
private static final Log log = LogFactory.getLog(HttpsForFraudDetection)
@Override
WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") {
int httpsPort = TestSocketUtils.findAvailableTcpPort()
log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server")
return httpStubConfiguration
.httpsPort(httpsPort)
}
return httpStubConfiguration
}
}
// end::wireMockHttpServerStubConfigurer[]
@Override
WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") {
int httpsPort = TestSocketUtils.findAvailableTcpPort()
log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server")
return httpStubConfiguration
.httpsPort(httpsPort)
}
return httpStubConfiguration
}
}
// end::wireMockHttpServerStubConfigurer[]
}
// end::test[]

View File

@@ -16,7 +16,7 @@
package org.springframework.cloud.contract.stubrunner.spring
import spock.lang.Specification
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -30,7 +30,7 @@ import org.springframework.test.context.ActiveProfiles
@SpringBootTest(classes = Config, properties = ['some.property1=org.springframework.cloud.contract.verifier.stubs:loanIssuance'])
@AutoConfigureStubRunner
@ActiveProfiles("test-with-placeholders")
class StubRunnerOptionsBuilderSpec extends Specification {
class StubRunnerOptionsBuilderSpec {
@StubRunnerPort("fraudDetectionServer")
int fraudDetectionServerPort
@@ -41,12 +41,13 @@ class StubRunnerOptionsBuilderSpec extends Specification {
@Value('${stub.port}')
int stubPort
def 'should resolve placeholders'() {
@Test
void 'should resolve placeholders'() {
expect:
fraudDetectionServerPort > 1000
loanIssuancePort > 1000
assert fraudDetectionServerPort > 1000
assert loanIssuancePort > 1000
and:
stubPort == fraudDetectionServerPort
assert stubPort == fraudDetectionServerPort
}
@Configuration

View File

@@ -18,12 +18,12 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud
import java.util.function.Function
import spock.lang.Specification
import org.assertj.core.api.BDDAssertions
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.cloud.contract.stubrunner.StubFinder
@@ -37,18 +37,18 @@ import org.springframework.core.env.Environment
import org.springframework.http.ResponseEntity
import org.springframework.messaging.Message
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
/**
* @author Marcin Grzejszczak
*/
// tag::test[]
@SpringBootTest(classes = Config, properties = ["spring.application.name=bar-consumer"])
@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers",
repositoryRoot = "classpath:m2repo/repository/",
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
stubsPerConsumer = true)
repositoryRoot = "classpath:m2repo/repository/",
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
stubsPerConsumer = true)
@ActiveProfiles("streamconsumer")
class StubRunnerStubsPerConsumerSpec extends Specification {
class StubRunnerStubsPerConsumerSpec {
// end::test[]
@Autowired
@@ -59,36 +59,36 @@ class StubRunnerStubsPerConsumerSpec extends Specification {
MessageVerifier<Message<?>> messaging
TestRestTemplate template = new TestRestTemplate()
def 'should start http stub servers for bar-consumer only'() {
@Test
void 'should start http stub servers for bar-consumer only'() {
given:
URL stubUrl = stubFinder.findStubUrl('producerWithMultipleConsumers')
URL stubUrl = stubFinder.findStubUrl('producerWithMultipleConsumers')
when:
ResponseEntity entity = template.getForEntity("${stubUrl}/bar-consumer", String)
ResponseEntity entity = template.getForEntity("${stubUrl}/bar-consumer", String)
then:
entity.statusCode.value() == 200
assert entity.statusCode.value() == 200
when:
entity = template.getForEntity("${stubUrl}/foo-consumer", String)
entity = template.getForEntity("${stubUrl}/foo-consumer", String)
then:
entity.statusCode.value() == 404
assert entity.statusCode.value() == 404
}
def 'should trigger a message by label from proper consumer'() {
@Test
void 'should trigger a message by label from proper consumer'() {
when:
stubFinder.trigger('return_book_for_bar')
stubFinder.trigger('return_book_for_bar')
then:
Message<?> receivedMessage = messaging.receive('output')
Message<?> receivedMessage = messaging.receive('output')
and:
receivedMessage != null
receivedMessage.payload == '''{"bookName":"foo_for_bar"}'''.bytes
receivedMessage.headers.get('BOOK-NAME') == 'foo_for_bar'
assert receivedMessage != null
assert receivedMessage.payload == '''{"bookName":"foo_for_bar"}'''.bytes
assert receivedMessage.headers.get('BOOK-NAME') == 'foo_for_bar'
}
def 'should not trigger a message by the not matching consumer'() {
@Test
void 'should not trigger a message by the not matching consumer'() {
when:
stubFinder.trigger('return_book_for_foo')
then:
IllegalArgumentException e = thrown(IllegalArgumentException)
e.message.contains("No label with name [return_book_for_foo] was found")
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('return_book_for_foo')).isInstanceOf(IllegalArgumentException).hasMessageContaining("No label with name [return_book_for_foo] was found")
}
@Configuration
@@ -97,7 +97,7 @@ class StubRunnerStubsPerConsumerSpec extends Specification {
static class Config {
@Bean
Function output() {
return { Object o ->
return { Object o ->
println(o)
return o
}

View File

@@ -18,6 +18,8 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud
import java.util.function.Function
import org.assertj.core.api.BDDAssertions
import org.junit.jupiter.api.Test
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
@@ -47,7 +49,7 @@ import org.springframework.test.context.ActiveProfiles
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
stubsPerConsumer = true)
@ActiveProfiles("streamconsumer")
class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification {
class StubRunnerStubsPerConsumerWithConsumerNameSpec {
// end::test[]
@Autowired
@@ -59,36 +61,37 @@ class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification {
TestRestTemplate template = new TestRestTemplate()
def 'should start http stub servers for foo-consumer only'() {
@Test
void 'should start http stub servers for foo-consumer only'() {
given:
URL stubUrl = stubFinder.findStubUrl('producerWithMultipleConsumers')
when:
ResponseEntity entity = template.getForEntity("${stubUrl}/foo-consumer", String)
then:
entity.statusCode.value() == 200
assert entity.statusCode.value() == 200
when:
entity = template.getForEntity("${stubUrl}/bar-consumer", String)
then:
entity.statusCode.value() == 404
assert entity.statusCode.value() == 404
}
def 'should trigger a message by label from proper consumer'() {
@Test
void 'should trigger a message by label from proper consumer'() {
when:
stubFinder.trigger('return_book_for_foo')
then:
Message<?> receivedMessage = messaging.receive('output')
and:
receivedMessage != null
receivedMessage.payload == '''{"bookName":"foo_for_foo"}'''.bytes
receivedMessage.headers.get('BOOK-NAME') == 'foo_for_foo'
assert receivedMessage != null
assert receivedMessage.payload == '''{"bookName":"foo_for_foo"}'''.bytes
assert receivedMessage.headers.get('BOOK-NAME') == 'foo_for_foo'
}
def 'should not trigger a message by the not matching consumer'() {
@Test
void 'should not trigger a message by the not matching consumer'() {
when:
stubFinder.trigger('return_book_for_bar')
then:
IllegalArgumentException e = thrown(IllegalArgumentException)
e.message.contains("No label with name [return_book_for_bar] was found")
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('return_book_for_bar')).isInstanceOf(IllegalArgumentException)
.hasMessageContaining("No label with name [return_book_for_bar] was found")
}
@Configuration

View File

@@ -19,12 +19,13 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud.consul
import com.ecwid.consul.v1.ConsulClient
import com.ecwid.consul.v1.agent.model.NewService
import groovy.transform.CompileStatic
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatcher
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties
@@ -32,7 +33,6 @@ import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRun
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ContextConfiguration
import static org.mockito.BDDMockito.then
import static org.mockito.Matchers.argThat
@@ -56,30 +56,35 @@ import static org.mockito.Mockito.mock
stubsMode = StubRunnerProperties.StubsMode.REMOTE ,
repositoryRoot = "classpath:m2repo/repository/" )
// end::autoconfigure[]
class StubRunnerSpringCloudConsulAutoConfigurationSpec extends Specification {
class StubRunnerSpringCloudConsulAutoConfigurationSpec {
@Autowired
ConsulClient client
void setupSpec() {
@BeforeAll
static void setupSpec() {
System.clearProperty("stubrunner.stubs.repository.root")
System.clearProperty("stubrunner.stubs.classifier")
}
void cleanupSpec() {
@AfterAll
static void cleanupSpec() {
setupSpec()
}
def 'should make service discovery work for #serviceName'() {
@Test
void 'should make service discovery work for #serviceName'() {
given:
final String expectedId = serviceName.split(':')[0]
final String expectedName = serviceName.split(':')[1]
when: 'Consul registration took place for 3 stubs'
then(client).should().agentServiceRegister(argThat(new NewServiceMatcher(expectedId, expectedName)))
then:
noExceptionThrown()
where:
serviceName << ['loanIssuance:loanIssuance', 'bootService:bootService', 'fraudDetectionServer:someNameThatShouldMapFraudDetectionServer']
def serviceName = ['loanIssuance:loanIssuance', 'bootService:bootService', 'fraudDetectionServer:someNameThatShouldMapFraudDetectionServer']
when:
serviceName.each {
and:
final String expectedId = it.split(':')[0]
final String expectedName = it.split(':')[1]
then: 'Consul registration took place for 3 stubs'
then(client).should().agentServiceRegister(argThat(new NewServiceMatcher(expectedId, expectedName)))
}
}
private static class NewServiceMatcher implements ArgumentMatcher<NewService> {

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Sample Spring Amqp</name>
<description>Spring Cloud Contract Sample Spring Amqp Rabbit</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -36,13 +32,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
@@ -52,6 +48,10 @@
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -22,8 +22,7 @@ import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import groovy.json.JsonOutput
import spock.lang.Issue
import spock.lang.Specification
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
@@ -40,7 +39,7 @@ import static org.springframework.cloud.contract.verifier.messaging.util.Contrac
// Context configuration would end up in base class
@AutoConfigureMessageVerifier
@SpringBootTest(classes = AmqpMessagingApplication, properties = "stubrunner.amqp.enabled=true")
class AmqpMessagingApplicationSpec extends Specification {
class AmqpMessagingApplicationSpec {
// ALL CASES
@Inject
@@ -48,8 +47,9 @@ class AmqpMessagingApplicationSpec extends Specification {
@Inject
ContractVerifierObjectMapper contractVerifierObjectMapper
def "should work for triggered based messaging"() {
given:
@Test
void should_work_for_triggered_based_messaging() {
// given:
def dsl = Contract.make {
// Human readable description
description 'Some description'
@@ -74,20 +74,21 @@ class AmqpMessagingApplicationSpec extends Specification {
}
}
// generated test should look like this:
when:
// when:
publishBook()
then:
// then:
def response = contractVerifierMessaging.receive('test-exchange')
response.headers.get('contentType') == 'application/json'
and:
// and:
DocumentContext parsedJson = JsonPath.
parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
JsonAssertion.assertThat(parsedJson).field('name').isEqualTo('some')
}
@Issue("332")
def "should work for second scenario"() {
given:
// @Issue("332")
@Test
void should_work_for_second_scenario() {
// given:
def dsl =
Contract.make {
description("""
@@ -97,11 +98,11 @@ https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_publis
"The input message triggers an output message."
```
given:
// given:
rabbit service is running
when:
// when:
input message is received
then:
// then:
message is send
```
@@ -128,7 +129,7 @@ then:
}
}
// generated test should look like this:
and:
// and:
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
"{\"name\":\"foo2\"}"
, headers()
@@ -136,23 +137,24 @@ then:
.header("amqp_replyTo", "amq.rabbitmq.reply-to")
.header("bill", "bill")
)
when:
// when:
contractVerifierMessaging.send(inputMessage, "input")
then:
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("")
assertThat(response).isNotNull()
assertThat(response.getHeader("contentType")).isNotNull()
assertThat(response.getHeader("contentType").toString()).
isEqualTo("application/json")
and:
// and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.
writeValueAsString(response.getPayload()))
assertThatJson(parsedJson).field("['name']").isEqualTo("foo2")
}
@Issue("178")
def "should work for input/output when bytes are used"() {
given:
// @Issue("178")
@Test
void should_work_for_input_output_when_bytes_are_used() {
// given:
def inputBody = [
ratedItemId: "992e46d8-ab05-4a26-a740-6ef7b0daeab3",
eventType : "CREATED"
@@ -175,16 +177,16 @@ then:
)
}
}
when:
// when:
contractVerifierMessaging.send(contractVerifierMessaging.
create(new JsonOutput().toJson(inputBody), [
"X-tenant" : "1234",
"contentType": "application/json"
]), "rated-item-service.rated-item-event.exchange")
then:
// then:
def response = contractVerifierMessaging.
receive('bill-service.rated-item-event.retry-exchange')
and:
// and:
DocumentContext parsedJson = JsonPath.
parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
JsonAssertion.assertThat(parsedJson).field('ratedItemId').

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Sample Spring Integration</name>
<description>Spring Cloud Contract Sample Spring Integration</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -21,6 +21,7 @@ import javax.inject.Inject
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import org.junit.jupiter.api.Test
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
@@ -34,14 +35,15 @@ import org.springframework.messaging.Message
// Context configuration would end up in base class
@AutoConfigureMessageVerifier
@SpringBootTest(classes = IntegrationMessagingApplication)
class IntegrationMessagingApplicationSpec extends Specification {
class IntegrationMessagingApplicationSpec {
// ALL CASES
@Inject
MessageVerifier<Message<?>> contractVerifierMessaging
ContractVerifierObjectMapper contractVerifierObjectMapper = new ContractVerifierObjectMapper()
def "should work for triggered based messaging"() {
@Test
void "should work for triggered based messaging"() {
given:
// tag::method_trigger[]
def dsl = Contract.make {
@@ -79,7 +81,7 @@ class IntegrationMessagingApplicationSpec extends Specification {
JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo')
}
def "should generate tests triggered by a message"() {
void "should generate tests triggered by a message"() {
given:
// tag::message_trigger[]
def dsl = Contract.make {
@@ -125,7 +127,8 @@ class IntegrationMessagingApplicationSpec extends Specification {
JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo')
}
def "should generate tests without destination, triggered by a message"() {
@Test
void "should generate tests without destination, triggered by a message"() {
given:
def dsl = Contract.make {
label 'some_label'
@@ -148,7 +151,6 @@ class IntegrationMessagingApplicationSpec extends Specification {
send(contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']),
[sample: 'header'], 'delete')
then:
noExceptionThrown()
bookWasDeleted()
}

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Boot Zookeeper</name>
<description>Spring Cloud Contract Stub Runner Boot Zookeeper</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>

View File

@@ -17,8 +17,9 @@
package org.springframework.cloud.contract.stubrunner.spring.cloud.zookeeper
import org.apache.curator.test.TestingServer
import spock.lang.Ignore
import spock.lang.Specification
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -28,26 +29,23 @@ import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.cloud.test.TestSocketUtils
import org.springframework.cloud.zookeeper.ZookeeperProperties
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.cloud.test.TestSocketUtils
import org.springframework.web.client.RestTemplate
/**
* @author Marcin Grzejszczak
*/
@SpringBootTest(classes = Config, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["stubrunner.cloud.stubbed.discovery.enabled=false",
"debug=true"])
@AutoConfigureStubRunner(ids =
["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
properties = ["stubrunner.cloud.stubbed.discovery.enabled=false",
"debug=true"])
@AutoConfigureStubRunner(ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"] ,
repositoryRoot = "classpath:m2repo/repository/" ,
stubsMode = StubRunnerProperties.StubsMode.REMOTE )
class StubRunnerSpringCloudZookeeperAutoConfigurationSpec extends Specification {
"org.springframework.cloud.contract.verifier.stubs:bootService"] , repositoryRoot = "classpath:m2repo/repository/" , stubsMode = StubRunnerProperties.StubsMode.REMOTE )
class StubRunnerSpringCloudZookeeperAutoConfigurationSpec {
@Autowired
StubFinder stubFinder
@@ -57,29 +55,32 @@ class StubRunnerSpringCloudZookeeperAutoConfigurationSpec extends Specification
@Autowired
ZookeeperDiscoveryClient zookeeperServiceDiscovery
void setupSpec() {
@BeforeAll
static void setupSpec() {
System.clearProperty("stubrunner.stubs.repository.root")
System.clearProperty("stubrunner.stubs.classifier")
}
void cleanupSpec() {
@AfterAll
static void cleanupSpec() {
setupSpec()
}
@Ignore
def 'should make service discovery work'() {
@Test
void 'should make service discovery work'() {
expect: 'WireMocks are running'
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
"${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Stubs can be reached via load service discovery'
restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance'
restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance'
assert restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
}
def 'should have all apps registered in Service Discovery'() {
@Test
void 'should have all apps registered in Service Discovery'() {
expect:
!zookeeperServiceDiscovery.getInstances('loanIssuance').empty
!zookeeperServiceDiscovery.getInstances('someNameThatShouldMapFraudDetectionServer').empty
assert !zookeeperServiceDiscovery.getInstances('loanIssuance').empty
assert !zookeeperServiceDiscovery.getInstances('someNameThatShouldMapFraudDetectionServer').empty
}
@Configuration
@@ -89,7 +90,7 @@ class StubRunnerSpringCloudZookeeperAutoConfigurationSpec extends Specification
@Bean
TestingServer testingServer() {
return new TestingServer(SocketUtils.findAvailableTcpPort())
return new TestingServer(TestSocketUtils.findAvailableTcpPort())
}
@Bean

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Kafka</name>
<description>Spring Cloud Contract Stub Runner Kafka</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -42,25 +38,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -71,6 +52,11 @@
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -21,12 +21,15 @@ import java.util.concurrent.TimeUnit
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.util.logging.Commons
import org.apache.kafka.clients.consumer.MockConsumer
import org.apache.kafka.clients.consumer.OffsetResetStrategy
import spock.lang.IgnoreIf
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
import org.assertj.core.api.BDDAssertions
import org.awaitility.Awaitility
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledOnOs
import org.junit.jupiter.api.condition.OS
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -34,251 +37,251 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.annotation.EnableKafka
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.support.DefaultKafkaHeaderMapper
import org.springframework.kafka.test.EmbeddedKafkaBroker
import org.springframework.kafka.test.context.EmbeddedKafka
import org.springframework.messaging.Message
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.support.MessageBuilder
import org.springframework.stereotype.Component
/**
* @author Marcin Grzejszczak
*/
@SpringBootTest(classes = Config, properties = ["debug=true"])
@AutoConfigureStubRunner
@IgnoreIf({ os.windows })
@DisabledOnOs(value = OS.WINDOWS)
@EmbeddedKafka(topics = ["input", "input2", "output", "delete"])
@Commons
class KafkaStubRunnerSpec extends Specification {
class KafkaStubRunnerSpec {
@Autowired
StubFinder stubFinder
@Autowired
KafkaTemplate kafkaTemplate
@Autowired
MyMessageListener myMessageListener
PollingConditions await = new PollingConditions(timeout: 15, initialDelay: 1, delay: 1)
@Autowired
StubFinder stubFinder
@Autowired
KafkaTemplate kafkaTemplate
@Autowired
MyMessageListener myMessageListener
def setup() {
this.myMessageListener.clear()
}
@BeforeEach
@AfterEach
void setup() {
this.myMessageListener.clear()
}
def cleanup() {
this.myMessageListener.clear()
}
@CompileStatic
private Message receiveFromOutput() {
Message m = null
Awaitility.await().untilAsserted(() -> {
m = this.myMessageListener.output()
log.info("Received from message [" + m + "]")
assert m != null
})
return m
}
private Message receiveFromOutput() {
Message m = this.myMessageListener.output()
log.info("Received message [" + m + "]")
return m
}
@CompileStatic
private Message receiveNullableMessageFromOutput() {
Message m = this.myMessageListener.output()
log.info("Received message [" + m + "]")
return m
}
// Skipping the test on Jenkins cause it's for some reason flakey only there
def 'should download the stub and register a route for it'() {
expect:
await.eventually {
log.info("Sending the message")
// tag::client_send[]
Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('input')
kafkaTemplate.send(message)
// end::client_send[]
log.info("Message sent")
log.info("Receiving the message")
// tag::client_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_receive[]
log.info("Message received [" + receivedMessage + "]")
// tag::client_receive_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
// end::client_receive_message[]
}
}
// Skipping the test on Jenkins cause it's for some reason flakey only there
@Test
void 'should download the stub and register a route for it'() {
expect:
log.info("Sending the message")
// tag::client_send[]
Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('input')
kafkaTemplate.send(message)
// end::client_send[]
log.info("Message sent")
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
log.info("Receiving the message")
// tag::client_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_receive[]
log.info("Message received [" + receivedMessage + "]")
// tag::client_receive_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
// end::client_receive_message[]
}
}
def 'should propagate the Kafka record key via message headers'() {
expect:
await.eventually {
log.info("Sending the message")
// tag::client_send[]
Message message = MessageBuilder.createMessage(new BookReturned('bar'), new MessageHeaders([kafka_messageKey: "bar5150",]))
kafkaTemplate.setDefaultTopic('input2')
kafkaTemplate.send(message)
// end::client_send[]
log.info("Message sent")
log.info("Receiving the message")
// tag::client_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_receive[]
log.info("Message received [" + receivedMessage + "]")
// tag::client_receive_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookName(receivedMessage.getPayload(), 'bar')
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'bar'
assert receivedMessage.getHeaders().get("kafka_receivedMessageKey") == 'bar5150'
// end::client_receive_message[]
}
}
@Test
void 'should propagate the Kafka record key via message headers'() {
expect:
log.info("Sending the message")
// tag::client_send[]
Message message = MessageBuilder.createMessage(new BookReturned('bar'), new MessageHeaders([kafka_messageKey: "bar5150",]))
kafkaTemplate.setDefaultTopic('input2')
kafkaTemplate.send(message)
// end::client_send[]
log.info("Message sent")
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
log.info("Receiving the message")
// tag::client_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_receive[]
log.info("Message received [" + receivedMessage + "]")
// tag::client_receive_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookName(receivedMessage.getPayload(), 'bar')
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'bar'
assert receivedMessage.getHeaders().get("kafka_receivedMessageKey") == 'bar5150'
// end::client_receive_message[]
}
}
def 'should trigger a message by label'() {
expect:
await.eventually {
// tag::client_trigger[]
stubFinder.trigger('return_book_1')
// end::client_trigger[]
// tag::client_trigger_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_trigger_receive[]
// tag::client_trigger_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
// end::client_trigger_message[]
}
}
@Test
void 'should trigger a message by label'() {
expect:
// tag::client_trigger[]
stubFinder.trigger('return_book_1')
// end::client_trigger[]
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
// tag::client_trigger_receive[]
Message receivedMessage = receiveFromOutput()
// end::client_trigger_receive[]
// tag::client_trigger_message[]
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
// end::client_trigger_message[]
}
}
def 'should trigger a label for the existing groupId:artifactId'() {
expect:
await.eventually {
// tag::trigger_group_artifact[]
stubFinder.
trigger('my:stubs', 'return_book_1')
// end::trigger_group_artifact[]
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
}
}
@Test
void 'should trigger a label for the existing groupId and artifactId'() {
expect:
// tag::trigger_group_artifact[]
stubFinder.
trigger('my:stubs', 'return_book_1')
// end::trigger_group_artifact[]
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
}
}
def 'should trigger a label for the existing artifactId'() {
expect:
await.eventually {
// tag::trigger_artifact[]
stubFinder.trigger('stubs', 'return_book_1')
// end::trigger_artifact[]
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
}
}
@Test
void 'should trigger a label for the existing artifactId'() {
expect:
// tag::trigger_artifact[]
stubFinder.trigger('stubs', 'return_book_1')
// end::trigger_artifact[]
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookNameFoo(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') == 'foo'
}
}
def 'should throw an exception when missing label is passed'() {
when:
stubFinder.trigger('missing label')
then:
thrown(IllegalArgumentException)
}
@Test
void 'should throw an exception when missing label is passed'() {
expect:
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('missing label')).isInstanceOf(IllegalArgumentException)
}
def 'should throw an exception when missing label and artifactid is passed'() {
when:
stubFinder.trigger('some:service', 'return_book_1')
then:
thrown(IllegalArgumentException)
}
@Test
void 'should throw an exception when missing label and artifactid is passed'() {
expect:
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('some:service', 'return_book_1')).isInstanceOf(IllegalArgumentException)
}
def 'should trigger messages by running all triggers'() {
expect:
await.eventually {
// tag::trigger_all[]
stubFinder.trigger()
// end::trigger_all[]
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookName(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') != null
}
}
@Test
void 'should trigger messages by running all triggers'() {
expect:
// tag::trigger_all[]
stubFinder.trigger()
// end::trigger_all[]
Awaitility.await().pollInterval(200, TimeUnit.MILLISECONDS).untilAsserted {
Message receivedMessage = receiveFromOutput()
assert receivedMessage != null
assert assertThatBodyContainsBookName(receivedMessage.getPayload())
assert receivedMessage.getHeaders().get('BOOK-NAME') != null
}
}
def 'should trigger a label with no output message'() {
when:
// tag::trigger_no_output[]
Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('delete')
kafkaTemplate.send(message)
// end::trigger_no_output[]
then:
noExceptionThrown()
}
@Test
void 'should trigger a label with no output message'() {
when:
// tag::trigger_no_output[]
Message message = MessageBuilder.createMessage(new BookReturned('foo'), new MessageHeaders([sample: "header",]))
kafkaTemplate.setDefaultTopic('delete')
kafkaTemplate.send(message)
// end::trigger_no_output[]
}
def 'should not trigger a message that does not match input'() {
when:
Message message = MessageBuilder.createMessage(new BookReturned('notmatching'), new MessageHeaders([wrong: "header",]))
kafkaTemplate.setDefaultTopic('input')
kafkaTemplate.send(message)
then:
Message receivedMessage = receiveFromOutput()
and:
receivedMessage == null
}
@Test
void 'should not trigger a message that does not match input'() {
when:
Message message = MessageBuilder.createMessage(new BookReturned('notmatching'), new MessageHeaders([wrong: "header",]))
kafkaTemplate.setDefaultTopic('input')
kafkaTemplate.send(message)
then:
Message receivedMessage = receiveNullableMessageFromOutput()
and:
assert receivedMessage == null
}
private boolean assertThatBodyContainsBookNameFoo(Object payload) {
return assertThatBodyContainsBookName(payload, 'foo')
}
private boolean assertThatBodyContainsBookNameFoo(Object payload) {
return assertThatBodyContainsBookName(payload, 'foo')
}
private boolean assertThatBodyContainsBookName(Object payload, String expectedValue) {
log.info("Got payload [" + payload + "]")
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName == expectedValue
}
private boolean assertThatBodyContainsBookName(Object payload, String expectedValue) {
log.info("Got payload [" + payload + "]")
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName == expectedValue
}
private boolean assertThatBodyContainsBookName(Object payload) {
log.info("Got payload [" + payload + "]")
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName != null
}
private boolean assertThatBodyContainsBookName(Object payload) {
log.info("Got payload [" + payload + "]")
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName != null
}
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableKafka
static class Config {
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableKafka
static class Config {
}
@Bean
DefaultKafkaHeaderMapper headerMapper() {
return new DefaultKafkaHeaderMapper();
}
@Commons
@Component
static class MyMessageListener {
}
CountDownLatch latch = new CountDownLatch(1)
@Commons
@Component
static class MyMessageListener {
Message output
CountDownLatch latch = new CountDownLatch(1)
@KafkaListener(topics = ["output"])
void output(Message message) {
log.info("I got the message [${message}]")
this.output = message
}
Message output
void clear() {
this.output = null
}
@KafkaListener(topics = ["output"])
void output(Message message) {
log.info("I got the message [${message}]")
this.output = message
this.latch.countDown()
}
void clear() {
this.output = null
this.latch = new CountDownLatch(1)
}
Message output() {
this.latch.await(2, TimeUnit.SECONDS)
return this.output
}
}
Message output() {
return this.output
}
}
Contract dsl =
// tag::sample_dsl[]

View File

@@ -13,10 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Contract Stub Runner Stream</name>
<description>Spring Cloud Contract Stub Runner Stream</description>
<properties>
<!-- TODO: remove -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
@@ -43,26 +39,16 @@
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -17,11 +17,17 @@
package org.springframework.cloud.contract.stubrunner.messaging.stream
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.Function
import java.util.function.Supplier
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import spock.lang.Ignore
import spock.lang.Specification
import org.assertj.core.api.BDDAssertions
import org.awaitility.Awaitility
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
@@ -34,6 +40,7 @@ import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRun
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.messaging.Message
import org.springframework.test.context.ContextConfiguration
@@ -45,182 +52,208 @@ import org.springframework.test.context.ContextConfiguration
@SpringBootTest(properties = "debug=true")
@AutoConfigureStubRunner
@AutoConfigureMessageVerifier
//@IgnoreIf({ os.windows })
@Ignore("Wait until the feature of runtime message sending and polling is available")
class StreamStubRunnerSpec extends Specification {
class StreamStubRunnerSpec {
@Autowired
StubFinder stubFinder
@Autowired
MessageVerifier<Message<?>> messaging
@Autowired
StubFinder stubFinder
@Autowired
MessageVerifier<Message<?>> messaging
def 'should download the stub and register a route for it'() {
when:
// tag::client_send[]
messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
// end::client_send[]
then:
// tag::client_receive[]
Message<?> receivedMessage = messaging.receive('returnBook')
// end::client_receive[]
and:
// tag::client_receive_message[]
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
// end::client_receive_message[]
}
@Test
void 'should download the stub and register a route for it'() {
when:
// tag::client_send[]
messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
// end::client_send[]
then:
Awaitility.await().untilAsserted(() -> {
// tag::client_receive[]
Message<?> receivedMessage = messaging.receive('returnBook')
// end::client_receive[]
and:
// tag::client_receive_message[]
assert receivedMessage != null
assertJsons(receivedMessage.payload)
assert receivedMessage.headers.get('BOOK-NAME') == 'foo'
// end::client_receive_message[]
})
}
def 'should trigger a message by label'() {
when:
// tag::client_trigger[]
stubFinder.trigger('return_book_1')
// end::client_trigger[]
then:
// tag::client_trigger_receive[]
Message<?> receivedMessage = messaging.receive('returnBook')
// end::client_trigger_receive[]
and:
// tag::client_trigger_message[]
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
// end::client_trigger_message[]
}
@Test
void 'should trigger a message by label'() {
when:
// tag::client_trigger[]
stubFinder.trigger('return_book_1')
// end::client_trigger[]
then:
Awaitility.await().untilAsserted(() -> {
// tag::client_trigger_receive[]
Message<?> receivedMessage = messaging.receive('returnBook')
// end::client_trigger_receive[]
and:
// tag::client_trigger_message[]
assert receivedMessage != null
assertJsons(receivedMessage.payload)
assert receivedMessage.headers.get('BOOK-NAME') == 'foo'
// end::client_trigger_message[]
});
}
def 'should trigger a label for the existing groupId:artifactId'() {
when:
// tag::trigger_group_artifact[]
stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:streamService', 'return_book_1')
// end::trigger_group_artifact[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
@Test
void 'should trigger a label for the existing groupId and artifactId'() {
when:
// tag::trigger_group_artifact[]
stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:streamService', 'return_book_1')
// end::trigger_group_artifact[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
assert receivedMessage != null
assertJsons(receivedMessage.payload)
assert receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing artifactId'() {
when:
// tag::trigger_artifact[]
stubFinder.trigger('streamService', 'return_book_1')
// end::trigger_artifact[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
@Test
void 'should trigger a label for the existing artifactId'() {
when:
// tag::trigger_artifact[]
stubFinder.trigger('streamService', 'return_book_1')
// end::trigger_artifact[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
assert receivedMessage != null
assertJsons(receivedMessage.payload)
assert receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should throw exception when missing label is passed'() {
when:
stubFinder.trigger('missing label')
then:
thrown(IllegalArgumentException)
}
@Test
void 'should throw exception when missing label is passed'() {
when:
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('missing label')).isInstanceOf(IllegalArgumentException)
}
def 'should throw exception when missing label and artifactid is passed'() {
when:
stubFinder.trigger('some:service', 'return_book_1')
then:
thrown(IllegalArgumentException)
}
@Test
void 'should throw exception when missing label and artifactid is passed'() {
when:
BDDAssertions.thenThrownBy(() -> stubFinder.trigger('some:service', 'return_book_1')).isInstanceOf(IllegalArgumentException)
}
def 'should trigger messages by running all triggers'() {
when:
// tag::trigger_all[]
stubFinder.trigger()
// end::trigger_all[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
@Test
void 'should trigger messages by running all triggers'() {
when:
// tag::trigger_all[]
stubFinder.trigger()
// end::trigger_all[]
then:
Message<?> receivedMessage = messaging.receive('returnBook')
and:
assert receivedMessage != null
assertJsons(receivedMessage.payload)
assert receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label with no output message'() {
when:
// tag::trigger_no_output[]
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
// end::trigger_no_output[]
then:
noExceptionThrown()
}
@Test
void 'should trigger a label with no output message'() {
when:
// tag::trigger_no_output[]
messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
// end::trigger_no_output[]
}
def 'should not trigger a message that does not match input'() {
when:
messaging.send(new BookReturned('not_matching'), [wrong: 'header_value'], 'bookStorage')
then:
Message<?> receivedMessage = messaging.receive('returnBook', 100, TimeUnit.MILLISECONDS)
and:
receivedMessage == null
}
@Test
void 'should not trigger a message that does not match input'() {
when:
messaging.send(new BookReturned('not_matching'), [wrong: 'header_value'], 'bookStorage')
then:
Message<?> receivedMessage = messaging.receive('returnBook', 100, TimeUnit.MILLISECONDS)
and:
assert receivedMessage == null
}
private boolean assertJsons(Object payload) {
String objectAsString = payload instanceof String ? payload :
payload instanceof byte[] ? new String(payload)
: JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName == 'foo'
}
private boolean assertJsons(Object payload) {
String objectAsString = payload instanceof String ? payload :
payload instanceof byte[] ? new String(payload)
: JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName == 'foo'
}
Contract dsl =
// tag::sample_dsl[]
Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
// end::sample_dsl[]
Contract dsl =
// tag::sample_dsl[]
Contract.make {
label 'return_book_1'
input { triggeredBy('bookReturnedTriggered()') }
outputMessage {
sentTo('returnBook')
body('''{ "bookName" : "foo" }''')
headers { header('BOOK-NAME', 'foo') }
}
}
// end::sample_dsl[]
Contract dsl2 =
// tag::sample_dsl_2[]
Contract.make {
label 'return_book_2'
input {
messageFrom('bookStorage')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
}
outputMessage {
sentTo('returnBook')
body([
bookName: 'foo'
])
headers { header('BOOK-NAME', 'foo') }
}
}
// end::sample_dsl_2[]
Contract dsl2 =
// tag::sample_dsl_2[]
Contract.make {
label 'return_book_2'
input {
messageFrom('bookStorage')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
}
outputMessage {
sentTo('returnBook')
body([
bookName: 'foo'
])
headers { header('BOOK-NAME', 'foo') }
}
}
// end::sample_dsl_2[]
Contract dsl3 =
// tag::sample_dsl_3[]
Contract.make {
label 'delete_book'
input {
messageFrom('delete')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
assertThat('bookWasDeleted()')
}
}
// end::sample_dsl_3[]
Contract dsl3 =
// tag::sample_dsl_3[]
Contract.make {
label 'delete_book'
input {
messageFrom('delete')
messageBody([
bookName: 'foo'
])
messageHeaders { header('sample', 'header') }
assertThat('bookWasDeleted()')
}
}
// end::sample_dsl_3[]
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
@Configuration
@EnableAutoConfiguration
protected static class Config {
@ImportAutoConfiguration(TestChannelBinderConfiguration.class)
@Configuration
@EnableAutoConfiguration
protected static class Config {
}
@Bean
Function<String, String> test1() {
return (input) -> {
println "Test 1 [${input}]"
return input
}
}
}
@Bean
Consumer<String> test2() {
return (input) -> {
println "Test 2 [${input}]"
}
}
@Bean
Consumer<String> test3() {
return (input) -> {
println "Test 3 [${input}]"
}
}
}
}

View File

@@ -16,13 +16,13 @@
package org.springframework.cloud.contract.stubrunner.messaging.stream
import spock.lang.Specification
import org.junit.jupiter.api.Test
import org.springframework.cloud.contract.spec.Contract
import org.springframework.messaging.Message
import org.springframework.messaging.support.MessageBuilder
class StubRunnerStreamTransformerSpec extends Specification {
class StubRunnerStreamTransformerSpec {
Message message = MessageBuilder.withPayload("hello").build()
@@ -39,13 +39,14 @@ class StubRunnerStreamTransformerSpec extends Specification {
}
}
def 'should not transform the message if there is no output message'() {
@Test
void 'should not transform the message if there is no output message'() {
given:
StubRunnerStreamTransformer streamTransformer = new StubRunnerStreamTransformer(noOutputMessageContract)
when:
def result = streamTransformer.transform(message)
then:
result.is(message)
assert result.is(message)
}
def dsl = Contract.make {
@@ -70,7 +71,8 @@ class StubRunnerStreamTransformerSpec extends Specification {
}
}
def 'should convert dsl into message'() {
@Test
void 'should convert dsl into message'() {
given:
StubRunnerStreamTransformer streamTransformer = new StubRunnerStreamTransformer(dsl) {
@Override
@@ -81,7 +83,7 @@ class StubRunnerStreamTransformerSpec extends Specification {
when:
def result = streamTransformer.transform(message)
then:
result.payload == '{"responseId":"123"}'.bytes
assert result.payload == '{"responseId":"123"}'.bytes
}
def dslWithRegexInGString = Contract.make {
@@ -106,7 +108,8 @@ class StubRunnerStreamTransformerSpec extends Specification {
}
}
def 'should convert dsl into message with regex in GString'() {
@Test
void 'should convert dsl into message with regex in GString'() {
given:
StubRunnerStreamTransformer streamTransformer = new StubRunnerStreamTransformer(dslWithRegexInGString) {
@Override
@@ -117,10 +120,11 @@ class StubRunnerStreamTransformerSpec extends Specification {
when:
def result = streamTransformer.transform(message)
then:
result.payload == '''{"id":"99","temperature":"123.45"}'''.bytes
assert result.payload == '''{"id":"99","temperature":"123.45"}'''.bytes
}
def 'should parse dsl without DslProperty'() {
@Test
void 'should parse dsl without DslProperty'() {
given:
Contract contract = Contract.make {
// Human readable description
@@ -158,10 +162,11 @@ class StubRunnerStreamTransformerSpec extends Specification {
when:
def result = streamTransformer.transform(message)
then:
result.payload == '''{"orderId":"40058c70-891c-4176-a033-f70bad0c5f77","description":"This is the order description"}'''.bytes
assert result.payload == '''{"orderId":"40058c70-891c-4176-a033-f70bad0c5f77","description":"This is the order description"}'''.bytes
}
def 'should work for binary payloads from file'() {
@Test
void 'should work for binary payloads from file'() {
given:
Contract contract = Contract.make {
label 'send_order'
@@ -185,7 +190,7 @@ class StubRunnerStreamTransformerSpec extends Specification {
when:
def result = streamTransformer.transform(message)
then:
result.payload == StubRunnerStreamTransformerSpec.getResource("/response.pdf").bytes
assert result.payload == StubRunnerStreamTransformerSpec.getResource("/response.pdf").bytes
}
}

View File

@@ -5,10 +5,16 @@ spring:
cloud:
stream:
bindings:
output:
test1-out-0:
destination: returnBook
input:
test1-in-0:
destination: bookStorage
test2-in-0:
destination: delete
test3-in-0:
destination: returnBook
function:
definition: test1;test2;test3
server:
port: 0