From fa51d6a076be8c5dc620ea6e226ad6c87dc2c0ac Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 15 Nov 2022 12:20:56 +0100 Subject: [PATCH] Brings back Eureka support, updates samples, removes mocked kafka & rabbit samples --- docs/pom.xml | 8 +- pom.xml | 62 +++- spring-cloud-contract-stub-runner/pom.xml | 5 - .../stubrunner/BatchStubRunnerFactory.java | 8 +- .../cloud/contract/stubrunner/StubRunner.java | 4 +- .../stubrunner/StubRunnerExecutor.java | 6 +- .../stubrunner/StubRunnerFactory.java | 6 +- .../kafka/StubRunnerKafkaConfiguration.java | 15 - .../spring/StubRunnerConfiguration.java | 38 ++- .../cloud/eureka/EurekaStubsRegistrar.java | 12 +- ...ner.spring.AutoConfigureStubRunner.imports | 1 + .../StubRunnerBootEurekaExample.java | 2 - ...nerSpringCloudAutoConfigurationSpec.groovy | 95 +++--- ...gCloudReactiveAutoConfigurationSpec.groovy | 93 +++--- ...ContractVerifierAmqpAutoConfiguration.java | 119 -------- .../amqp/MessageListenerAccessor.java | 109 ------- ...ockConnectionFactoryAutoConfiguration.java | 88 ------ .../amqp/SpringAmqpStubMessages.java | 212 ------------- .../ContractVerifierCamelConfiguration.java | 10 +- ...tractVerifierIntegrationConfiguration.java | 10 +- .../internal/ContractVerifierMessaging.java | 22 +- .../jms/ContractVerifierJmsConfiguration.java | 10 +- .../ContractVerifierKafkaConfiguration.java | 116 ------- ...tVerifierKafkaStubMessagesInitializer.java | 67 ----- .../messaging/kafka/KafkaStubMessages.java | 171 ----------- .../kafka/KafkaStubMessagesInitializer.java | 43 --- ...NoOpContractVerifierAutoConfiguration.java | 8 +- ...ntractVerifierStreamAutoConfiguration.java | 10 +- ....boot.AutoConfigureMessageVerifier.imports | 3 - .../amqp/ContractVerifierHelperSpec.groovy | 51 ---- .../amqp/MessageListenerAccessorSpec.groovy | 106 ------- .../amqp/SpringAmqpStubMessagesSpec.groovy | 221 -------------- .../ContractVerifierKafkaHelperSpec.groovy | 77 ----- ...actVerifierAmqpAutoConfigurationTests.java | 78 ----- .../amqp/SpringAmqpStubMessagesTests.java | 55 ---- ...ntractVerifierKafkaConfigurationTests.java | 96 ------ tests/pom.xml | 20 +- .../AmqpMessagingApplicationSpec.groovy | 2 + tests/samples-messaging-camel/pom.xml | 32 +- .../main/java/com/example/BookReturned.java | 5 + .../com/example/BookRouteConfiguration.java | 26 +- .../CamelMessagingApplicationSpec.groovy | 62 ++-- .../src/test/resources/logback-test.xml | 15 + tests/samples-messaging-jms/pom.xml | 17 +- .../main/java/com/example/BookDeleter.java | 7 +- .../main/java/com/example/BookService.java | 2 +- .../JmsMessagingApplicationSpec.groovy | 247 +++++++-------- .../AmqpStubRunnerRabbitListenerSpec.groovy | 2 + .../messaging/amqp/AmqpStubRunnerSpec.groovy | 2 + .../pom.xml | 28 +- ...ingCloudEurekaAutoConfigurationSpec.groovy | 91 +++--- .../pom.xml | 19 -- .../camel/CamelStubRunnerSpec.groovy | 283 ------------------ .../pom.xml | 30 +- .../messaging/jms/JmsStubRunnerSpec.groovy | 108 ++++--- .../test/resources/stubs/bookReturned1.groovy | 4 +- .../test/resources/stubs/bookReturned2.groovy | 4 +- .../kafka/KafkaStubRunnerSpec.groovy | 2 + 58 files changed, 611 insertions(+), 2434 deletions(-) delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/RabbitMockConnectionFactoryAutoConfiguration.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessages.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfiguration.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java delete mode 100644 spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessagesInitializer.java delete mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierHelperSpec.groovy delete mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessorSpec.groovy delete mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesSpec.groovy delete mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaHelperSpec.groovy delete mode 100644 spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfigurationTests.java delete mode 100644 spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesTests.java delete mode 100644 spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfigurationTests.java create mode 100644 tests/samples-messaging-camel/src/test/resources/logback-test.xml delete mode 100644 tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy diff --git a/docs/pom.xml b/docs/pom.xml index 4550fde76e..cacfde52de 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -19,8 +19,7 @@ ${basedir}/.. 3.4 stubrunner.*|wiremock.*| - - + deploy 4.0.0 @@ -149,11 +148,10 @@ org.asciidoctor asciidoctor-maven-plugin - - + maven-deploy-plugin diff --git a/pom.xml b/pom.xml index 6438ba9e64..d5342f3fbc 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,8 @@ 1.13.0 3.0.0-M7 4.2.0 + ${artemis-jms-server.version} + 1.17.5 4.0.0 @@ -70,6 +72,7 @@ false false + 2.26.0 @@ -119,6 +122,26 @@ camel-spring-boot ${camel.version} + + org.apache.camel.springboot + camel-rabbitmq-starter + ${camel.version} + + + org.apache.camel.springboot + camel-direct-starter + ${camel.version} + + + org.apache.camel.springboot + camel-bean-starter + ${camel.version} + + + org.apache.camel.springboot + camel-jackson-starter + ${camel.version} + org.apache.camel camel-core @@ -144,6 +167,11 @@ camel-activemq ${camel.version} + + org.apache.activemq + artemis-jms-server + ${artemis-jms-server.version} + net.sf.jopt-simple jopt-simple @@ -477,7 +505,21 @@ org.awaitility awaitility ${awaitility.version} - test + + + org.apache.activemq + artemis-junit + ${artemis.version} + + + org.testcontainers + rabbitmq + ${testcontainers.version} + + + org.testcontainers + junit-jupiter + ${testcontainers.version} @@ -620,6 +662,24 @@ true + + + + + netflix-candidates + Netflix Candidates + https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates + + false + + diff --git a/spring-cloud-contract-stub-runner/pom.xml b/spring-cloud-contract-stub-runner/pom.xml index 50aa6d7cca..6cfd44ddcf 100644 --- a/spring-cloud-contract-stub-runner/pom.xml +++ b/spring-cloud-contract-stub-runner/pom.xml @@ -75,11 +75,6 @@ jopt-simple true - org.springframework.kafka spring-kafka diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunnerFactory.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunnerFactory.java index 45e0b5bd7f..a16d126151 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunnerFactory.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/BatchStubRunnerFactory.java @@ -16,7 +16,7 @@ package org.springframework.cloud.contract.stubrunner; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages; /** @@ -32,13 +32,13 @@ public class BatchStubRunnerFactory { private final StubDownloader stubDownloader; - private final MessageVerifier contractVerifierMessaging; + private final MessageVerifierSender contractVerifierMessaging; public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions) { this(stubRunnerOptions, new NoOpStubMessages()); } - public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, MessageVerifier verifier) { + public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, MessageVerifierSender verifier) { this(stubRunnerOptions, aetherStubDownloader(stubRunnerOptions), verifier); } @@ -47,7 +47,7 @@ public class BatchStubRunnerFactory { } public BatchStubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader, - MessageVerifier contractVerifierMessaging) { + MessageVerifierSender contractVerifierMessaging) { this.stubRunnerOptions = stubRunnerOptions; this.stubDownloader = stubDownloader; this.contractVerifierMessaging = contractVerifierMessaging; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java index 29b6967cf7..6a337eccaf 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunner.java @@ -30,7 +30,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.contract.spec.Contract; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.StringUtils; @@ -60,7 +60,7 @@ public class StubRunner implements StubRunning { } public StubRunner(StubRunnerOptions stubRunnerOptions, String repositoryPath, StubConfiguration stubsConfiguration, - MessageVerifier contractVerifierMessaging) { + MessageVerifierSender contractVerifierMessaging) { this.stubsConfiguration = stubsConfiguration; this.stubRunnerOptions = stubRunnerOptions; List serverStubs = SpringFactoriesLoader.loadFactories(HttpServerStub.class, null); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java index d05767254b..371a1f48bf 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerExecutor.java @@ -41,7 +41,7 @@ import org.springframework.cloud.contract.stubrunner.AvailablePortScanner.PortCa import org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockHttpServerStub; import org.springframework.cloud.contract.verifier.converter.YamlContract; import org.springframework.cloud.contract.verifier.converter.YamlContractConverter; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessageMetadata; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages; import org.springframework.cloud.contract.verifier.util.BodyExtractor; @@ -57,7 +57,7 @@ class StubRunnerExecutor implements StubFinder { private final AvailablePortScanner portScanner; - private final MessageVerifier contractVerifierMessaging; + private final MessageVerifierSender contractVerifierMessaging; private final List serverStubs; @@ -65,7 +65,7 @@ class StubRunnerExecutor implements StubFinder { private final YamlContractConverter yamlContractConverter = new YamlContractConverter(); - StubRunnerExecutor(AvailablePortScanner portScanner, MessageVerifier contractVerifierMessaging, + StubRunnerExecutor(AvailablePortScanner portScanner, MessageVerifierSender contractVerifierMessaging, List serverStubs) { this.portScanner = portScanner; this.contractVerifierMessaging = contractVerifierMessaging; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java index b26970e919..106e77aa11 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java @@ -36,7 +36,7 @@ import org.springframework.cloud.contract.stubrunner.provider.wiremock.WireMockH import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter; import org.springframework.cloud.contract.verifier.converter.StubGenerator; import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter; import org.springframework.core.io.Resource; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -53,10 +53,10 @@ class StubRunnerFactory { private final StubDownloader stubDownloader; - private final MessageVerifier contractVerifierMessaging; + private final MessageVerifierSender contractVerifierMessaging; StubRunnerFactory(StubRunnerOptions stubRunnerOptions, StubDownloader stubDownloader, - MessageVerifier contractVerifierMessaging) { + MessageVerifierSender contractVerifierMessaging) { this.stubRunnerOptions = stubRunnerOptions; this.stubDownloader = stubDownloader; this.contractVerifierMessaging = contractVerifierMessaging; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaConfiguration.java index 7543e615b7..284408f95e 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.cloud.contract.stubrunner.messaging.kafka; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -26,7 +25,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -34,8 +32,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.contract.spec.Contract; import org.springframework.cloud.contract.stubrunner.BatchStubRunner; import org.springframework.cloud.contract.stubrunner.StubConfiguration; -import org.springframework.cloud.contract.verifier.messaging.kafka.ContractVerifierKafkaConfiguration; -import org.springframework.cloud.contract.verifier.messaging.kafka.KafkaStubMessagesInitializer; import org.springframework.cloud.contract.verifier.util.MapConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -59,21 +55,10 @@ import org.springframework.util.StringUtils; @ConditionalOnClass({ KafkaTemplate.class, EmbeddedKafkaBroker.class }) @ConditionalOnProperty(name = "stubrunner.kafka.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnBean(EmbeddedKafkaBroker.class) -@AutoConfigureBefore(ContractVerifierKafkaConfiguration.class) public class StubRunnerKafkaConfiguration { private static final Log log = LogFactory.getLog(StubRunnerKafkaConfiguration.class); - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = "stubrunner.kafka.initializer.enabled", havingValue = "true", matchIfMissing = true) - KafkaStubMessagesInitializer stubRunnerKafkaStubMessagesInitializer() { - if (log.isDebugEnabled()) { - log.debug("Registering a noop kafka messages initializer"); - } - return (broker, kafkaProperties) -> new HashMap<>(); - } - @Bean @ConditionalOnMissingBean(name = "stubFlowRegistrar") public FlowRegistrar stubFlowRegistrar(ConfigurableListableBeanFactory beanFactory, diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java index e22050cf05..0c3cb2772c 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/StubRunnerConfiguration.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -36,6 +35,8 @@ import org.springframework.cloud.contract.stubrunner.StubRunnerOptions; import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder; import org.springframework.cloud.contract.verifier.converter.YamlContract; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpStubMessages; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -156,9 +157,12 @@ public class StubRunnerConfiguration { } +@SuppressWarnings("unchecked") class LazyMessageVerifier implements MessageVerifier { - private MessageVerifier messageVerifier; + private MessageVerifierSender messageVerifierSender; + + private MessageVerifierReceiver messageVerifierReceiver; private final BeanFactory beanFactory; @@ -166,36 +170,40 @@ class LazyMessageVerifier implements MessageVerifier { this.beanFactory = beanFactory; } - private MessageVerifier messageVerifier() { - if (this.messageVerifier == null) { - try { - this.messageVerifier = this.beanFactory.getBean(MessageVerifier.class); - } - catch (BeansException ex) { - this.messageVerifier = new NoOpStubMessages(); - } + private MessageVerifierSender messageVerifierSender() { + if (this.messageVerifierSender == null) { + this.messageVerifierSender = this.beanFactory.getBeanProvider(MessageVerifierSender.class) + .getIfAvailable(NoOpStubMessages::new); } - return this.messageVerifier; + return this.messageVerifierSender; + } + + private MessageVerifierReceiver messageVerifierReceiver() { + if (this.messageVerifierReceiver == null) { + this.messageVerifierReceiver = this.beanFactory.getBeanProvider(MessageVerifierReceiver.class) + .getIfAvailable(NoOpStubMessages::new); + } + return this.messageVerifierReceiver; } @Override public void send(Object message, String destination, YamlContract contract) { - messageVerifier().send(message, destination, contract); + messageVerifierSender().send(message, destination, contract); } @Override public Object receive(String destination, long timeout, TimeUnit timeUnit, YamlContract contract) { - return messageVerifier().receive(destination, timeout, timeUnit, contract); + return messageVerifierReceiver().receive(destination, timeout, timeUnit, contract); } @Override public Object receive(String destination, YamlContract contract) { - return messageVerifier().receive(destination, contract); + return messageVerifierReceiver().receive(destination, contract); } @Override public void send(Object payload, Map headers, String destination, YamlContract contract) { - messageVerifier().send(payload, headers, destination, contract); + messageVerifierSender().send(payload, headers, destination, contract); } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java index ad13b2e6c0..7d9d3983eb 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java @@ -30,14 +30,17 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; +import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.contract.stubrunner.StubConfiguration; import org.springframework.cloud.contract.stubrunner.StubRunning; import org.springframework.cloud.contract.stubrunner.spring.cloud.StubMapperProperties; import org.springframework.cloud.contract.stubrunner.spring.cloud.StubsRegistrar; +import org.springframework.cloud.loadbalancer.support.SimpleObjectProvider; import org.springframework.cloud.netflix.eureka.CloudEurekaClient; import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean; +import org.springframework.cloud.netflix.eureka.EurekaHealthCheckHandler; import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; import org.springframework.cloud.netflix.eureka.InstanceInfoFactory; import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration; @@ -92,11 +95,16 @@ public class EurekaStubsRegistrar implements StubsRegistrar { + ", " + instance.getNonSecurePort() + ", " + instance.getInstanceId() + "]"); InstanceInfo instanceInfo = new InstanceInfoFactory().create(instance); ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(instance, instanceInfo); - AbstractDiscoveryClientOptionalArgs args = args(); + AbstractDiscoveryClientOptionalArgs args = args(); EurekaClient client = new CloudEurekaClient(applicationInfoManager, this.eurekaClientConfigBean, args, this.context); EurekaRegistration registration = EurekaRegistration.builder(instance) .with(this.eurekaClientConfigBean, this.context).with(client).build(); + EurekaHealthCheckHandler eurekaHealthCheckHandler = new EurekaHealthCheckHandler( + StatusAggregator.getDefault()); + eurekaHealthCheckHandler.setApplicationContext(context); + eurekaHealthCheckHandler.afterPropertiesSet(); + registration.setHealthCheckHandler(new SimpleObjectProvider<>(eurekaHealthCheckHandler)); this.registrations.add(registration); try { this.serviceRegistry.register(registration); @@ -110,7 +118,7 @@ public class EurekaStubsRegistrar implements StubsRegistrar { } } - private AbstractDiscoveryClientOptionalArgs args() { + private AbstractDiscoveryClientOptionalArgs args() { try { return this.context.getBean(AbstractDiscoveryClientOptionalArgs.class); } diff --git a/spring-cloud-contract-stub-runner/src/main/resources/META-INF/spring/org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner.imports b/spring-cloud-contract-stub-runner/src/main/resources/META-INF/spring/org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner.imports index 59fd232984..15c6286085 100644 --- a/spring-cloud-contract-stub-runner/src/main/resources/META-INF/spring/org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner.imports +++ b/spring-cloud-contract-stub-runner/src/main/resources/META-INF/spring/org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner.imports @@ -4,6 +4,7 @@ org.springframework.cloud.contract.stubrunner.spring.cloud.loadbalancer.SpringCl org.springframework.cloud.contract.stubrunner.messaging.integration.StubRunnerIntegrationConfiguration org.springframework.cloud.contract.stubrunner.messaging.jms.StubRunnerJmsConfiguration org.springframework.cloud.contract.stubrunner.messaging.stream.StubRunnerStreamConfiguration +org.springframework.cloud.contract.stubrunner.spring.cloud.eureka.StubRunnerSpringCloudEurekaAutoConfiguration org.springframework.cloud.contract.stubrunner.spring.cloud.zookeeper.StubRunnerSpringCloudZookeeperAutoConfiguration org.springframework.cloud.contract.stubrunner.spring.cloud.consul.StubRunnerSpringCloudConsulAutoConfiguration org.springframework.cloud.contract.stubrunner.messaging.StubRunnerStreamsIntegrationAutoConfiguration diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java index 5158dda531..1c33b26645 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/serverexamples/StubRunnerBootEurekaExample.java @@ -20,7 +20,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer; import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * @author Marcin Grzejszczak @@ -28,7 +27,6 @@ import org.springframework.cloud.netflix.eureka.EnableEurekaClient; // tag::stubrunnereureka[] @SpringBootApplication @EnableStubRunnerServer -@EnableEurekaClient @AutoConfigureStubRunner public class StubRunnerBootEurekaExample { diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy index aafcf1e6ff..5183c14507 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudAutoConfigurationSpec.groovy @@ -16,7 +16,10 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud -import spock.lang.Specification +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -42,55 +45,59 @@ import org.springframework.web.client.RestTemplate @ActiveProfiles("cloudtest") // tag::autoconfigure[] @AutoConfigureStubRunner( - ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", - "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", - "org.springframework.cloud.contract.verifier.stubs:bootService"], - stubsMode = StubRunnerProperties.StubsMode.REMOTE, - repositoryRoot = "classpath:m2repo/repository/") + ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"], + stubsMode = StubRunnerProperties.StubsMode.REMOTE, + repositoryRoot = "classpath:m2repo/repository/") // end::autoconfigure[] -class StubRunnerSpringCloudAutoConfigurationSpec extends Specification { +class StubRunnerSpringCloudAutoConfigurationSpec { - @Autowired - StubFinder stubFinder - @Autowired - @LoadBalanced - RestTemplate restTemplate - @Autowired - LoadBalancerClientFactory loadBalancerClientFactory; + @Autowired + StubFinder stubFinder + @Autowired + @LoadBalanced + RestTemplate restTemplate + @Autowired + LoadBalancerClientFactory loadBalancerClientFactory; - void setupSpec() { - System.clearProperty("stubrunner.repository.root") - System.clearProperty("stubrunner.classifier") - } + @BeforeAll + static void setupSpec() { + System.clearProperty("stubrunner.repository.root") + System.clearProperty("stubrunner.classifier") + } - void cleanupSpec() { - setupSpec() - } + @AfterAll + static void cleanupSpec() { + setupSpec() + } - def setup() { - assert loadBalancerClientFactory instanceof StubRunnerLoadBalancerClientFactory - } + @BeforeEach + void setup() { + assert loadBalancerClientFactory.getClass().getSimpleName() == "StubRunnerLoadBalancerClientFactory" + } - // tag::test[] - def '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' - and: 'Stubs can be reached via load service discovery' - restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' - restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' - } - // end::test[] + // tag::test[] + @Test + void 'should make service discovery work'() { + expect: 'WireMocks are running' + assert "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + assert "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' + and: 'Stubs can be reached via load service discovery' + assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' + assert restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' + } + // end::test[] - @Configuration - @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, - ConsulAutoConfiguration, ZookeeperAutoConfiguration]) - static class Config { + @Configuration + @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, + ConsulAutoConfiguration, ZookeeperAutoConfiguration]) + static class Config { - @Bean - @LoadBalanced - RestTemplate restTemplate() { - return new RestTemplate() - } - } + @Bean + @LoadBalanced + RestTemplate restTemplate() { + return new RestTemplate() + } + } } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy index 1cd17eacab..6466928c58 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/StubRunnerSpringCloudReactiveAutoConfigurationSpec.groovy @@ -18,7 +18,8 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud import org.junit.AfterClass import org.junit.BeforeClass -import spock.lang.Specification +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -45,53 +46,55 @@ import org.springframework.web.client.RestTemplate @SpringBootTest(classes = Config) @ActiveProfiles("cloudtest") @AutoConfigureStubRunner( - ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", - "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", - "org.springframework.cloud.contract.verifier.stubs:bootService"], - stubsMode = StubRunnerProperties.StubsMode.REMOTE, - repositoryRoot = "classpath:m2repo/repository/") -class StubRunnerSpringCloudReactiveAutoConfigurationSpec extends Specification { - @Autowired - StubFinder stubFinder - @Autowired - ReactiveDiscoveryClient reactiveDiscoveryClient; - @Autowired - LoadBalancerClientFactory loadBalancerClientFactory; - RestTemplate restTemplate = new RestTemplate() + ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"], + stubsMode = StubRunnerProperties.StubsMode.REMOTE, + repositoryRoot = "classpath:m2repo/repository/") +class StubRunnerSpringCloudReactiveAutoConfigurationSpec { + @Autowired + StubFinder stubFinder + @Autowired + ReactiveDiscoveryClient reactiveDiscoveryClient; + @Autowired + LoadBalancerClientFactory loadBalancerClientFactory; + RestTemplate restTemplate = new RestTemplate() - @BeforeClass - @AfterClass - static void setupProps() { - System.clearProperty("stubrunner.repository.root") - System.clearProperty("stubrunner.classifier") - } + @BeforeClass + @AfterClass + static void setupProps() { + System.clearProperty("stubrunner.repository.root") + System.clearProperty("stubrunner.classifier") + } - def setup() { - assert loadBalancerClientFactory instanceof StubRunnerLoadBalancerClientFactory - } + @BeforeEach + void setup() { + assert loadBalancerClientFactory.getClass().getSimpleName() == "StubRunnerLoadBalancerClientFactory" + } - // tag::test[] - def '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' - and: 'Stubs can be reached via load service discovery' - ServiceInstance loanIssuance = reactiveDiscoveryClient.getInstances('loanIssuance').blockFirst() - restTemplate.getForObject(loanIssuance.uri.toString() + '/name', String) == 'loanIssuance' - ServiceInstance fraudDetection = reactiveDiscoveryClient.getInstances('someNameThatShouldMapFraudDetectionServer').blockFirst() - restTemplate.getForObject(fraudDetection.uri.toString() + '/name', String) == 'fraudDetectionServer' - } - // end::test[] + // tag::test[] + @Test + void 'should make service discovery work'() { + expect: 'WireMocks are running' + assert "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + assert "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' + and: 'Stubs can be reached via load service discovery' + ServiceInstance loanIssuance = reactiveDiscoveryClient.getInstances('loanIssuance').blockFirst() + assert restTemplate.getForObject(loanIssuance.uri.toString() + '/name', String) == 'loanIssuance' + ServiceInstance fraudDetection = reactiveDiscoveryClient.getInstances('someNameThatShouldMapFraudDetectionServer').blockFirst() + assert restTemplate.getForObject(fraudDetection.uri.toString() + '/name', String) == 'fraudDetectionServer' + } + // end::test[] - @Configuration - @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, - ConsulAutoConfiguration, ZookeeperAutoConfiguration]) - static class Config { + @Configuration + @EnableAutoConfiguration(exclude = [EurekaClientAutoConfiguration, + ConsulAutoConfiguration, ZookeeperAutoConfiguration]) + static class Config { - @Bean - @LoadBalanced - RestTemplate restTemplate() { - return new RestTemplate() - } - } + @Bean + @LoadBalanced + RestTemplate restTemplate() { + return new RestTemplate() + } + } } 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 deleted file mode 100644 index d08583d99c..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfiguration.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import java.util.List; - -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.SimpleAmqpHeaderMapper; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.amqp.support.converter.MessagingMessageConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.amqp.RabbitProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; -import org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration; -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; -import org.springframework.cloud.contract.verifier.messaging.stream.ContractVerifierStreamAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static java.util.Collections.emptyList; - -/** - * Configuration setting up {@link MessageVerifier} for use with plain - * spring-rabbit/spring-amqp. - * - * @author Mathias Düsterhöft - * @since 1.0.2 - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(RabbitTemplate.class) -@AutoConfigureBefore(ContractVerifierIntegrationConfiguration.class) -@AutoConfigureAfter(ContractVerifierStreamAutoConfiguration.class) -public class ContractVerifierAmqpAutoConfiguration { - - @Bean - @ConditionalOnBean({ RabbitTemplate.class, MessageVerifier.class }) - @ConditionalOnMissingBean - public ContractVerifierMessaging contractVerifierMessaging(MessageVerifier exchange, - RabbitTemplate rabbitTemplate) { - return new ContractVerifierHelper(exchange, rabbitTemplate.getMessageConverter()); - } - - @Configuration - @ConditionalOnProperty(name = "stubrunner.amqp.enabled", havingValue = "true") - static class ContractVerifierAmqpSpyAutoConfiguration { - - @SpyBean - private RabbitTemplate rabbitTemplate; - - @Autowired(required = false) - private RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry; - - @Autowired(required = false) - private List simpleMessageListenerContainers = emptyList(); - - @Autowired(required = false) - private List bindings = emptyList(); - - @Autowired - private RabbitProperties rabbitProperties; - - @Bean - @ConditionalOnMissingBean - public MessageVerifier contractVerifierMessageExchange() { - return new SpringAmqpStubMessages(this.rabbitTemplate, - new MessageListenerAccessor(this.rabbitListenerEndpointRegistry, - this.simpleMessageListenerContainers, this.bindings), - this.rabbitProperties); - } - - } - -} - -class ContractVerifierHelper extends ContractVerifierMessaging { - - private final MessageConverter messageConverter; - - ContractVerifierHelper(MessageVerifier exchange, MessageConverter messageConverter) { - super(exchange); - this.messageConverter = messageConverter; - } - - @Override - protected ContractVerifierMessage convert(Message message) { - MessagingMessageConverter messageConverter = new MessagingMessageConverter(this.messageConverter, - new SimpleAmqpHeaderMapper()); - org.springframework.messaging.Message messagingMessage; - messagingMessage = (org.springframework.messaging.Message) messageConverter.fromMessage(message); - return new ContractVerifierMessage(messagingMessage.getPayload(), messagingMessage.getHeaders()); - } - -} 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 deleted file mode 100644 index 7c0e036a5e..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessor.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.Binding.DestinationType; -import org.springframework.amqp.rabbit.listener.MessageListenerContainer; -import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; -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 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. - * - * @author Mathias Düsterhöft - * @since 1.0.2 - */ -class MessageListenerAccessor { - - private final RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry; - - private final List simpleMessageListenerContainers; - - private final List bindings; - - MessageListenerAccessor(RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry, - List simpleMessageListenerContainers, List bindings) { - this.rabbitListenerEndpointRegistry = rabbitListenerEndpointRegistry; - this.simpleMessageListenerContainers = simpleMessageListenerContainers; - this.bindings = bindings; - } - - List getListenerContainersForDestination(String destination, String routingKey) { - List listenerContainers = collectListenerContainers(); - // we interpret the destination as exchange name and collect all the queues bound - // to this exchange - Set queueNames = collectQueuesBoundToDestination(destination, routingKey); - return getListenersByBoundQueues(listenerContainers, queueNames); - } - - private List getListenersByBoundQueues( - List listenerContainers, Set queueNames) { - List matchingContainers = new ArrayList<>(); - for (SimpleMessageListenerContainer listenerContainer : listenerContainers) { - if (listenerContainer.getQueueNames() != null) { - for (String queueName : listenerContainer.getQueueNames()) { - if (queueNames.contains(queueName)) { - matchingContainers.add(listenerContainer); - break; - } - } - } - } - return matchingContainers; - } - - private Set collectQueuesBoundToDestination(String destination, String routingKey) { - Set queueNames = new HashSet<>(); - for (Binding binding : this.bindings) { - if (destination.equals(binding.getExchange()) - && (routingKey == null || routingKey.equals(binding.getRoutingKey())) - && DestinationType.QUEUE.equals(binding.getDestinationType())) { - queueNames.add(binding.getDestination()); - } - } - return queueNames; - } - - private List collectListenerContainers() { - List listenerContainers = new ArrayList<>(); - if (this.simpleMessageListenerContainers != null) { - listenerContainers.addAll(this.simpleMessageListenerContainers); - } - if (this.rabbitListenerEndpointRegistry != null) { - for (MessageListenerContainer listenerContainer : this.rabbitListenerEndpointRegistry - .getListenerContainers()) { - if (listenerContainer instanceof SimpleMessageListenerContainer) { - listenerContainers.add((SimpleMessageListenerContainer) listenerContainer); - } - } - } - return listenerContainers; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/RabbitMockConnectionFactoryAutoConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/RabbitMockConnectionFactoryAutoConfiguration.java deleted file mode 100644 index 61d0a517b4..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/RabbitMockConnectionFactoryAutoConfiguration.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.lang.NonNull; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Spring rabbit test utility that provides a mock ConnectionFactory to avoid having to - * connect against a running broker. - * - * Set verifier.amqp.mockConnection=true to enable the mocked ConnectionFactory - * - * @author Mathias Düsterhöft - * @since 1.0.2 - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnBean(ContractVerifierAmqpAutoConfiguration.class) -@ConditionalOnProperty(value = "stubrunner.amqp.mockConnection", havingValue = "true", matchIfMissing = true) -public class RabbitMockConnectionFactoryAutoConfiguration { - - @Bean - public ConnectionFactory connectionFactory() { - final Connection mockConnection = mock(Connection.class); - final AMQP.Queue.DeclareOk mockDeclareOk = mock(AMQP.Queue.DeclareOk.class); - com.rabbitmq.client.ConnectionFactory mockConnectionFactory = mock(com.rabbitmq.client.ConnectionFactory.class, - new Answer() { - @Override - public Object answer(InvocationOnMock invocationOnMock) throws Throwable { - // hack for keeping backward compatibility with #303 - if ("newConnection".equals(invocationOnMock.getMethod().getName())) { - return mockConnection; - } - return Mockito.RETURNS_DEFAULTS.answer(invocationOnMock); - } - }); - try { - final Channel mockChannel = mock(Channel.class, invocationOnMock -> { - if ("queueDeclare".equals(invocationOnMock.getMethod().getName())) { - return mockDeclareOk; - } - return Mockito.RETURNS_DEFAULTS.answer(invocationOnMock); - }); - when(mockConnection.isOpen()).thenReturn(true); - when(mockConnection.createChannel()).thenReturn(mockChannel); - when(mockConnection.createChannel(Mockito.anyInt())).thenReturn(mockChannel); - } - catch (Exception e) { - throw new RuntimeException(e); - } - return new AbstractConnectionFactory(mockConnectionFactory) { - @Override - public @NonNull org.springframework.amqp.rabbit.connection.Connection createConnection() { - return super.createBareConnection(); - } - }; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessages.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessages.java deleted file mode 100644 index fc57ec19fb..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessages.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import com.rabbitmq.client.Channel; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.core.MessagePropertiesBuilder; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; -import org.springframework.amqp.support.AmqpHeaders; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.amqp.RabbitProperties; -import org.springframework.cloud.contract.verifier.converter.YamlContract; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessageMetadata; -import org.springframework.cloud.contract.verifier.util.MetadataUtil; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.Assert; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mockingDetails; -import static org.mockito.Mockito.verify; -import static org.springframework.amqp.support.converter.DefaultClassMapper.DEFAULT_CLASSID_FIELD_NAME; - -/** - * {@link MessageVerifier} implementation to integrate with plain - * spring-amqp/spring-rabbit. It is meant to be used without interacting with a running - * bus. - * - * It relies on the RabbitTemplate to be a spy to be able to capture send messages. - * - * Messages are not sent to the bus - but are handed over to a - * {@link SimpleMessageListenerContainer} which allows us to test the full deserialization - * and listener invocation. - * - * @author Mathias Düsterhöft - * @since 1.0.2 - */ -public class SpringAmqpStubMessages implements MessageVerifier { - - private static final Log log = LogFactory.getLog(SpringAmqpStubMessages.class); - - private final RabbitTemplate rabbitTemplate; - - private final MessageListenerAccessor messageListenerAccessor; - - private RabbitProperties rabbitProperties; - - @Autowired - public SpringAmqpStubMessages(RabbitTemplate rabbitTemplate, MessageListenerAccessor messageListenerAccessor, - RabbitProperties rabbitProperties) { - Assert.notNull(rabbitTemplate, "RabbitTemplate must be set"); - Assert.isTrue(mockingDetails(rabbitTemplate).isSpy() || mockingDetails(rabbitTemplate).isMock(), - "StubRunner AMQP will work only if RabbiTemplate is a spy"); - this.rabbitTemplate = rabbitTemplate; - this.messageListenerAccessor = messageListenerAccessor; - this.rabbitProperties = rabbitProperties; - } - - @Override - public void send(T payload, Map messageHeaders, String destination, YamlContract contract) { - final MessageHeaders headers = new MessageHeaders(messageHeaders); - Message message = org.springframework.amqp.core.MessageBuilder.withBody(((String) payload).getBytes()) - .andProperties(MessagePropertiesBuilder.newInstance() - .setContentType(header(headers, MessageHeaders.CONTENT_TYPE)).copyHeaders(headers).build()) - .build(); - if (headers.containsKey(DEFAULT_CLASSID_FIELD_NAME)) { - message.getMessageProperties().setHeader(DEFAULT_CLASSID_FIELD_NAME, - headers.get(DEFAULT_CLASSID_FIELD_NAME)); - } - if (headers.containsKey(AmqpHeaders.RECEIVED_ROUTING_KEY)) { - message.getMessageProperties().setReceivedRoutingKey(header(headers, AmqpHeaders.RECEIVED_ROUTING_KEY)); - } - send(message, destination, contract); - } - - private String header(MessageHeaders headers, String headerName) { - Object value = headers.get(headerName); - - if (value == null) { - return ""; - } - else if (value instanceof String) { - return (String) value; - } - else if (value instanceof Iterable) { - Iterable values = ((Iterable) value); - return values.iterator().hasNext() ? (String) values.iterator().next() : ""; - } - return value.toString(); - } - - public void mergeMessagePropertiesFromMetadata(YamlContract contract, Message message) { - if (contract != null && contract.metadata.containsKey(AmqpMetadata.METADATA_KEY)) { - AmqpMetadata amqpMetadata = AmqpMetadata.fromMetadata(contract.metadata); - ContractVerifierMessageMetadata messageMetadata = ContractVerifierMessageMetadata - .fromMetadata(contract.metadata); - boolean isInput = isInputMessage(messageMetadata); - MessageProperties fromMetadata = isInput ? amqpMetadata.getInput().getMessageProperties() - : amqpMetadata.getOutputMessage().getMessageProperties(); - MetadataUtil.merge(message.getMessageProperties(), fromMetadata); - } - } - - public boolean isInputMessage(ContractVerifierMessageMetadata messageMetadata) { - return messageMetadata.getMessageType() == ContractVerifierMessageMetadata.MessageType.INPUT; - } - - @Override - public void send(Message message, String destination, YamlContract contract) { - mergeMessagePropertiesFromMetadata(contract, message); - final String routingKey = message.getMessageProperties().getReceivedRoutingKey(); - List listenerContainers = this.messageListenerAccessor - .getListenerContainersForDestination(destination, routingKey); - if (listenerContainers.isEmpty()) { - throw new IllegalStateException("no listeners found for destination " + destination); - } - for (SimpleMessageListenerContainer listenerContainer : listenerContainers) { - Object messageListener = listenerContainer.getMessageListener(); - if (isChannelAwareListener(listenerContainer, messageListener)) { - try { - ((ChannelAwareMessageListener) messageListener).onMessage(message, - createChannel(listenerContainer, transactionalChannel())); - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - else { - ((MessageListener) messageListener).onMessage(message); - } - } - } - - Channel createChannel(SimpleMessageListenerContainer listenerContainer, boolean transactional) { - return listenerContainer.getConnectionFactory().createConnection().createChannel(transactional); - } - - boolean isChannelAwareListener(SimpleMessageListenerContainer listenerContainer, Object messageListener) { - return messageListener instanceof ChannelAwareMessageListener - && listenerContainer.getConnectionFactory() != null; - } - - private boolean transactionalChannel() { - if (this.rabbitProperties == null) { - // backward compatibility - return true; - } - return this.rabbitProperties.getPublisherConfirmType() == null - || this.rabbitProperties.getPublisherConfirmType() == CachingConnectionFactory.ConfirmType.NONE; - } - - @Override - public Message receive(String destination, long timeout, TimeUnit timeUnit, YamlContract contract) { - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); - ArgumentCaptor routingKeyCaptor = ArgumentCaptor.forClass(String.class); - verify(this.rabbitTemplate, atLeastOnce()).send(eq(destination), routingKeyCaptor.capture(), - messageCaptor.capture(), ArgumentMatchers.any()); - if (messageCaptor.getAllValues().isEmpty()) { - log.info("no messages found on destination [" + destination + "]"); - return null; - } - else if (messageCaptor.getAllValues().size() > 1) { - log.info("multiple messages found on destination [" + destination + "] returning last one"); - return messageCaptor.getValue(); - } - Message message = messageCaptor.getValue(); - if (message == null) { - log.info("no messages found on destination [" + destination + "]"); - return null; - } - if (!routingKeyCaptor.getValue().isEmpty()) { - log.info("routing key passed [" + routingKeyCaptor.getValue() + "]"); - message.getMessageProperties().setReceivedRoutingKey(routingKeyCaptor.getValue()); - } - return message; - } - - @Override - public Message receive(String destination, YamlContract contract) { - return receive(destination, 5, TimeUnit.SECONDS, contract); - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/camel/ContractVerifierCamelConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/camel/ContractVerifierCamelConfiguration.java index 005c3b62f3..041872911d 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/camel/ContractVerifierCamelConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/camel/ContractVerifierCamelConfiguration.java @@ -27,6 +27,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; import org.springframework.cloud.contract.verifier.messaging.jms.ContractVerifierJmsConfiguration; @@ -54,16 +56,16 @@ public class ContractVerifierCamelConfiguration { @Bean @ConditionalOnMissingBean - public ContractVerifierMessaging contractVerifierMessaging(MessageVerifier exchange) { - return new ContractVerifierCamelHelper(exchange); + public ContractVerifierMessaging contractVerifierMessaging(MessageVerifierSender sender, MessageVerifierReceiver receiver) { + return new ContractVerifierCamelHelper(sender, receiver); } } class ContractVerifierCamelHelper extends ContractVerifierMessaging { - ContractVerifierCamelHelper(MessageVerifier exchange) { - super(exchange); + ContractVerifierCamelHelper(MessageVerifierSender sender, MessageVerifierReceiver receiver) { + super(sender, receiver); } @Override diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/integration/ContractVerifierIntegrationConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/integration/ContractVerifierIntegrationConfiguration.java index a439ec94ae..87c1aca775 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/integration/ContractVerifierIntegrationConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/integration/ContractVerifierIntegrationConfiguration.java @@ -21,6 +21,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration; @@ -47,16 +49,16 @@ public class ContractVerifierIntegrationConfiguration { @Bean @ConditionalOnMissingBean - public ContractVerifierMessaging> contractVerifierMessaging(MessageVerifier> exchange) { - return new ContractVerifierHelper(exchange); + public ContractVerifierMessaging> contractVerifierMessaging(MessageVerifierSender> sender, MessageVerifierReceiver> receiver) { + return new ContractVerifierHelper(sender, receiver); } } class ContractVerifierHelper extends ContractVerifierMessaging> { - ContractVerifierHelper(MessageVerifier> exchange) { - super(exchange); + ContractVerifierHelper(MessageVerifierSender> sender, MessageVerifierReceiver> receiver) { + super(sender, receiver); } @Override diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierMessaging.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierMessaging.java index 987cc27e77..f7737e4923 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierMessaging.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/internal/ContractVerifierMessaging.java @@ -25,6 +25,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.contract.verifier.converter.YamlContract; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; /** * Wrapper around messaging. Abstracts all message related operations like sending, @@ -38,12 +40,18 @@ public class ContractVerifierMessaging { private static final Log log = LogFactory.getLog(ContractVerifierMessaging.class); - private final MessageVerifier exchange; + private final MessageVerifierSender sender; - public ContractVerifierMessaging(MessageVerifier exchange) { - this.exchange = exchange; - if (exchange != null) { - log.info("The message verifier implementation is of type [" + exchange.getClass() + "]"); + private final MessageVerifierReceiver receiver; + + public ContractVerifierMessaging(MessageVerifierSender sender, MessageVerifierReceiver receiver) { + this.sender = sender; + this.receiver = receiver; + if (sender != null) { + log.info("The message verifier sender implementation is of type [" + sender.getClass() + "]"); + } + if (receiver != null) { + log.info("The message verifier receiver implementation is of type [" + receiver.getClass() + "]"); } } @@ -51,7 +59,7 @@ public class ContractVerifierMessaging { if (contract != null) { setMessageType(contract, ContractVerifierMessageMetadata.MessageType.INPUT); } - this.exchange.send(message.getPayload(), message.getHeaders(), destination, contract); + this.sender.send(message.getPayload(), message.getHeaders(), destination, contract); } public void send(ContractVerifierMessage message, String destination) { @@ -62,7 +70,7 @@ public class ContractVerifierMessaging { if (contract != null) { setMessageType(contract, ContractVerifierMessageMetadata.MessageType.OUTPUT); } - return convert(this.exchange.receive(destination, contract)); + return convert(this.receiver.receive(destination, contract)); } private void setMessageType(YamlContract contract, ContractVerifierMessageMetadata.MessageType output) { diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/jms/ContractVerifierJmsConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/jms/ContractVerifierJmsConfiguration.java index 298fdbb10d..199921df02 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/jms/ContractVerifierJmsConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/jms/ContractVerifierJmsConfiguration.java @@ -34,6 +34,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; @@ -60,8 +62,8 @@ public class ContractVerifierJmsConfiguration { @Bean @ConditionalOnMissingBean - ContractVerifierMessaging contractVerifierJmsMessaging(MessageVerifier exchange) { - return new ContractVerifierJmsHelper(exchange); + ContractVerifierMessaging contractVerifierJmsMessaging(MessageVerifierSender sender, MessageVerifierReceiver receiver) { + return new ContractVerifierJmsHelper(sender, receiver); } } @@ -70,8 +72,8 @@ class ContractVerifierJmsHelper extends ContractVerifierMessaging { private static final Log log = LogFactory.getLog(ContractVerifierJmsHelper.class); - ContractVerifierJmsHelper(MessageVerifier exchange) { - super(exchange); + ContractVerifierJmsHelper(MessageVerifierSender sender, MessageVerifierReceiver receiver) { + super(sender, receiver); } @Override diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfiguration.java deleted file mode 100644 index 3ad1e9ab6a..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfiguration.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka; - -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; -import org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration; -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; -import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.test.EmbeddedKafkaBroker; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; - -/** - * @author Marcin Grzejszczak - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ KafkaTemplate.class, EmbeddedKafkaBroker.class }) -@ConditionalOnProperty(name = "stubrunner.kafka.enabled", havingValue = "true", matchIfMissing = true) -@AutoConfigureBefore({ ContractVerifierIntegrationConfiguration.class, NoOpContractVerifierAutoConfiguration.class }) -@ConditionalOnBean(EmbeddedKafkaBroker.class) -public class ContractVerifierKafkaConfiguration { - - private static final Log log = LogFactory.getLog(ContractVerifierKafkaConfiguration.class); - - @Bean - @ConditionalOnMissingBean - MessageVerifier> contractVerifierKafkaMessageExchange(Supplier kafkaTemplate, - EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties, KafkaStubMessagesInitializer initializer) { - return new KafkaStubMessages(kafkaTemplate.get(), broker, kafkaProperties, initializer); - } - - @Bean - @ConditionalOnMissingBean - Supplier contractVerifierKafkaTemplateSupplier(KafkaTemplate kafkaTemplate) { - return () -> kafkaTemplate; - } - - @Bean - @ConditionalOnMissingBean - KafkaStubMessagesInitializer contractVerifierKafkaStubMessagesInitializer() { - if (log.isDebugEnabled()) { - log.debug("Registering contract verifier stub messages initializer"); - } - return new ContractVerifierKafkaStubMessagesInitializer(); - } - - @Bean - @ConditionalOnMissingBean - ContractVerifierMessaging> contractVerifierKafkaMessaging(MessageVerifier> exchange) { - return new ContractVerifierKafkaHelper(exchange); - } - -} - -class ContractVerifierKafkaHelper extends ContractVerifierMessaging> { - - ContractVerifierKafkaHelper(MessageVerifier> exchange) { - super(exchange); - } - - @Override - protected ContractVerifierMessage convert(Message message) { - return new ContractVerifierMessage(message.getPayload(), convertHeaders(message.getHeaders())); - } - - private MessageHeaders convertHeaders(Map headers) { - final Map headersMap = new HashMap<>(); - if (headers != null) { - headers.forEach((k, v) -> headersMap.put(k, maybeConvertValue(v))); - } - return new MessageHeaders(headersMap); - } - - private Object maybeConvertValue(Object value) { - if (value == null) { - return value; - } - if (!(value instanceof byte[])) { - return value; - } - return new String((byte[]) value, StandardCharsets.UTF_8); - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java deleted file mode 100644 index 8c9f2988ee..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.kafka.clients.consumer.Consumer; -import org.apache.kafka.clients.consumer.ConsumerConfig; - -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; -import org.springframework.kafka.core.DefaultKafkaConsumerFactory; -import org.springframework.kafka.test.EmbeddedKafkaBroker; -import org.springframework.kafka.test.utils.KafkaTestUtils; - -class ContractVerifierKafkaStubMessagesInitializer implements KafkaStubMessagesInitializer { - - private static final Log log = LogFactory.getLog(ContractVerifierKafkaStubMessagesInitializer.class); - - @Override - public Map initialize(EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties) { - Map map = new HashMap<>(); - for (String topic : broker.getTopics()) { - map.put(topic, prepareListener(broker, topic, kafkaProperties)); - } - return map; - } - - private Consumer prepareListener(EmbeddedKafkaBroker broker, String destination, KafkaProperties kafkaProperties) { - Map consumerProperties = KafkaTestUtils - .consumerProps(kafkaProperties.getConsumer().getGroupId(), "false", broker); - - // Respect custom key/value deserializers and any additional props under - // 'spring.kafka.consumer.properties' - consumerProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - kafkaProperties.getConsumer().getKeyDeserializer()); - consumerProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - kafkaProperties.getConsumer().getValueDeserializer()); - consumerProperties.putAll(kafkaProperties.getConsumer().getProperties()); - - DefaultKafkaConsumerFactory consumerFactory = new DefaultKafkaConsumerFactory<>( - consumerProperties); - Consumer consumer = consumerFactory.createConsumer(); - broker.consumeFromAnEmbeddedTopic(consumer, destination); - if (log.isDebugEnabled()) { - log.debug("Prepared consumer for destination [" + destination + "]"); - } - return consumer; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java deleted file mode 100644 index d5b57da11f..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.kafka.clients.consumer.Consumer; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.common.header.Header; -import org.apache.kafka.common.header.Headers; - -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; -import org.springframework.cloud.contract.verifier.converter.YamlContract; -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.support.KafkaHeaders; -import org.springframework.kafka.support.converter.MessagingMessageConverter; -import org.springframework.kafka.test.EmbeddedKafkaBroker; -import org.springframework.kafka.test.utils.KafkaTestUtils; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.MessageBuilder; - -class KafkaStubMessages implements MessageVerifier> { - - private static final Log log = LogFactory.getLog(KafkaStubMessages.class); - - final KafkaTemplate kafkaTemplate; - - private final Receiver receiver; - - KafkaStubMessages(KafkaTemplate kafkaTemplate, EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties, - KafkaStubMessagesInitializer initializer) { - this.kafkaTemplate = kafkaTemplate; - Map topicToConsumer = initializer.initialize(broker, kafkaProperties); - this.receiver = new Receiver(topicToConsumer); - } - - @Override - public void send(Message message, String destination, YamlContract contract) { - String defaultTopic = this.kafkaTemplate.getDefaultTopic(); - try { - this.kafkaTemplate.setDefaultTopic(destination); - if (log.isDebugEnabled()) { - log.debug("Will send a message [" + message + "] to destination [" + destination + "]"); - } - this.kafkaTemplate.send(message).get(5, TimeUnit.SECONDS); - this.kafkaTemplate.flush(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - finally { - this.kafkaTemplate.setDefaultTopic(defaultTopic); - } - } - - @Override - public Message receive(String destination, long timeout, TimeUnit timeUnit, YamlContract contract) { - return this.receiver.receive(destination, timeout, timeUnit, contract); - } - - @Override - public Message receive(String destination, YamlContract contract) { - return receive(destination, 5, TimeUnit.SECONDS, contract); - } - - @Override - public void send(Object payload, Map headers, String destination, YamlContract contract) { - Message message = MessageBuilder.createMessage(payload, new MessageHeaders(headers)); - send(message, destination, contract); - } - -} - -class Receiver { - - private static final Log log = LogFactory.getLog(Receiver.class); - - private final MessagingMessageConverter messagingMessageConverter = new MessagingMessageConverter(); - - private final Map consumers; - - Receiver(Map consumers) { - this.consumers = consumers; - } - - Message receive(String topic, long timeout, TimeUnit timeUnit, YamlContract contract) { - Consumer consumer = this.consumers.get(topic); - if (consumer == null) { - throw new IllegalStateException("No consumer set up for topic [" + topic + "]"); - } - ConsumerRecord record = KafkaTestUtils.getSingleRecord(consumer, topic, Duration.ofMillis(timeout)); - if (log.isDebugEnabled()) { - log.debug("Got a single record for destination [" + topic + "]"); - } - return toMessage(consumer, record); - } - - Message toMessage(Consumer consumer, ConsumerRecord record) { - Map headersMap = toMap(record.headers()); - - // Leverage spring-kafka to add the headers - messagingMessageConverter.commonHeaders(null, consumer, headersMap, record.key(), record.topic(), - record.partition(), record.offset(), - record.timestampType() != null ? record.timestampType().name() : null, record.timestamp()); - // commonHeaders() maps the record key under 'kafka_receivedMessageKey' - put - // under 'kafka_messageKey' as well to satisfy both client/server usages as there - // is not currently a way to set a header name based on client/server - headersMap.put(KafkaHeaders.KEY, record.key()); - - // TODO explore using MessagingMessageConverter to do all of the conversion - // (ideally delete this entire method) - Object textPayload = record.value(); - // sometimes it's a message sometimes just payload - if (textPayload instanceof String && ((String) textPayload).contains("payload") - && ((String) textPayload).contains("headers")) { - try { - Object object = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse((String) textPayload); - JSONObject jo = (JSONObject) object; - String payload = (String) jo.get("payload"); - JSONObject headersInJson = (JSONObject) jo.get("headers"); - headersMap.putAll(headersInJson); - return MessageBuilder.createMessage(unquoted(payload), new MessageHeaders(headersMap)); - } - catch (ParseException ex) { - throw new IllegalStateException(ex); - } - } - return MessageBuilder.createMessage(unquoted(textPayload), new MessageHeaders(headersMap)); - } - - private Map toMap(Headers headers) { - Map map = new HashMap<>(); - for (Header header : headers) { - map.put(header.key(), header.value()); - } - return map; - } - - private Object unquoted(Object value) { - String textPayload = value instanceof byte[] ? new String((byte[]) value) : value.toString(); - if (textPayload.startsWith("\"") && textPayload.endsWith("\"")) { - return textPayload.substring(1, textPayload.length() - 1).replace("\\\"", "\""); - } - return textPayload; - } - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessagesInitializer.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessagesInitializer.java deleted file mode 100644 index 1cbe67439b..0000000000 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessagesInitializer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka; - -import java.util.Map; - -import org.apache.kafka.clients.consumer.Consumer; - -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; -import org.springframework.kafka.test.EmbeddedKafkaBroker; - -/** - * Logic used to initialize {@link KafkaStubMessages}. This interface might have a - * different implementation for the producer side and for the consumer side. That's - * because you can't poll for a single message by different consumers. - * - * @author Marcin Grzejszczak - * @since 2.2.0 - */ -public interface KafkaStubMessagesInitializer { - - /** - * @param broker - embedded Kafka broker - * @param kafkaProperties - kafka properties - * @return topic to initialized consumer mapping - */ - Map initialize(EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties); - -} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/noop/NoOpContractVerifierAutoConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/noop/NoOpContractVerifierAutoConfiguration.java index cac30e2962..04b8d27e4d 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/noop/NoOpContractVerifierAutoConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/noop/NoOpContractVerifierAutoConfiguration.java @@ -22,6 +22,8 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper; import org.springframework.context.annotation.Bean; @@ -39,14 +41,14 @@ public class NoOpContractVerifierAutoConfiguration { @Bean @ConditionalOnMissingBean(MessageVerifier.class) - public MessageVerifier contractVerifierMessageExchange() { + public NoOpStubMessages contractVerifierMessageExchange() { return new NoOpStubMessages(); } @Bean @ConditionalOnMissingBean(ContractVerifierMessaging.class) - public ContractVerifierMessaging contractVerifierMessaging(MessageVerifier exchange) { - return new ContractVerifierMessaging<>(exchange); + public ContractVerifierMessaging contractVerifierMessaging(NoOpStubMessages messages) { + return new ContractVerifierMessaging<>(messages, messages); } @Bean diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/ContractVerifierStreamAutoConfiguration.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/ContractVerifierStreamAutoConfiguration.java index 8e6c2d7816..88d6a46fa1 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/ContractVerifierStreamAutoConfiguration.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/ContractVerifierStreamAutoConfiguration.java @@ -22,6 +22,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierReceiver; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifierSender; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; import org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration; @@ -44,8 +46,8 @@ public class ContractVerifierStreamAutoConfiguration { @Bean @ConditionalOnMissingBean - public ContractVerifierMessaging contractVerifierMessagingConverter(MessageVerifier> exchange) { - return new ContractVerifierHelper(exchange); + public ContractVerifierMessaging contractVerifierMessagingConverter(MessageVerifierSender> sender, MessageVerifierReceiver> receiver) { + return new ContractVerifierHelper(sender, receiver); } @Configuration(proxyBeanMethods = false) @@ -79,8 +81,8 @@ public class ContractVerifierStreamAutoConfiguration { class ContractVerifierHelper extends ContractVerifierMessaging> { - ContractVerifierHelper(MessageVerifier> exchange) { - super(exchange); + ContractVerifierHelper(MessageVerifierSender> sender, MessageVerifierReceiver> receiver) { + super(sender, receiver); } @Override diff --git a/spring-cloud-contract-verifier/src/main/resources/META-INF/spring/org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier.imports b/spring-cloud-contract-verifier/src/main/resources/META-INF/spring/org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier.imports index 750a6c52ef..d095ad09bd 100644 --- a/spring-cloud-contract-verifier/src/main/resources/META-INF/spring/org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier.imports +++ b/spring-cloud-contract-verifier/src/main/resources/META-INF/spring/org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier.imports @@ -1,8 +1,5 @@ org.springframework.cloud.contract.verifier.messaging.stream.ContractVerifierStreamAutoConfiguration org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration -org.springframework.cloud.contract.verifier.messaging.amqp.ContractVerifierAmqpAutoConfiguration -org.springframework.cloud.contract.verifier.messaging.amqp.RabbitMockConnectionFactoryAutoConfiguration org.springframework.cloud.contract.verifier.messaging.camel.ContractVerifierCamelConfiguration org.springframework.cloud.contract.verifier.messaging.jms.ContractVerifierJmsConfiguration -org.springframework.cloud.contract.verifier.messaging.kafka.ContractVerifierKafkaConfiguration org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierHelperSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierHelperSpec.groovy deleted file mode 100644 index dfbfd52cdc..0000000000 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierHelperSpec.groovy +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp - -import spock.lang.Specification - -import org.springframework.amqp.core.Message -import org.springframework.amqp.core.MessageBuilder -import org.springframework.amqp.core.MessagePropertiesBuilder -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage - -import static org.springframework.amqp.core.MessageProperties.CONTENT_TYPE_JSON - -/** - * @author Mathias Düsterhöft - */ -class ContractVerifierHelperSpec extends Specification { - - def "should convert message"() { - given: - String payload = '''{"name":"some"}''' - Message message = MessageBuilder - .withBody(payload.bytes) - .andProperties(MessagePropertiesBuilder.newInstance() - .setHeader("my-header", "some") - .setContentType(CONTENT_TYPE_JSON) - .build()).build() - ContractVerifierHelper contractVerifierHelper = new ContractVerifierHelper(null, new Jackson2JsonMessageConverter()) - when: - ContractVerifierMessage contractVerifierMessage = contractVerifierHelper.convert(message) - then: - ((Map) contractVerifierMessage.payload).containsKey("name") - contractVerifierMessage.headers.containsKey("contentType") - contractVerifierMessage.headers.containsKey("my-header") - } -} diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessorSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessorSpec.groovy deleted file mode 100644 index b7eeab5ccb..0000000000 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/MessageListenerAccessorSpec.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp - -import spock.lang.Specification - -import org.springframework.amqp.core.Binding -import org.springframework.amqp.core.BindingBuilder -import org.springframework.amqp.core.DirectExchange -import org.springframework.amqp.core.Queue -import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer - -class MessageListenerAccessorSpec extends Specification { - - String queueName = "test.queue" - String exchange = "test-exchange" - SimpleMessageListenerContainer listenerContainer - Binding binding - - def "should get single simple listener container"() { - given: - givenSimpleMessageListenerContainer() - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [this.listenerContainer], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination(this.exchange, null) - then: - listenerContainersForDestination.size() == 1 - listenerContainersForDestination.get(0) == this.listenerContainer - } - - def "should get single simple listener container for matching routing key"() { - given: - givenSimpleMessageListenerContainer() - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [this.listenerContainer], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination(this.exchange, '#') - then: - listenerContainersForDestination.size() == 1 - listenerContainersForDestination.get(0) == this.listenerContainer - } - - def "should get empty simple listener container for non matching routing key"() { - given: - givenSimpleMessageListenerContainer() - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [this.listenerContainer], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination(this.exchange, 'not matching') - then: - listenerContainersForDestination.isEmpty() - } - - def "should get empty listener container list for unknown destination"() { - given: - givenSimpleMessageListenerContainer() - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [this.listenerContainer], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination("some-exchange", null) - then: - listenerContainersForDestination.isEmpty() - } - - def "should get empty listener container list for queue with no matching listener"() { - given: - givenSimpleMessageListenerContainer() - this.binding = BindingBuilder.bind(new Queue("some.queue")).to(new DirectExchange(this.exchange)).with("#") - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [this.listenerContainer], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination(this.exchange, null) - then: - listenerContainersForDestination.isEmpty() - } - - def "should get single simple listener container from RabbitListenerEndpointRegistry"() { - given: - givenSimpleMessageListenerContainer() - RabbitListenerEndpointRegistry rabbitListenerEndpointRegistryMock = Mock(RabbitListenerEndpointRegistry) - rabbitListenerEndpointRegistryMock.getListenerContainers() >> [this.listenerContainer] - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(rabbitListenerEndpointRegistryMock, [], [this.binding]) - when: - List listenerContainersForDestination = messageListenerAccessor.getListenerContainersForDestination(this.exchange, null) - then: - listenerContainersForDestination.size() == 1 - listenerContainersForDestination.get(0) == this.listenerContainer - } - - def givenSimpleMessageListenerContainer() { - this.listenerContainer = new SimpleMessageListenerContainer() - this.listenerContainer.setQueueNames(this.queueName) - this.binding = BindingBuilder.bind(new Queue(this.queueName)).to(new DirectExchange(this.exchange)).with("#") - } -} diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesSpec.groovy deleted file mode 100644 index 121b084368..0000000000 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesSpec.groovy +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp - -import com.rabbitmq.client.Channel -import org.mockito.exceptions.verification.WantedButNotInvoked -import spock.lang.Specification - -import org.springframework.amqp.core.Binding -import org.springframework.amqp.core.BindingBuilder -import org.springframework.amqp.core.DirectExchange -import org.springframework.amqp.core.Message -import org.springframework.amqp.core.MessageProperties -import org.springframework.amqp.core.Queue -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory -import org.springframework.amqp.rabbit.core.RabbitTemplate -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer -import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter -import org.springframework.boot.autoconfigure.amqp.RabbitProperties -import org.springframework.cloud.contract.verifier.converter.YamlContract - -import static org.mockito.Mockito.mock -import static org.springframework.amqp.core.MessageProperties.CONTENT_TYPE_JSON -import static org.springframework.amqp.support.converter.DefaultClassMapper.DEFAULT_CLASSID_FIELD_NAME - -/** - * @author Mathias Düsterhöft - */ -class SpringAmqpStubMessagesSpec extends Specification { - - RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class) - SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer() - MessageListenerAdapter messageListenerAdapter = Mock(MessageListenerAdapter.class) - RabbitProperties rabbitProperties = new RabbitProperties() - Message message = Mock(Message.class) - - String queueName = "test.queue" - String exchange = "test-exchange" - String payload = '''{"name":"some"}''' - String routingKey = "resource.created" - - def "should send amqp message with type id"() { - given: - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) - - when: - messageVerifier.send(payload, - [(DEFAULT_CLASSID_FIELD_NAME): "org.example.Some", - "amqp_receivedRoutingKey" : routingKey, - "contentType" : CONTENT_TYPE_JSON], - exchange) - then: - 1 * messageListenerAdapter.onMessage({ Message msg -> - msg.getMessageProperties().getReceivedRoutingKey() == "resource.created" && - msg.getMessageProperties().getContentType() == CONTENT_TYPE_JSON && - msg.getMessageProperties().getHeaders().get(DEFAULT_CLASSID_FIELD_NAME) == "org.example.Some" - }) - } - - // for JDK 16 the merge option won't work and the metadata needs to be FULLY applied - def "should send amqp message with metadata id"() { - given: - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) - - when: - messageVerifier.send(payload, - [(DEFAULT_CLASSID_FIELD_NAME): "org.example.Some", - "amqp_receivedRoutingKey" : routingKey, - "contentType" : CONTENT_TYPE_JSON], - exchange, new YamlContract(metadata: - [ - "amqp": [ - "input": ["messageProperties": [ - correlationId: "correlationIdValue", - consumerQueue: "queue", - contentType: CONTENT_TYPE_JSON, - receivedRoutingKey: routingKey]] - ], - "verifierMessage" : [messageType: "INPUT"] - ])) - then: - 1 * messageListenerAdapter.onMessage({ Message msg -> - msg.getMessageProperties().getReceivedRoutingKey() == "resource.created" && - msg.getMessageProperties().getContentType() == CONTENT_TYPE_JSON && - msg.getMessageProperties().getHeaders().get(DEFAULT_CLASSID_FIELD_NAME) == "org.example.Some" && - msg.getMessageProperties().getCorrelationId() == "correlationIdValue" && - msg.getMessageProperties().getConsumerQueue() == "queue" - }) - } - - def "should send amqp message for non transactional channel"() { - given: - rabbitProperties.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.SIMPLE) - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - boolean createChannelCalled = false - boolean transactionalChannel = false - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) { - @Override - boolean isChannelAwareListener(SimpleMessageListenerContainer listenerContainer, Object messageListener) { - return true - } - - @Override - Channel createChannel(SimpleMessageListenerContainer listenerContainer, boolean transactional) { - createChannelCalled = true - transactionalChannel = transactional - return null - } - } - - when: - messageVerifier.send(payload, - [(DEFAULT_CLASSID_FIELD_NAME): "org.example.Some", - "amqp_receivedRoutingKey" : routingKey, - "contentType" : CONTENT_TYPE_JSON], - exchange) - then: - createChannelCalled - !transactionalChannel - } - - def "should send amqp message for transactional channel"() { - given: - rabbitProperties.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.NONE) - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - boolean createChannelCalled = false - boolean transactionalChannel = false - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) { - @Override - boolean isChannelAwareListener(SimpleMessageListenerContainer listenerContainer, Object messageListener) { - return true - } - - @Override - Channel createChannel(SimpleMessageListenerContainer listenerContainer, boolean transactional) { - createChannelCalled = true - transactionalChannel = transactional - return null - } - } - - when: - messageVerifier.send(payload, - [(DEFAULT_CLASSID_FIELD_NAME): "org.example.Some", - "amqp_receivedRoutingKey" : routingKey, - "contentType" : CONTENT_TYPE_JSON], - exchange) - then: - createChannelCalled - transactionalChannel - } - - def "should fail to receive a message if rabbit template wasn't called"() { - given: - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) - when: - messageVerifier.receive("foo") - then: - thrown(WantedButNotInvoked) - } - - def "should return null if received called and message was sent without any body"() { - given: - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) - - and: - rabbitTemplate.send("foo", "bar", null, null) - expect: - messageVerifier.receive("foo") == null - } - - def "should return match the message if received called and message was sent with a message with null payload"() { - given: - listenerContainer.setMessageListener(messageListenerAdapter) - listenerContainer.setQueueNames(queueName) - Binding binding = BindingBuilder.bind(new Queue(queueName)).to(new DirectExchange(exchange)).with(routingKey) - MessageListenerAccessor messageListenerAccessor = new MessageListenerAccessor(null, [listenerContainer], [binding]) - SpringAmqpStubMessages messageVerifier = new SpringAmqpStubMessages(rabbitTemplate, messageListenerAccessor, rabbitProperties) - message.getMessageProperties() >> new MessageProperties() - and: - rabbitTemplate.send("foo", "bar", message, null) - expect: - messageVerifier.receive("foo") is message - } -} diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaHelperSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaHelperSpec.groovy deleted file mode 100644 index 550f2fa51f..0000000000 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaHelperSpec.groovy +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka - -import spock.lang.Specification - -import org.springframework.cloud.contract.verifier.messaging.MessageVerifier -import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage -import org.springframework.cloud.contract.verifier.messaging.kafka.ContractVerifierKafkaHelper -import org.springframework.messaging.Message -import org.springframework.messaging.support.MessageBuilder - -import static org.mockito.Mockito.mock - -/** - * Unit tests for {@link ContractVerifierKafkaHelper}. - * - * @author Chris Bono - */ -class ContractVerifierKafkaHelperSpec extends Specification { - - def "should convert message with no headers"() { - given: - Message message = MessageBuilder - .withPayload("some-data") - .build() - ContractVerifierKafkaHelper contractVerifierKafkaHelper = new ContractVerifierKafkaHelper(mock(MessageVerifier.class)) - when: - ContractVerifierMessage contractVerifierMessage = contractVerifierKafkaHelper.convert(message) - then: - contractVerifierMessage.payload == "some-data" - } - - def "should convert message with basic header"() { - given: - Message message = MessageBuilder - .withPayload("some-data") - .setHeader("some-header", "5150") - .build() - ContractVerifierKafkaHelper contractVerifierKafkaHelper = new ContractVerifierKafkaHelper(mock(MessageVerifier.class)) - when: - ContractVerifierMessage contractVerifierMessage = contractVerifierKafkaHelper.convert(message) - then: - contractVerifierMessage.payload == "some-data" - contractVerifierMessage.headers.containsKey("some-header") - contractVerifierMessage.headers.get("some-header") == "5150" - } - - def "should convert message with byte[] header"() { - given: - Message message = MessageBuilder - .withPayload("some-data") - .setHeader("some-header", "5150".getBytes()) - .build() - ContractVerifierKafkaHelper contractVerifierKafkaHelper = new ContractVerifierKafkaHelper(mock(MessageVerifier.class)) - when: - ContractVerifierMessage contractVerifierMessage = contractVerifierKafkaHelper.convert(message) - then: - contractVerifierMessage.payload == "some-data" - contractVerifierMessage.headers.containsKey("some-header") - new String(contractVerifierMessage.headers.get("some-header")) == "5150" - } -} diff --git a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfigurationTests.java b/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfigurationTests.java deleted file mode 100644 index 6279bfece4..0000000000 --- a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/ContractVerifierAmqpAutoConfigurationTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import org.junit.Test; - -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.mock.mockito.MockitoPostProcessor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -/** - * @author Tim Ysewyn - * @author Henning Garus - */ -public class ContractVerifierAmqpAutoConfigurationTests { - - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, - ContractVerifierAmqpAutoConfiguration.class, RabbitMockConnectionFactoryAutoConfiguration.class)) - // register MockitoPostProcessor manually to perform the SpyBean injection for - // ContractVerifierAmqpAutoConfiguration. This is normally done automatically - // during test setup - .withInitializer(context -> MockitoPostProcessor.register((BeanDefinitionRegistry) context)); - - @Test - public void shouldNotCreateBeansByDefault() { - this.contextRunner.run((context) -> { - assertThat(context.getBeansOfType(SpringAmqpStubMessages.class)).hasSize(0); - assertThat(context.getBeansOfType(ContractVerifierHelper.class)).hasSize(0); - }); - } - - @Test - public void shouldNotCreateBeansWhenDisabled() { - this.contextRunner.withPropertyValues("stubrunner.amqp.enabled=false").run((context) -> { - assertThat(context.getBeansOfType(SpringAmqpStubMessages.class)).hasSize(0); - assertThat(context.getBeansOfType(ContractVerifierHelper.class)).hasSize(0); - }); - } - - @Test - public void shouldCreateBeansWhenExplicitlyEnabled() { - this.contextRunner.withPropertyValues("stubrunner.amqp.enabled=true").run((context) -> { - assertThat(context.getBeansOfType(SpringAmqpStubMessages.class)).hasSize(1); - assertThat(context.getBeansOfType(ContractVerifierHelper.class)).hasSize(1); - }); - } - - @Test - public void shouldNotThrowOnSendWhenRabbitTemplateIsTransacted() { - this.contextRunner.withPropertyValues("stubrunner.amqp.enabled=true").run(context -> assertThatCode(() -> { - RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class); - rabbitTemplate.setChannelTransacted(true); - rabbitTemplate.convertAndSend("A Message"); - }).doesNotThrowAnyException()); - } - -} diff --git a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesTests.java b/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesTests.java deleted file mode 100644 index 444f81a2e1..0000000000 --- a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/amqp/SpringAmqpStubMessagesTests.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2021-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.amqp; - -import java.util.Collections; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.boot.autoconfigure.amqp.RabbitProperties; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class SpringAmqpStubMessagesTests { - - @Test - void should_send_message_without_headers_and_contract() { - final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class); - final MessageListenerAccessor messageListenerAccessor = mock(MessageListenerAccessor.class); - final RabbitProperties rabbitProperties = mock(RabbitProperties.class); - final SimpleMessageListenerContainer messageListenerContainer = mock(SimpleMessageListenerContainer.class); - final MessageListener messageListener = mock(MessageListener.class); - - when(messageListenerContainer.getMessageListener()).thenReturn(messageListener); - when(messageListenerAccessor.getListenerContainersForDestination(any(), any())) - .thenReturn(Collections.singletonList(messageListenerContainer)); - - final SpringAmqpStubMessages springAmqpStubMessages = new SpringAmqpStubMessages(rabbitTemplate, - messageListenerAccessor, rabbitProperties); - - Assertions.assertThatCode(() -> springAmqpStubMessages.send(anyString(), null, anyString(), null)) - .doesNotThrowAnyException(); - } - -} diff --git a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfigurationTests.java b/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfigurationTests.java deleted file mode 100644 index dca88615af..0000000000 --- a/spring-cloud-contract-verifier/src/test/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaConfigurationTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.verifier.messaging.kafka; - -import java.util.function.Supplier; - -import org.junit.Test; -import org.mockito.BDDMockito; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.test.EmbeddedKafkaBroker; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Tim Ysewyn - */ -public class ContractVerifierKafkaConfigurationTests { - - private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(KafkaAutoConfiguration.class, ContractVerifierKafkaConfiguration.class)) - .withUserConfiguration(CustomConfiguration.class); - - @Test - public void shouldCreateBeansByDefault() { - this.contextRunner.run((context) -> { - assertThat(context.getBeansOfType(KafkaStubMessages.class)).hasSize(1); - assertThat(context.getBeansOfType(ContractVerifierKafkaHelper.class)).hasSize(1); - }); - } - - @Test - public void shouldPickCustomKafkaTemplate() { - this.contextRunner.withUserConfiguration(CustomKafkaTemplateConfiguration.class).run((context) -> { - assertThat(context.getBeansOfType(KafkaStubMessages.class)).hasSize(1); - assertThat(context.getBean(KafkaStubMessages.class).kafkaTemplate) - .isSameAs(context.getBean(CustomKafkaTemplateConfiguration.class).myKafkaTemplate); - }); - } - - @Test - public void shouldNotCreateBeansWhenDisabled() { - this.contextRunner.withPropertyValues("stubrunner.kafka.enabled=false").run((context) -> { - assertThat(context.getBeansOfType(KafkaStubMessages.class)).hasSize(0); - assertThat(context.getBeansOfType(ContractVerifierKafkaHelper.class)).hasSize(0); - }); - } - - @Test - public void shouldCreateBeansWhenExplicitlyEnabled() { - this.contextRunner.withPropertyValues("stubrunner.kafka.enabled=true").run((context) -> { - assertThat(context.getBeansOfType(KafkaStubMessages.class)).hasSize(1); - assertThat(context.getBeansOfType(ContractVerifierKafkaHelper.class)).hasSize(1); - }); - } - - static class CustomConfiguration { - - @Bean - public EmbeddedKafkaBroker embeddedKafkaBroker() { - return new EmbeddedKafkaBroker(1); - } - - } - - static class CustomKafkaTemplateConfiguration { - - KafkaTemplate myKafkaTemplate = BDDMockito.mock(KafkaTemplate.class); - - @Bean - Supplier myKafkaTemplate() { - return () -> myKafkaTemplate; - } - - } - -} diff --git a/tests/pom.xml b/tests/pom.xml index fbf02c693a..b18acb0130 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -45,18 +45,18 @@ - + samples-messaging-camel samples-messaging-integration samples-messaging-amqp - - - + samples-messaging-jms + spring-cloud-contract-stub-runner-camel + spring-cloud-contract-stub-runner-boot-eureka spring-cloud-contract-stub-runner-boot-zookeeper spring-cloud-contract-stub-runner-context-path spring-cloud-contract-stub-runner-integration spring-cloud-contract-stub-runner-stream spring-cloud-contract-stub-runner-amqp - + spring-cloud-contract-stub-runner-jms spring-cloud-contract-stub-runner-kafka @@ -68,12 +68,12 @@ - + samples-messaging-camel samples-messaging-integration samples-messaging-amqp - - - + samples-messaging-jms + spring-cloud-contract-stub-runner-camel + spring-cloud-contract-stub-runner-boot-eureka spring-cloud-contract-stub-runner-boot-zookeeper spring-cloud-contract-stub-runner-context-path spring-cloud-contract-stub-runner-moco-contract-jar @@ -81,7 +81,7 @@ spring-cloud-contract-stub-runner-integration spring-cloud-contract-stub-runner-stream spring-cloud-contract-stub-runner-amqp - + spring-cloud-contract-stub-runner-jms spring-cloud-contract-stub-runner-kafka 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 653d72443a..44704120a5 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 @@ -22,6 +22,7 @@ import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath import com.toomuchcoding.jsonassert.JsonAssertion import groovy.json.JsonOutput +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -39,6 +40,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") +@Disabled("TODO: Migrate to middleware based approach") class AmqpMessagingApplicationSpec { // ALL CASES diff --git a/tests/samples-messaging-camel/pom.xml b/tests/samples-messaging-camel/pom.xml index f0875645c1..9effb86b5c 100644 --- a/tests/samples-messaging-camel/pom.xml +++ b/tests/samples-messaging-camel/pom.xml @@ -16,23 +16,23 @@ org.apache.camel.springboot - camel-spring-boot-starter + camel-rabbitmq-starter - org.apache.camel - camel-jms + org.apache.camel.springboot + camel-direct-starter - org.apache.camel - camel-activemq + org.apache.camel.springboot + camel-bean-starter - org.apache.activemq - activemq-pool + org.apache.camel.springboot + camel-jackson-starter - org.apache.camel - camel-jackson + org.springframework.boot + spring-boot-starter-amqp org.springframework.cloud @@ -50,8 +50,18 @@ test - org.spockframework - spock-junit4 + org.awaitility + awaitility + test + + + org.testcontainers + rabbitmq + test + + + org.testcontainers + junit-jupiter test diff --git a/tests/samples-messaging-camel/src/main/java/com/example/BookReturned.java b/tests/samples-messaging-camel/src/main/java/com/example/BookReturned.java index 525c9f6211..c89fb38bca 100644 --- a/tests/samples-messaging-camel/src/main/java/com/example/BookReturned.java +++ b/tests/samples-messaging-camel/src/main/java/com/example/BookReturned.java @@ -31,4 +31,9 @@ public class BookReturned implements Serializable { this.bookName = bookName; } + @Override + public String toString() { + return "BookReturned{" + "bookName='" + bookName + '\'' + '}'; + } + } diff --git a/tests/samples-messaging-camel/src/main/java/com/example/BookRouteConfiguration.java b/tests/samples-messaging-camel/src/main/java/com/example/BookRouteConfiguration.java index 90938624e3..1d66bd510a 100644 --- a/tests/samples-messaging-camel/src/main/java/com/example/BookRouteConfiguration.java +++ b/tests/samples-messaging-camel/src/main/java/com/example/BookRouteConfiguration.java @@ -16,9 +16,10 @@ package com.example; +import org.apache.camel.CamelContext; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.activemq.ActiveMQComponent; +import org.apache.camel.component.rabbitmq.RabbitMQComponent; import org.apache.camel.model.dataformat.JsonLibrary; import org.springframework.beans.factory.annotation.Value; @@ -32,26 +33,25 @@ import org.springframework.context.annotation.Configuration; public class BookRouteConfiguration { @Bean - ActiveMQComponent activeMQComponent(@Value("${activemq.url:vm://localhost?broker.persistent=false}") String url) { - ActiveMQComponent component = new ActiveMQComponent(); - component.setBrokerURL(url); - return component; - } - - @Bean - RoutesBuilder myRouter(final BookService bookService, final BookDeleter bookDeleter) { + RoutesBuilder myRouter(final BookService bookService, final BookDeleter bookDeleter, CamelContext context, + @Value("${spring.rabbitmq.port}") int port) { return new RouteBuilder() { @Override public void configure() throws Exception { + RabbitMQComponent component = context.getComponent("rabbitmq", RabbitMQComponent.class); + component.setAddresses("localhost:" + port); + // scenario 1 - from bean to output from("direct:start").unmarshal().json(JsonLibrary.Jackson, BookReturned.class).bean(bookService) - .to("jms:output"); + .marshal().json(JsonLibrary.Jackson, BookReturned.class).to("rabbitmq:output?queue=output"); // scenario 2 - from input to output - from("jms:input").unmarshal().json(JsonLibrary.Jackson, BookReturned.class).bean(bookService) - .to("jms:output"); + from("rabbitmq:input?queue=input").unmarshal().json(JsonLibrary.Jackson, BookReturned.class) + .bean(bookService).marshal().json(JsonLibrary.Jackson, BookReturned.class) + .to("rabbitmq:output"); // scenario 3 - from input to no output - from("jms:delete").unmarshal().json(JsonLibrary.Jackson, BookDeleted.class).bean(bookDeleter); + from("rabbitmq:delete?queue=delete").unmarshal().json(JsonLibrary.Jackson, BookDeleted.class) + .bean(bookDeleter); } }; diff --git a/tests/samples-messaging-camel/src/test/groovy/com/example/CamelMessagingApplicationSpec.groovy b/tests/samples-messaging-camel/src/test/groovy/com/example/CamelMessagingApplicationSpec.groovy index 155ea8121a..a5d3a52b78 100644 --- a/tests/samples-messaging-camel/src/test/groovy/com/example/CamelMessagingApplicationSpec.groovy +++ b/tests/samples-messaging-camel/src/test/groovy/com/example/CamelMessagingApplicationSpec.groovy @@ -23,25 +23,28 @@ import com.jayway.jsonpath.JsonPath import com.toomuchcoding.jsonassert.JsonAssertion import org.apache.camel.Message import org.apache.camel.model.ModelCamelContext -import spock.lang.Specification -import spock.util.concurrent.PollingConditions +import org.awaitility.Awaitility +import org.junit.jupiter.api.Test +import org.testcontainers.containers.RabbitMQContainer +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers 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 import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper -import org.springframework.test.context.ContextConfiguration - +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource /** * SPIKE ON TESTS FROM NOTES IN MessagingSpec */ // Context configuration would end up in base class @AutoConfigureMessageVerifier -@SpringBootTest(classes = CamelMessagingApplication) -class CamelMessagingApplicationSpec extends Specification { +@SpringBootTest(classes = CamelMessagingApplication, properties = 'camel.component.rabbitmq.declare=true') +@Testcontainers +class CamelMessagingApplicationSpec { // ALL CASES @Autowired @@ -53,11 +56,16 @@ class CamelMessagingApplicationSpec extends Specification { ContractVerifierObjectMapper contractVerifierObjectMapper = new ContractVerifierObjectMapper() - void setupSpec() { - System.setProperty("org.apache.activemq.SERIALIZABLE_PACKAGES", "*") + @Container + static RabbitMQContainer broker = new RabbitMQContainer("rabbitmq:3.7.25-management-alpine"); + + @DynamicPropertySource + static void setup(DynamicPropertyRegistry registry) { + registry.add("spring.rabbitmq.port", () -> broker.getAmqpPort()); } - def "should work for triggered based messaging"() { + @Test + void "should work for triggered based messaging"() { given: Contract.make { label 'some_label' @@ -65,7 +73,7 @@ class CamelMessagingApplicationSpec extends Specification { triggeredBy('bookReturnedTriggered()') } outputMessage { - sentTo('activemq:output') + sentTo('rabbitmq:output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') @@ -76,20 +84,21 @@ class CamelMessagingApplicationSpec extends Specification { when: bookReturnedTriggered() then: - def response = contractVerifierMessaging.receive('activemq:output') - response.headers.get('BOOK-NAME') == 'foo' + def response = contractVerifierMessaging.receive('rabbitmq:output') + assert response.headers.get('BOOK-NAME') == 'foo' and: DocumentContext parsedJson = JsonPath. parse(contractVerifierObjectMapper.writeValueAsString(response.body)) JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo') } - def "should generate tests triggered by a message"() { + @Test + void "should generate tests triggered by a message"() { given: Contract.make { label 'some_label' input { - messageFrom('jms:input') + messageFrom('rabbitmq:input') messageBody([ bookName: 'foo' ]) @@ -99,7 +108,7 @@ class CamelMessagingApplicationSpec extends Specification { } } outputMessage { - sentTo('jms:output') + sentTo('rabbitmq:output') body([ bookName: 'foo' ]) @@ -112,22 +121,23 @@ class CamelMessagingApplicationSpec extends Specification { when: contractVerifierMessaging.send( contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), - [sample: 'header'], 'jms:input') + [sample: 'header'], 'rabbitmq:input') then: - def response = contractVerifierMessaging.receive('jms:output') - response.headers.get('BOOK-NAME') == 'foo' + def response = contractVerifierMessaging.receive('rabbitmq:output') + assert response.headers.get('BOOK-NAME') == 'foo' and: DocumentContext parsedJson = JsonPath. parse(contractVerifierObjectMapper.writeValueAsString(response.body)) 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: Contract.make { label 'some_label' input { - messageFrom('jms:delete') + messageFrom('rabbitmq:delete') messageBody([ bookName: 'foo' ]) @@ -141,9 +151,8 @@ class CamelMessagingApplicationSpec extends Specification { when: contractVerifierMessaging. send(contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), - [sample: 'header'], 'jms:delete') + [sample: 'header'], 'rabbitmq:delete') then: - noExceptionThrown() bookWasDeleted() } @@ -152,12 +161,9 @@ class CamelMessagingApplicationSpec extends Specification { sendBody('direct:start', '''{"bookName" : "foo" }''') } - PollingConditions pollingConditions = new PollingConditions() - void bookWasDeleted() { - pollingConditions.eventually { + Awaitility.await().untilAsserted(() -> { assert bookDeleter.bookSuccessfulyDeleted.get() - } + }) } - } diff --git a/tests/samples-messaging-camel/src/test/resources/logback-test.xml b/tests/samples-messaging-camel/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..3951c629f7 --- /dev/null +++ b/tests/samples-messaging-camel/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + diff --git a/tests/samples-messaging-jms/pom.xml b/tests/samples-messaging-jms/pom.xml index a617327d1d..92dc7e92c6 100644 --- a/tests/samples-messaging-jms/pom.xml +++ b/tests/samples-messaging-jms/pom.xml @@ -16,7 +16,15 @@ org.springframework.boot - spring-boot-starter-activemq + spring-boot-starter-artemis + + + org.apache.activemq + artemis-jms-server + + + com.fasterxml.jackson.core + jackson-databind org.springframework.cloud @@ -25,7 +33,7 @@ org.spockframework - spock-spring + spock-core test @@ -33,6 +41,11 @@ spring-boot-starter-test test + + org.awaitility + awaitility + test + diff --git a/tests/samples-messaging-jms/src/main/java/com/example/BookDeleter.java b/tests/samples-messaging-jms/src/main/java/com/example/BookDeleter.java index 1e68181684..875a1664c0 100644 --- a/tests/samples-messaging-jms/src/main/java/com/example/BookDeleter.java +++ b/tests/samples-messaging-jms/src/main/java/com/example/BookDeleter.java @@ -18,13 +18,12 @@ package com.example; import java.util.concurrent.atomic.AtomicBoolean; -import javax.jms.JMSException; -import javax.jms.Message; - +import jakarta.jms.JMSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.annotation.JmsListener; +import org.springframework.messaging.Message; import org.springframework.stereotype.Component; @Component @@ -46,7 +45,7 @@ public class BookDeleter { public void bookDeleted(Message message) throws JMSException { log.info("Deleting book " + message); this.bookSuccessfulyDeleted.set(true); - log.info("Book successfuly deleted [" + this.bookSuccessfulyDeleted + "]"); + log.info("Book successfully deleted [" + this.bookSuccessfulyDeleted + "]"); } } diff --git a/tests/samples-messaging-jms/src/main/java/com/example/BookService.java b/tests/samples-messaging-jms/src/main/java/com/example/BookService.java index e274af58ae..2c5717069d 100644 --- a/tests/samples-messaging-jms/src/main/java/com/example/BookService.java +++ b/tests/samples-messaging-jms/src/main/java/com/example/BookService.java @@ -44,7 +44,7 @@ public class BookService { public void returnBook() { BookReturned bookReturned = new BookReturned("foo"); jmsTemplate.convertAndSend("output2", "{\"bookName\":\"foo\"}", message -> { - message.setStringProperty("BOOK-NAME", bookReturned.bookName); + message.setStringProperty("BOOKNAME", bookReturned.bookName); return message; }); } diff --git a/tests/samples-messaging-jms/src/test/groovy/com/example/JmsMessagingApplicationSpec.groovy b/tests/samples-messaging-jms/src/test/groovy/com/example/JmsMessagingApplicationSpec.groovy index 8488090f62..61722dcdde 100644 --- a/tests/samples-messaging-jms/src/test/groovy/com/example/JmsMessagingApplicationSpec.groovy +++ b/tests/samples-messaging-jms/src/test/groovy/com/example/JmsMessagingApplicationSpec.groovy @@ -17,14 +17,15 @@ package com.example import javax.inject.Inject -import javax.jms.JMSException -import javax.jms.Message import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath import com.toomuchcoding.jsonassert.JsonAssertion -import spock.lang.Specification -import spock.util.concurrent.PollingConditions +import jakarta.jms.JMSException +import jakarta.jms.Message +import org.awaitility.Awaitility +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -44,131 +45,131 @@ import org.springframework.test.annotation.DirtiesContext // Context configuration would end up in base class @AutoConfigureMessageVerifier @SpringBootTest(classes = JmsMessagingApplication) -class JmsMessagingApplicationSpec extends Specification { +class JmsMessagingApplicationSpec { - // ALL CASES - @Autowired - JmsTemplate jmsTemplate - @Autowired - BookDeleter bookDeleter - @Inject - MessageVerifier messageVerifier - @Inject - ContractVerifierMessaging contractVerifierMessaging - @Inject - ContractVerifierObjectMapper contractVerifierObjectMapper + // ALL CASES + @Autowired + JmsTemplate jmsTemplate + @Autowired + BookDeleter bookDeleter + @Inject + MessageVerifier messageVerifier + @Inject + ContractVerifierMessaging contractVerifierMessaging + @Inject + ContractVerifierObjectMapper contractVerifierObjectMapper - void setupSpec() { - System.setProperty("org.apache.activemq.SERIALIZABLE_PACKAGES", "*") - System.setProperty("debug", "true") - } + @BeforeAll + static void setupSpec() { + System.setProperty("debug", "true") + } - def "should work for triggered based messaging"() { - given: - Contract.make { - label 'some_label' - input { - triggeredBy('bookReturnedTriggered()') - } - outputMessage { - sentTo('output') - body('''{ "bookName" : "foo" }''') - headers { - header('BOOK-NAME', 'foo') - } - } - } - // generated test should look like this: - when: - bookReturnedTriggered() - then: - ContractVerifierMessage response = contractVerifierMessaging.receive('output') - response.getHeader('BOOK-NAME') == 'foo' - and: - DocumentContext parsedJson = JsonPath. - parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())) - JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo') - } + @Test + void "should work for triggered based messaging"() { + given: + Contract.make { + label 'some_label' + input { + triggeredBy('bookReturnedTriggered()') + } + outputMessage { + sentTo('output') + body('''{ "bookName" : "foo" }''') + headers { + header('BOOKNAME', 'foo') + } + } + } + // generated test should look like this: + when: + bookReturnedTriggered() + then: + ContractVerifierMessage response = contractVerifierMessaging.receive('output') + assert response.getHeader('BOOKNAME') == 'foo' + and: + DocumentContext parsedJson = JsonPath. + parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())) + JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo') + } - @DirtiesContext - def "should generate tests triggered by a message"() { - given: - Contract.make { - label 'some_label' - input { - messageFrom('input2') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - } - outputMessage { - sentTo('output2') - body([ - bookName: 'foo' - ]) - headers { - header('BOOK-NAME', 'foo') - } - } - } - // generated test should look like this: - when: - messageVerifier.send( - contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), - [sample: 'header'], 'input2') - then: - ContractVerifierMessage response = contractVerifierMessaging.receive('output2') - response.getHeader('BOOK-NAME') == 'foo' - and: - DocumentContext parsedJson = JsonPath. - parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())) - JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo') - } + @DirtiesContext + @Test + void "should generate tests triggered by a message"() { + given: + Contract.make { + label 'some_label' + input { + messageFrom('input2') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + } + outputMessage { + sentTo('output2') + body([ + bookName: 'foo' + ]) + headers { + header('BOOKNAME', 'foo') + } + } + } + // generated test should look like this: + when: + messageVerifier.send( + contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), + [sample: 'header'], 'input2') + then: + ContractVerifierMessage response = contractVerifierMessaging.receive('output2') + assert response.getHeader('BOOKNAME') == 'foo' + and: + DocumentContext parsedJson = JsonPath. + parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())) + JsonAssertion.assertThat(parsedJson).field('bookName').isEqualTo('foo') + } - def "should generate tests without destination, triggered by a message"() { - given: - Contract.make { - label 'some_label' - input { - messageFrom('delete') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - assertThat('bookWasDeleted()') - } - } - // generated test should look like this: - when: - messageVerifier. - send(contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), - [sample: 'header'], 'delete') - then: - noExceptionThrown() - bookWasDeleted() - } + @Test + void "should generate tests without destination, triggered by a message"() { + given: + Contract.make { + label 'some_label' + input { + messageFrom('delete') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + assertThat('bookWasDeleted()') + } + } + // generated test should look like this: + when: + messageVerifier. + send(contractVerifierObjectMapper.writeValueAsString([bookName: 'foo']), + [sample: 'header'], 'delete') + then: + bookWasDeleted() + } - void bookReturnedTriggered() { - jmsTemplate.convertAndSend("output", '''{"bookName" : "foo" }''', new MessagePostProcessor() { - @Override - Message postProcessMessage(Message message) throws JMSException { - message.setStringProperty("BOOK-NAME", "foo") - return message - } - }) - } + void bookReturnedTriggered() { + jmsTemplate.convertAndSend("output", '''{"bookName" : "foo" }''', new MessagePostProcessor() { + @Override + Message postProcessMessage(Message message) throws JMSException { + message.setStringProperty("BOOKNAME", "foo") + return message + } + }) + } - PollingConditions pollingConditions = new PollingConditions() - - void bookWasDeleted() { - pollingConditions.eventually { - assert bookDeleter.bookSuccessfulyDeleted.get() - } - } + void bookWasDeleted() { + Awaitility.await().untilAsserted( () -> { + assert bookDeleter.bookSuccessfulyDeleted.get() + }) + } } diff --git a/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerRabbitListenerSpec.groovy b/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerRabbitListenerSpec.groovy index fde3f16228..dc8bc35c39 100644 --- a/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerRabbitListenerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerRabbitListenerSpec.groovy @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.stubrunner.messaging.amqp +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -27,6 +28,7 @@ import org.springframework.test.context.ActiveProfiles @AutoConfigureStubRunner @SpringBootTest(classes = AmqpMessagingApplication) @ActiveProfiles("listener") +@Disabled("TODO: Migrate to middleware based approach") class AmqpStubRunnerRabbitListenerSpec { @Autowired diff --git a/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy b/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy index f657206d95..826deef269 100644 --- a/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-amqp/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/amqp/AmqpStubRunnerSpec.groovy @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.stubrunner.messaging.amqp +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.mockito.ArgumentCaptor @@ -30,6 +31,7 @@ import static org.mockito.BDDMockito.then @AutoConfigureStubRunner @SpringBootTest(classes = AmqpMessagingApplication) +@Disabled("TODO: Migrate to middleware based approach") class AmqpStubRunnerSpec { @Autowired diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml b/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml index 5e4dc31ec6..f73177c5d2 100644 --- a/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/pom.xml @@ -13,20 +13,11 @@ jar Spring Cloud Contract Stub Runner Boot Eureka Spring Cloud Contract Stub Runner Boot Eureka - - 1.19.1 - org.springframework.cloud spring-cloud-contract-stub-runner - - com.sun.jersey.contribs - jersey-apache-client4 - ${jersey-apache-client4.version} - true - org.springframework.cloud spring-cloud-starter-contract-stub-runner-jetty @@ -42,25 +33,11 @@ spring-cloud-starter-netflix-eureka-server true - - junit - junit - test - - - org.codehaus.groovy - groovy - org.spockframework spock-core test - - org.spockframework - spock-spring - test - org.springframework.boot spring-boot-starter-test @@ -76,6 +53,11 @@ spring-boot-starter-webflux test + + org.awaitility + awaitility + test + diff --git a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy index 207c8c44b0..970a3a9f8e 100644 --- a/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-boot-eureka/src/test/groovy/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfigurationSpec.groovy @@ -16,41 +16,35 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka -import javax.servlet.Filter -import javax.servlet.FilterChain -import javax.servlet.FilterConfig -import javax.servlet.ServletException -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse +import java.util.concurrent.TimeUnit -import spock.lang.AutoCleanup -import spock.lang.Shared -import spock.lang.Specification -import spock.util.concurrent.PollingConditions +import groovy.util.logging.Slf4j +import jakarta.servlet.Filter +import jakarta.servlet.FilterChain +import jakarta.servlet.FilterConfig +import jakarta.servlet.ServletException +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import org.awaitility.Awaitility +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.SpringApplication 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.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.netflix.eureka.EnableEurekaClient import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpMethod import org.springframework.http.client.ClientHttpResponse -import org.springframework.test.context.ContextConfiguration import org.springframework.web.client.DefaultResponseErrorHandler -import org.springframework.web.client.RequestCallback -import org.springframework.web.client.ResponseExtractor -import org.springframework.web.client.RestClientException import org.springframework.web.client.RestTemplate - /** * @author Marcin Grzejszczak */ @@ -62,24 +56,24 @@ import org.springframework.web.client.RestTemplate "eureka.client.enabled=true", "eureka.instance.leaseRenewalIntervalInSeconds=1", "ribbon.ServerListRefreshInterval=100"]) -@AutoConfigureStubRunner(ids = -["org.springframework.cloud.contract.verifier.stubs:loanIssuance", - "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", - "org.springframework.cloud.contract.verifier.stubs:bootService"] , +@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 StubRunnerSpringCloudEurekaAutoConfigurationSpec extends Specification { +@Slf4j +class StubRunnerSpringCloudEurekaAutoConfigurationSpec { @Autowired StubFinder stubFinder + @Autowired @LoadBalanced RestTemplate restTemplate - @Shared - @AutoCleanup - ConfigurableApplicationContext eurekaServer - void setupSpec() { + static ConfigurableApplicationContext eurekaServer + + @BeforeAll + static void setupSpec() { System.clearProperty("stubrunner.stubs.repository.root") System.clearProperty("stubrunner.stubs.classifier") eurekaServer = SpringApplication.run(EurekaServer, @@ -90,44 +84,41 @@ class StubRunnerSpringCloudEurekaAutoConfigurationSpec extends Specification { "--spring.profiles.active=eureka") } - void cleanupSpec() { + @AfterAll + static void cleanupSpec() { System.clearProperty("stubrunner.stubs.repository.root") System.clearProperty("stubrunner.stubs.classifier") } - PollingConditions conditions = new PollingConditions(timeout: 240, delay: 1) - - 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' + assert "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' + assert "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' and: 'Stubs can be reached via load service discovery' - conditions.eventually { - assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' - } - restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' + log.info("Waiting for stubs to register in Eureka...") + Awaitility.await() + .pollInterval(1, TimeUnit.SECONDS) + .pollDelay(10, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .untilAsserted(() -> { + try { + assert restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' + } catch (Exception ex) { + throw new AssertionError(ex) + } + }) + assert restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' } @Configuration @EnableAutoConfiguration - @EnableEurekaClient static class Config { @Bean @LoadBalanced RestTemplate restTemplate() { - def template = new RestTemplate() { - - @Override - protected T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor responseExtractor) throws RestClientException { - try { - return super.doExecute(url, method, requestCallback, responseExtractor) - } - catch (Exception e) { - throw new AssertionError(e) - } - } - } + def template = new RestTemplate() template.errorHandler = new DefaultResponseErrorHandler() { @Override void handleError(ClientHttpResponse response) throws IOException { diff --git a/tests/spring-cloud-contract-stub-runner-camel/pom.xml b/tests/spring-cloud-contract-stub-runner-camel/pom.xml index f3e0f9f240..7fa4ef2fda 100644 --- a/tests/spring-cloud-contract-stub-runner-camel/pom.xml +++ b/tests/spring-cloud-contract-stub-runner-camel/pom.xml @@ -31,25 +31,11 @@ org.apache.camel camel-jackson - - junit - junit - test - - - org.apache.groovy - groovy - org.spockframework spock-core test - - org.spockframework - spock-spring - test - org.springframework.boot spring-boot-starter-test @@ -60,11 +46,6 @@ camel-activemq test - - org.apache.activemq - activemq-pool - test - org.springframework.boot spring-boot-starter-web 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 deleted file mode 100644 index 71e254a0bd..0000000000 --- a/tests/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.stubrunner.messaging.camel - -import groovy.json.JsonOutput -import groovy.json.JsonSlurper -import org.apache.activemq.spring.ActiveMQConnectionFactory -import org.apache.camel.CamelContext -import org.apache.camel.ConsumerTemplate -import org.apache.camel.Exchange -import org.apache.camel.ProducerTemplate -import org.apache.camel.component.activemq.ActiveMQComponent -import org.apache.camel.component.jms.JmsConfiguration -import org.apache.camel.impl.engine.DefaultShutdownStrategy -import spock.lang.Ignore -import spock.lang.Specification - -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 -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.test.annotation.DirtiesContext -import org.springframework.test.context.ContextConfiguration - -/** - * @author Marcin Grzejszczak - */ -@ContextConfiguration(classes = Config, loader = SpringBootContextLoader) -@SpringBootTest(properties = "debug=true") -@AutoConfigureStubRunner -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -@Ignore -class CamelStubRunnerSpec extends Specification { - - //TODO: fix extremely slow test that causes pipeline to fail and then unignore - - @Autowired - StubFinder stubFinder - @Autowired - CamelContext camelContext - ConsumerTemplate consumerTemplate - ProducerTemplate producerTemplate - - def setup() { - consumerTemplate = camelContext.createConsumerTemplate() - producerTemplate = camelContext.createProducerTemplate() - } - - def cleanup() { - // ensure that message were taken from the queue - consumerTemplate.receive('jms:output', 100) - consumerTemplate.receive('jms:input', 100) - this.producerTemplate.stop() - this.consumerTemplate.stop() - def strategy = new DefaultShutdownStrategy(this.camelContext) - strategy.timeout = 1 - this.camelContext.shutdownStrategy = strategy - } - - def 'should not trigger a message that does not match input'() { - when: - producerTemplate. - sendBodyAndHeaders('jms:input', new BookReturned('notmatching'), [wrong: 'header_value']) - then: - Exchange receivedMessage = consumerTemplate.receive('jms:output', 100) - and: - receivedMessage == null - } - - def 'should download the stub and register a route for it'() { - when: - // tag::client_send[] - producerTemplate. - sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header']) - // end::client_send[] - then: - // tag::client_receive[] - Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000) - // end::client_receive[] - and: - // tag::client_receive_message[] - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.in.body) - receivedMessage.in.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[] - Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000) - // end::client_trigger_receive[] - and: - // tag::client_trigger_message[] - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.in.body) - receivedMessage.in.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:camelService', 'return_book_1') - // end::trigger_group_artifact[] - then: - Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000) - and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.in.body) - receivedMessage.in.headers.get('BOOK-NAME') == 'foo' - } - - def 'should trigger a label for the existing artifactId'() { - when: - // tag::trigger_artifact[] - stubFinder.trigger('camelService', 'return_book_1') - // end::trigger_artifact[] - then: - Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000) - and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.in.body) - receivedMessage.in.headers.get('BOOK-NAME') == 'foo' - } - - def 'should throw an exception when missing label is passed'() { - when: - stubFinder.trigger('missing label') - then: - thrown(IllegalArgumentException) - } - - def 'should throw an exception when missing label and artifactid is passed'() { - when: - stubFinder.trigger('some:service', 'return_book_1') - then: - thrown(IllegalArgumentException) - } - - def 'should trigger messages by running all triggers'() { - when: - // tag::trigger_all[] - stubFinder.trigger() - // end::trigger_all[] - then: - Exchange receivedMessage = consumerTemplate.receive('jms:output', 5000) - and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.in.body) - receivedMessage.in.headers.get('BOOK-NAME') == 'foo' - } - - def 'should trigger a label with no output message'() { - when: - // tag::trigger_no_output[] - producerTemplate. - sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header']) - // end::trigger_no_output[] - then: - noExceptionThrown() - } - - private boolean assertThatBodyContainsBookNameFoo(Object payload) { - String objectAsString = payload instanceof String ? payload : - JsonOutput.toJson(payload) - def json = new JsonSlurper().parseText(objectAsString) - return json.bookName == 'foo' - } - - @Configuration - @ComponentScan - @EnableAutoConfiguration - static class Config { - - @Bean - ActiveMQConnectionFactory activeMQConnectionFactory(@Value('${activemq.url:vm://localhost?broker.persistent=false}') String url) { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL: url) - try { - factory.trustAllPackages = true - } - catch (Throwable e) { - } - return factory - } - - @Bean - JmsConfiguration jmsConfiguration(ActiveMQConnectionFactory activeMQConnectionFactory) { - return new JmsConfiguration(connectionFactory: activeMQConnectionFactory) - } - - @Bean - ActiveMQComponent activeMQComponent(JmsConfiguration jmsConfiguration) { - return new ActiveMQComponent(configuration: jmsConfiguration) - } - } - - - Contract dsl = - // tag::sample_dsl[] - Contract.make { - label 'return_book_1' - input { - triggeredBy('bookReturnedTriggered()') - } - outputMessage { - sentTo('jms:output') - 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('jms:input') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - } - outputMessage { - sentTo('jms:output') - 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('jms:delete') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - assertThat('bookWasDeleted()') - } - } - // end::sample_dsl_3[] -} diff --git a/tests/spring-cloud-contract-stub-runner-jms/pom.xml b/tests/spring-cloud-contract-stub-runner-jms/pom.xml index fbc018143e..40f76b55e7 100644 --- a/tests/spring-cloud-contract-stub-runner-jms/pom.xml +++ b/tests/spring-cloud-contract-stub-runner-jms/pom.xml @@ -14,38 +14,24 @@ Spring Cloud Contract Stub Runner JMS Spring Cloud Contract Stub Runner JMS + + org.springframework.boot + spring-boot-starter-artemis + + + org.apache.activemq + artemis-jms-server + org.springframework.cloud spring-cloud-contract-stub-runner - - - org.springframework.cloud - spring-cloud-starter-contract-stub-runner-jetty test - - org.springframework.boot - spring-boot-starter-activemq - - - junit - junit - test - - - org.apache.groovy - groovy - org.spockframework spock-core test - - org.spockframework - spock-spring - test - org.springframework.boot spring-boot-starter-test diff --git a/tests/spring-cloud-contract-stub-runner-jms/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/jms/JmsStubRunnerSpec.groovy b/tests/spring-cloud-contract-stub-runner-jms/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/jms/JmsStubRunnerSpec.groovy index 5cc194286c..633fb44ec4 100644 --- a/tests/spring-cloud-contract-stub-runner-jms/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/jms/JmsStubRunnerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-jms/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/jms/JmsStubRunnerSpec.groovy @@ -16,24 +16,23 @@ package org.springframework.cloud.contract.stubrunner.messaging.jms -import javax.jms.JMSException -import javax.jms.Message -import javax.jms.TextMessage - import groovy.json.JsonOutput import groovy.json.JsonSlurper -import org.apache.activemq.ActiveMQConnectionFactory -import spock.lang.IgnoreIf -import spock.lang.Specification +import jakarta.jms.JMSException +import jakarta.jms.Message +import jakarta.jms.TextMessage +import org.assertj.core.api.BDDAssertions +import org.junit.jupiter.api.AfterEach +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.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.EnableAutoConfiguration 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.jms.annotation.EnableJms @@ -44,21 +43,23 @@ import org.springframework.jms.core.MessagePostProcessor */ @SpringBootTest(classes = Config, properties = ["debug=true"]) @AutoConfigureStubRunner -@IgnoreIf({ os.windows }) -class JmsStubRunnerSpec extends Specification { +@DisabledOnOs(OS.WINDOWS) +class JmsStubRunnerSpec { @Autowired StubFinder stubFinder @Autowired JmsTemplate jmsTemplate - def cleanup() { + @AfterEach + void cleanup() { // ensure that message were taken from the queue jmsTemplate.receive('output') jmsTemplate.receive('input') } - def 'should download the stub and register a route for it'() { + @Test + void 'should download the stub and register a route for it'() { when: // tag::client_send[] jmsTemplate. @@ -76,13 +77,14 @@ class JmsStubRunnerSpec extends Specification { // end::client_receive[] and: // tag::client_receive_message[] - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.getText()) - receivedMessage.getStringProperty('BOOK-NAME') == 'foo' + assert receivedMessage != null + assert assertThatBodyContainsBookNameFoo(receivedMessage.getText()) + assert receivedMessage.getStringProperty('BOOKNAME') == 'foo' // end::client_receive_message[] } - def 'should trigger a message by label'() { + @Test + void 'should trigger a message by label'() { when: // tag::client_trigger[] stubFinder.trigger('return_book_1') @@ -93,13 +95,14 @@ class JmsStubRunnerSpec extends Specification { // end::client_trigger_receive[] and: // tag::client_trigger_message[] - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.getText()) - receivedMessage.getStringProperty('BOOK-NAME') == 'foo' + assert receivedMessage != null + assert assertThatBodyContainsBookNameFoo(receivedMessage.getText()) + assert receivedMessage.getStringProperty('BOOKNAME') == 'foo' // end::client_trigger_message[] } - def 'should trigger a label for the existing groupId:artifactId'() { + @Test + void 'should trigger a label for the existing groupId:artifactId'() { when: // tag::trigger_group_artifact[] stubFinder. @@ -108,12 +111,13 @@ class JmsStubRunnerSpec extends Specification { then: TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output') and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.getText()) - receivedMessage.getStringProperty('BOOK-NAME') == 'foo' + assert receivedMessage != null + assert assertThatBodyContainsBookNameFoo(receivedMessage.getText()) + assert receivedMessage.getStringProperty('BOOKNAME') == 'foo' } - def 'should trigger a label for the existing artifactId'() { + @Test + void 'should trigger a label for the existing artifactId'() { when: // tag::trigger_artifact[] stubFinder.trigger('stubs', 'return_book_1') @@ -121,26 +125,25 @@ class JmsStubRunnerSpec extends Specification { then: TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output') and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.getText()) - receivedMessage.getStringProperty('BOOK-NAME') == 'foo' + assert receivedMessage != null + assert assertThatBodyContainsBookNameFoo(receivedMessage.getText()) + assert receivedMessage.getStringProperty('BOOKNAME') == 'foo' } - def 'should throw an exception when missing label is passed'() { + @Test + void 'should throw an exception when missing label is passed'() { when: - stubFinder.trigger('missing label') - then: - thrown(IllegalArgumentException) + BDDAssertions.thenThrownBy(() -> stubFinder.trigger('missing label')).isInstanceOf(IllegalArgumentException) } - def 'should throw an exception when missing label and artifactid is passed'() { + @Test + void 'should throw an exception when missing label and artifactid is passed'() { when: - stubFinder.trigger('some:service', 'return_book_1') - then: - thrown(IllegalArgumentException) + BDDAssertions.thenThrownBy(() -> stubFinder.trigger('some:service', 'return_book_1')).isInstanceOf(IllegalArgumentException) } - def 'should trigger messages by running all triggers'() { + @Test + void 'should trigger messages by running all triggers'() { when: // tag::trigger_all[] stubFinder.trigger() @@ -148,12 +151,13 @@ class JmsStubRunnerSpec extends Specification { then: TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output') and: - receivedMessage != null - assertThatBodyContainsBookNameFoo(receivedMessage.getText()) - receivedMessage.getStringProperty('BOOK-NAME') == 'foo' + assert receivedMessage != null + assert assertThatBodyContainsBookNameFoo(receivedMessage.getText()) + assert receivedMessage.getStringProperty('BOOKNAME') == 'foo' } - def 'should trigger a label with no output message'() { + @Test + void 'should trigger a label with no output message'() { when: // tag::trigger_no_output[] jmsTemplate. @@ -165,11 +169,10 @@ class JmsStubRunnerSpec extends Specification { } }) // end::trigger_no_output[] - then: - noExceptionThrown() } - def 'should not trigger a message that does not match input'() { + @Test + void 'should not trigger a message that does not match input'() { when: jmsTemplate. convertAndSend('input', new BookReturned('notmatching'), new MessagePostProcessor() { @@ -182,7 +185,7 @@ class JmsStubRunnerSpec extends Specification { then: TextMessage receivedMessage = (TextMessage) jmsTemplate.receive('output') and: - receivedMessage == null + assert receivedMessage == null } private boolean assertThatBodyContainsBookNameFoo(Object payload) { @@ -197,16 +200,7 @@ class JmsStubRunnerSpec extends Specification { @EnableAutoConfiguration @EnableJms static class Config { - @Bean - ActiveMQConnectionFactory activeMQConnectionFactory(@Value('${activemq.url:vm://localhost?broker.persistent=false}') String url) { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL: url) - try { - factory.trustAllPackages = true - } - catch (Throwable e) { - } - return factory - } + } Contract dsl = @@ -220,7 +214,7 @@ class JmsStubRunnerSpec extends Specification { sentTo('output') body('''{ "bookName" : "foo" }''') headers { - header('BOOK-NAME', 'foo') + header('BOOKNAME', 'foo') } } } @@ -245,7 +239,7 @@ class JmsStubRunnerSpec extends Specification { bookName: 'foo' ]) headers { - header('BOOK-NAME', 'foo') + header('BOOKNAME', 'foo') } } } diff --git a/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned1.groovy b/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned1.groovy index 0094bc7b1d..7fc403e12f 100644 --- a/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned1.groovy +++ b/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned1.groovy @@ -7,7 +7,7 @@ org.springframework.cloud.contract.spec.Contract.make { sentTo('output') body('''{ "bookName" : "foo" }''') headers { - header('BOOK-NAME', 'foo') + header('BOOKNAME', 'foo') } } -} \ No newline at end of file +} diff --git a/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned2.groovy b/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned2.groovy index e0d53098d5..0f568e0d8d 100644 --- a/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned2.groovy +++ b/tests/spring-cloud-contract-stub-runner-jms/src/test/resources/stubs/bookReturned2.groovy @@ -15,7 +15,7 @@ org.springframework.cloud.contract.spec.Contract.make { bookName: 'foo' ]) headers { - header('BOOK-NAME', 'foo') + header('BOOKNAME', 'foo') } } -} \ No newline at end of file +} diff --git a/tests/spring-cloud-contract-stub-runner-kafka/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/kafka/KafkaStubRunnerSpec.groovy b/tests/spring-cloud-contract-stub-runner-kafka/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/kafka/KafkaStubRunnerSpec.groovy index f661e097c1..2160bb0d83 100644 --- a/tests/spring-cloud-contract-stub-runner-kafka/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/kafka/KafkaStubRunnerSpec.groovy +++ b/tests/spring-cloud-contract-stub-runner-kafka/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/kafka/KafkaStubRunnerSpec.groovy @@ -27,6 +27,7 @@ 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.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS @@ -55,6 +56,7 @@ import org.springframework.stereotype.Component @DisabledOnOs(value = OS.WINDOWS) @EmbeddedKafka(topics = ["input", "input2", "output", "delete"]) @Commons +@Disabled("TODO: Migrate to middleware based approach") class KafkaStubRunnerSpec { @Autowired