diff --git a/README.adoc b/README.adoc index 7106b452c4..bb5706f008 100644 --- a/README.adoc +++ b/README.adoc @@ -22,6 +22,11 @@ http://wiremock.org[WireMock] with different servers by using the https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples[samples] for more details. +IMPORTANT: The Spring Cloud Release Train BOM imports `spring-cloud-contract-dependencies` + which in turn has exclusions for the dependencies needed by WireMock. This might lead to a situation that + even if you're not using Spring Cloud Contract then your dependencies will be influenced + anyways. + If you have a Spring Boot application that uses Tomcat as an embedded server, for example (the default with `spring-boot-starter-web`), then you can simply add `spring-cloud-contract-wiremock` to your classpath diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy index 1ab61e7969..2e13e60b0e 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/server/StubRunnerBootSpec.groovy @@ -20,15 +20,18 @@ import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc import groovy.json.JsonSlurper 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 import spock.lang.Specification + /** * @author Marcin Grzejszczak */ // tag::boot_usage[] @ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader) +@SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false") @ActiveProfiles("test") class StubRunnerBootSpec extends Specification { diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy index b4300338c5..25b794f9b4 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfigurationSpec.groovy @@ -21,6 +21,7 @@ import org.junit.BeforeClass 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.contract.stubrunner.StubFinder import org.springframework.cloud.contract.stubrunner.StubNotFoundException import org.springframework.context.annotation.Configuration @@ -36,6 +37,7 @@ import spock.lang.Specification // Not necessary if Spring Cloud is used. TODO: make it work without this. // tag::test[] @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) +@SpringBootTest(properties = [" stubrunner.cloud.enabled=false", "stubrunner.camel.enabled=false"]) @AutoConfigureStubRunner @DirtiesContext @ActiveProfiles("test") diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java index d42e63d0d3..5ccf0945fe 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java @@ -71,7 +71,6 @@ public class ContractVerifierAmqpAutoConfiguration { @Bean @ConditionalOnMissingBean public MessageVerifier contractVerifierMessageExchange() { - return new SpringAmqpStubMessages(this.rabbitTemplate, new MessageListenerAccessor(this.rabbitListenerEndpointRegistry, this.simpleMessageListenerContainers, this.bindings)); } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java index 154366ec36..bfe38df2ff 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java @@ -14,7 +14,7 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; /** * Abstraction hiding details of the different sources of message listeners * - * Needed because {@link org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor} adds the listners to the + * Needed because {@link org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor} adds the listeners to the * {@link RabbitListenerEndpointRegistry} so that the registry is empty when wired into an auto configuration class * so we wrap it in the accessor to access the listeners late at runtime * @@ -38,7 +38,6 @@ class MessageListenerAccessor { List listenerContainers = collectListenerContainers(); //we interpret the destination as exchange name and collect all the queues bound to this exchange Set queueNames = collectQueuesBoundToDestination(destination); - return getListenersByBoundQueues(listenerContainers, queueNames); } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapper.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapper.java index 375d04ef56..b887a0ed72 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapper.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapper.java @@ -40,6 +40,8 @@ public class ContractVerifierObjectMapper { public String writeValueAsString(Object payload) throws JsonProcessingException { if (payload instanceof String) { return payload.toString(); + } else if (payload instanceof byte[]) { + return new String((byte[]) payload); } return this.objectMapper.writeValueAsString(payload); } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapperSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapperSpec.groovy new file mode 100644 index 0000000000..6ca12701d9 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierObjectMapperSpec.groovy @@ -0,0 +1,30 @@ +package org.springframework.cloud.contract.verifier.messaging.internal + +import spock.lang.Specification + +class ContractVerifierObjectMapperSpec extends Specification { + + ContractVerifierObjectMapper mapper = new ContractVerifierObjectMapper() + + def "should convert an object into a json representation"() { + given: + MyClass input = new MyClass(foo: "bar") + when: + String result = mapper.writeValueAsString(input) + then: + result == '''{"foo":"bar"}''' + } + + def "should convert bytes into a json representation"() { + given: + String input = '''{"foo":"bar"}''' + when: + String result = mapper.writeValueAsString(input.bytes) + then: + result == '''{"foo":"bar"}''' + } + + class MyClass { + String foo + } +} diff --git a/tests/samples-messaging-amqp/src/main/java/com/example/AmqpMessagingApplication.java b/tests/samples-messaging-amqp/src/main/java/com/example/AmqpMessagingApplication.java index 251f9f738d..12738954e7 100644 --- a/tests/samples-messaging-amqp/src/main/java/com/example/AmqpMessagingApplication.java +++ b/tests/samples-messaging-amqp/src/main/java/com/example/AmqpMessagingApplication.java @@ -43,4 +43,10 @@ public class AmqpMessagingApplication { public Exchange testExchange() { return new TopicExchange("test-exchange"); } + + @Bean + MessagePublisher messagePublisher(RabbitTemplate rabbitTemplate) { + return new MessagePublisher(rabbitTemplate, testExchange()); + } + } diff --git a/tests/samples-messaging-amqp/src/main/java/com/example/Issue178ListenerConfiguration.java b/tests/samples-messaging-amqp/src/main/java/com/example/Issue178ListenerConfiguration.java new file mode 100644 index 0000000000..4ba68e4563 --- /dev/null +++ b/tests/samples-messaging-amqp/src/main/java/com/example/Issue178ListenerConfiguration.java @@ -0,0 +1,81 @@ +package com.example; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageBuilder; +import org.springframework.amqp.core.MessageListener; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Marcin Grzejszczak + */ +@Configuration +class Issue178ListenerConfiguration { + @Bean + SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory, RabbitTemplate rabbitTemplate) { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.addQueueNames("rated-item-service.rated-item-event.exchange"); + container.setMessageListener(exampleListener(rabbitTemplate)); + return container; + } + + @Bean + MessageListener exampleListener(final RabbitTemplate rabbitTemplate) { + return new MessageListener() { + public void onMessage(Message message) { + System.out.println("received: " + message); + try { + String payload = new ObjectMapper().writeValueAsString(new MyPojo("992e46d8-ab05-4a26-a740-6ef7b0daeab3", "CREATED")); + Message outputMessage = MessageBuilder.withBody(payload.getBytes()).build(); + rabbitTemplate.send(issue178OutputExchange().getName(), "routingkey", outputMessage); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + }; + } + + static class MyPojo { + public String ratedItemId; + public String eventType; + + public MyPojo(String ratedItemId, String eventType) { + this.ratedItemId = ratedItemId; + this.eventType = eventType; + } + + public MyPojo() { + } + } + + @Bean Queue issue178InputQueue() { + return new Queue("rated-item-service.rated-item-event.exchange", false); + } + + @Bean + TopicExchange issue178InputExchange() { + return new TopicExchange("rated-item-service.rated-item-event.exchange"); + } + + @Bean + TopicExchange issue178OutputExchange() { + return new TopicExchange("bill-service.rated-item-event.retry-exchange"); + } + + @Bean Binding binding() { + return BindingBuilder.bind(issue178InputQueue()).to(issue178InputExchange()).with("rated-item-service.rated-item-event.exchange"); + } + +} diff --git a/tests/samples-messaging-amqp/src/main/java/com/example/MessagePublisher.java b/tests/samples-messaging-amqp/src/main/java/com/example/MessagePublisher.java index 36890870a3..420ade4ff0 100644 --- a/tests/samples-messaging-amqp/src/main/java/com/example/MessagePublisher.java +++ b/tests/samples-messaging-amqp/src/main/java/com/example/MessagePublisher.java @@ -2,16 +2,13 @@ package com.example; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -@Component public class MessagePublisher { private final RabbitTemplate rabbitTemplate; private final Exchange exchange; - @Autowired + public MessagePublisher(RabbitTemplate rabbitTemplate, Exchange exchange) { this.rabbitTemplate = rabbitTemplate; this.exchange = exchange; diff --git a/tests/samples-messaging-amqp/src/test/groovy/com/example/AmqpMessagingApplicationSpec.groovy b/tests/samples-messaging-amqp/src/test/groovy/com/example/AmqpMessagingApplicationSpec.groovy index 88499d4a37..43d70daa04 100644 --- a/tests/samples-messaging-amqp/src/test/groovy/com/example/AmqpMessagingApplicationSpec.groovy +++ b/tests/samples-messaging-amqp/src/test/groovy/com/example/AmqpMessagingApplicationSpec.groovy @@ -19,19 +19,24 @@ package com.example import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath import com.toomuchcoding.jsonassert.JsonAssertion +import groovy.json.JsonOutput 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.spec.Contract import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper import org.springframework.test.context.ContextConfiguration +import spock.lang.Issue import spock.lang.Specification import javax.inject.Inject + // Context configuration would end up in base class @ContextConfiguration(classes = [AmqpMessagingApplication], loader = SpringBootContextLoader) @AutoConfigureMessageVerifier +@SpringBootTest(properties = "stubrunner.amqp.enabled=true") class AmqpMessagingApplicationSpec extends Specification { // ALL CASES @@ -74,6 +79,44 @@ class AmqpMessagingApplicationSpec extends Specification { JsonAssertion.assertThat(parsedJson).field('name').isEqualTo('some') } + @Issue("178") + def "should work for input/output when bytes are used"() { + given: + def inputBody = [ + ratedItemId: "992e46d8-ab05-4a26-a740-6ef7b0daeab3", + eventType: "CREATED" + ] + def dsl = Contract.make { + label 'ratedItem-no-metricid' + input { + messageFrom("rated-item-service.rated-item-event.exchange") + messageHeaders { + header("X-tenant", "1234") + header("contentType", "application/json") + } + messageBody(inputBody) + } + outputMessage { + sentTo('bill-service.rated-item-event.retry-exchange') + body( + ratedItemId: "992e46d8-ab05-4a26-a740-6ef7b0daeab3", + eventType: "CREATED" + ) + } + } + when: + contractVerifierMessaging.send(contractVerifierMessaging.create(new JsonOutput().toJson(inputBody), [ + "X-tenant": "1234", + "contentType": "application/json" + ]), "rated-item-service.rated-item-event.exchange") + then: + def response = contractVerifierMessaging.receive('bill-service.rated-item-event.retry-exchange') + and: + DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload)) + JsonAssertion.assertThat(parsedJson).field('ratedItemId').isEqualTo('992e46d8-ab05-4a26-a740-6ef7b0daeab3') + JsonAssertion.assertThat(parsedJson).field('eventType').isEqualTo('CREATED') + } + // BASE CLASS WOULD HAVE THIS: @Autowired MessagePublisher messagePublisher diff --git a/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy b/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy index ac085b7275..b4ffc9c910 100644 --- a/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy +++ b/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy @@ -21,6 +21,7 @@ import com.jayway.jsonpath.JsonPath import com.toomuchcoding.jsonassert.JsonAssertion 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.spec.Contract import org.springframework.cloud.contract.verifier.messaging.MessageVerifier import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier @@ -37,6 +38,7 @@ import javax.inject.Inject // Context configuration would end up in base class @ContextConfiguration(classes = [StreamMessagingApplication], loader = SpringBootContextLoader) @DirtiesContext +@SpringBootTest(properties = "debug=true") @AutoConfigureMessageVerifier public class StreamMessagingApplicationSpec extends Specification { diff --git a/tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy b/tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy index 219ba928ee..eb5b5b3537 100644 --- a/tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.test.context.SpringBootContextLoader +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 @@ -40,6 +41,7 @@ import spock.lang.Specification * @author Marcin Grzejszczak */ @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) +@SpringBootTest(properties = "debug=true") @AutoConfigureStubRunner @IgnoreIf({ os.windows }) class CamelStubRunnerSpec extends Specification { diff --git a/tests/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy b/tests/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy index 84722c308b..a17f1ebac5 100644 --- a/tests/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-stream/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy @@ -21,6 +21,7 @@ import groovy.json.JsonSlurper 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.contract.spec.Contract import org.springframework.cloud.contract.stubrunner.StubFinder import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner @@ -36,10 +37,12 @@ import spock.lang.IgnoreIf import spock.lang.Specification import java.util.concurrent.TimeUnit + /** * @author Marcin Grzejszczak */ @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) +@SpringBootTest(properties = "debug=true") @AutoConfigureStubRunner @AutoConfigureMessageVerifier @IgnoreIf({ os.windows })