Brings back Eureka support, updates samples, removes mocked kafka & rabbit samples
This commit is contained in:
@@ -19,8 +19,7 @@
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<maven.plugin.plugin.version>3.4</maven.plugin.plugin.version>
|
||||
<configprops.inclusionPattern>stubrunner.*|wiremock.*|</configprops.inclusionPattern>
|
||||
<!-- TODO: Remove me -->
|
||||
<!--<upload-docs-zip.phase>deploy</upload-docs-zip.phase>-->
|
||||
<upload-docs-zip.phase>deploy</upload-docs-zip.phase>
|
||||
<!-- Aligned with Groovy in SC-Build -->
|
||||
<groovy.version>4.0.0</groovy.version>
|
||||
</properties>
|
||||
@@ -149,11 +148,10 @@
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- TODO: Remove me -->
|
||||
<!--<plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
</plugin>-->
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
62
pom.xml
62
pom.xml
@@ -50,6 +50,8 @@
|
||||
<gmavenplus-plugin.version>1.13.0</gmavenplus-plugin.version>
|
||||
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
|
||||
<awaitility.version>4.2.0</awaitility.version>
|
||||
<artemis.version>${artemis-jms-server.version}</artemis.version>
|
||||
<testcontainers.version>1.17.5</testcontainers.version>
|
||||
|
||||
<!-- We need to have compatibility with Gradle -->
|
||||
<groovy.version>4.0.0</groovy.version>
|
||||
@@ -70,6 +72,7 @@
|
||||
|
||||
<javadoc.failOnError>false</javadoc.failOnError>
|
||||
<javadoc.failOnWarnings>false</javadoc.failOnWarnings>
|
||||
<artemis-jms-server.version>2.26.0</artemis-jms-server.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -119,6 +122,26 @@
|
||||
<artifactId>camel-spring-boot</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-rabbitmq-starter</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-direct-starter</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-bean-starter</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-jackson-starter</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-core</artifactId>
|
||||
@@ -144,6 +167,11 @@
|
||||
<artifactId>camel-activemq</artifactId>
|
||||
<version>${camel.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>artemis-jms-server</artifactId>
|
||||
<version>${artemis-jms-server.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.jopt-simple</groupId>
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
@@ -477,7 +505,21 @@
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>artemis-junit</artifactId>
|
||||
<version>${artemis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
@@ -620,6 +662,24 @@
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<!-- FIXME: 4.0 -->
|
||||
<!-- Netflix -->
|
||||
<!--<repository>
|
||||
<id>netflix-snapshots</id>
|
||||
<name>Netflix Snapshots</name>
|
||||
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-snapshots</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>-->
|
||||
<repository>
|
||||
<id>netflix-candidates</id>
|
||||
<name>Netflix Candidates</name>
|
||||
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -75,11 +75,6 @@
|
||||
<artifactId>jopt-simple</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<HttpServerStub> serverStubs = SpringFactoriesLoader.loadFactories(HttpServerStub.class, null);
|
||||
|
||||
@@ -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<HttpServerStub> 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<HttpServerStub> serverStubs) {
|
||||
this.portScanner = portScanner;
|
||||
this.contractVerifierMessaging = contractVerifierMessaging;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Message> contractVerifierMessaging(MessageVerifier<Message> 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<SimpleMessageListenerContainer> simpleMessageListenerContainers = emptyList();
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<Binding> bindings = emptyList();
|
||||
|
||||
@Autowired
|
||||
private RabbitProperties rabbitProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MessageVerifier<Message> contractVerifierMessageExchange() {
|
||||
return new SpringAmqpStubMessages(this.rabbitTemplate,
|
||||
new MessageListenerAccessor(this.rabbitListenerEndpointRegistry,
|
||||
this.simpleMessageListenerContainers, this.bindings),
|
||||
this.rabbitProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ContractVerifierHelper extends ContractVerifierMessaging<Message> {
|
||||
|
||||
private final MessageConverter messageConverter;
|
||||
|
||||
ContractVerifierHelper(MessageVerifier<Message> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<SimpleMessageListenerContainer> simpleMessageListenerContainers;
|
||||
|
||||
private final List<Binding> bindings;
|
||||
|
||||
MessageListenerAccessor(RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry,
|
||||
List<SimpleMessageListenerContainer> simpleMessageListenerContainers, List<Binding> bindings) {
|
||||
this.rabbitListenerEndpointRegistry = rabbitListenerEndpointRegistry;
|
||||
this.simpleMessageListenerContainers = simpleMessageListenerContainers;
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
List<SimpleMessageListenerContainer> getListenerContainersForDestination(String destination, String routingKey) {
|
||||
List<SimpleMessageListenerContainer> listenerContainers = collectListenerContainers();
|
||||
// we interpret the destination as exchange name and collect all the queues bound
|
||||
// to this exchange
|
||||
Set<String> queueNames = collectQueuesBoundToDestination(destination, routingKey);
|
||||
return getListenersByBoundQueues(listenerContainers, queueNames);
|
||||
}
|
||||
|
||||
private List<SimpleMessageListenerContainer> getListenersByBoundQueues(
|
||||
List<SimpleMessageListenerContainer> listenerContainers, Set<String> queueNames) {
|
||||
List<SimpleMessageListenerContainer> 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<String> collectQueuesBoundToDestination(String destination, String routingKey) {
|
||||
Set<String> 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<SimpleMessageListenerContainer> collectListenerContainers() {
|
||||
List<SimpleMessageListenerContainer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Message> {
|
||||
|
||||
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 <T> void send(T payload, Map<String, Object> 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<SimpleMessageListenerContainer> 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<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
ArgumentCaptor<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Message> contractVerifierMessaging(MessageVerifier<Message> exchange) {
|
||||
return new ContractVerifierCamelHelper(exchange);
|
||||
public ContractVerifierMessaging<Message> contractVerifierMessaging(MessageVerifierSender<Message> sender, MessageVerifierReceiver<Message> receiver) {
|
||||
return new ContractVerifierCamelHelper(sender, receiver);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ContractVerifierCamelHelper extends ContractVerifierMessaging<Message> {
|
||||
|
||||
ContractVerifierCamelHelper(MessageVerifier<Message> exchange) {
|
||||
super(exchange);
|
||||
ContractVerifierCamelHelper(MessageVerifierSender<Message> sender, MessageVerifierReceiver<Message> receiver) {
|
||||
super(sender, receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<Message<?>> contractVerifierMessaging(MessageVerifier<Message<?>> exchange) {
|
||||
return new ContractVerifierHelper(exchange);
|
||||
public ContractVerifierMessaging<Message<?>> contractVerifierMessaging(MessageVerifierSender<Message<?>> sender, MessageVerifierReceiver<Message<?>> receiver) {
|
||||
return new ContractVerifierHelper(sender, receiver);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ContractVerifierHelper extends ContractVerifierMessaging<Message<?>> {
|
||||
|
||||
ContractVerifierHelper(MessageVerifier<Message<?>> exchange) {
|
||||
super(exchange);
|
||||
ContractVerifierHelper(MessageVerifierSender<Message<?>> sender, MessageVerifierReceiver<Message<?>> receiver) {
|
||||
super(sender, receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<M> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ContractVerifierMessaging.class);
|
||||
|
||||
private final MessageVerifier<M> exchange;
|
||||
private final MessageVerifierSender<M> sender;
|
||||
|
||||
public ContractVerifierMessaging(MessageVerifier<M> exchange) {
|
||||
this.exchange = exchange;
|
||||
if (exchange != null) {
|
||||
log.info("The message verifier implementation is of type [" + exchange.getClass() + "]");
|
||||
private final MessageVerifierReceiver<M> receiver;
|
||||
|
||||
public ContractVerifierMessaging(MessageVerifierSender<M> sender, MessageVerifierReceiver<M> 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<M> {
|
||||
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<M> {
|
||||
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) {
|
||||
|
||||
@@ -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<Message> contractVerifierJmsMessaging(MessageVerifier<Message> exchange) {
|
||||
return new ContractVerifierJmsHelper(exchange);
|
||||
ContractVerifierMessaging<Message> contractVerifierJmsMessaging(MessageVerifierSender<Message> sender, MessageVerifierReceiver<Message> receiver) {
|
||||
return new ContractVerifierJmsHelper(sender, receiver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,8 +72,8 @@ class ContractVerifierJmsHelper extends ContractVerifierMessaging<Message> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ContractVerifierJmsHelper.class);
|
||||
|
||||
ContractVerifierJmsHelper(MessageVerifier<Message> exchange) {
|
||||
super(exchange);
|
||||
ContractVerifierJmsHelper(MessageVerifierSender<Message> sender, MessageVerifierReceiver<Message> receiver) {
|
||||
super(sender, receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<Message<?>> contractVerifierKafkaMessageExchange(Supplier<KafkaTemplate> kafkaTemplate,
|
||||
EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties, KafkaStubMessagesInitializer initializer) {
|
||||
return new KafkaStubMessages(kafkaTemplate.get(), broker, kafkaProperties, initializer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
Supplier<KafkaTemplate> 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<Message<?>> contractVerifierKafkaMessaging(MessageVerifier<Message<?>> exchange) {
|
||||
return new ContractVerifierKafkaHelper(exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ContractVerifierKafkaHelper extends ContractVerifierMessaging<Message<?>> {
|
||||
|
||||
ContractVerifierKafkaHelper(MessageVerifier<Message<?>> exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContractVerifierMessage convert(Message<?> message) {
|
||||
return new ContractVerifierMessage(message.getPayload(), convertHeaders(message.getHeaders()));
|
||||
}
|
||||
|
||||
private MessageHeaders convertHeaders(Map<String, Object> headers) {
|
||||
final Map<String, Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Consumer> initialize(EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties) {
|
||||
Map<String, Consumer> 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<String, Object> 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<String, String> consumerFactory = new DefaultKafkaConsumerFactory<>(
|
||||
consumerProperties);
|
||||
Consumer<String, String> consumer = consumerFactory.createConsumer();
|
||||
broker.consumeFromAnEmbeddedTopic(consumer, destination);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Prepared consumer for destination [" + destination + "]");
|
||||
}
|
||||
return consumer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Message<?>> {
|
||||
|
||||
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<String, Consumer> 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<String, Consumer> consumers;
|
||||
|
||||
Receiver(Map<String, Consumer> 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<String, Object> 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<String, Object> toMap(Headers headers) {
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Consumer> initialize(EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties);
|
||||
|
||||
}
|
||||
@@ -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<Object> contractVerifierMessaging(NoOpStubMessages messages) {
|
||||
return new ContractVerifierMessaging<>(messages, messages);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -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<Message<?>> exchange) {
|
||||
return new ContractVerifierHelper(exchange);
|
||||
public ContractVerifierMessaging<?> contractVerifierMessagingConverter(MessageVerifierSender<Message<?>> sender, MessageVerifierReceiver<Message<?>> receiver) {
|
||||
return new ContractVerifierHelper(sender, receiver);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -79,8 +81,8 @@ public class ContractVerifierStreamAutoConfiguration {
|
||||
|
||||
class ContractVerifierHelper extends ContractVerifierMessaging<Message<?>> {
|
||||
|
||||
ContractVerifierHelper(MessageVerifier<Message<?>> exchange) {
|
||||
super(exchange);
|
||||
ContractVerifierHelper(MessageVerifierSender<Message<?>> sender, MessageVerifierReceiver<Message<?>> receiver) {
|
||||
super(sender, receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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<SimpleMessageListenerContainer> 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<SimpleMessageListenerContainer> 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<SimpleMessageListenerContainer> 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<SimpleMessageListenerContainer> 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<SimpleMessageListenerContainer> 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<SimpleMessageListenerContainer> 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("#")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<KafkaTemplate> myKafkaTemplate() {
|
||||
return () -> myKafkaTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,18 +45,18 @@
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<!-- <module>samples-messaging-camel</module>-->
|
||||
<module>samples-messaging-camel</module>
|
||||
<module>samples-messaging-integration</module>
|
||||
<module>samples-messaging-amqp</module>
|
||||
<!-- <module>samples-messaging-jms</module>-->
|
||||
<!-- <module>spring-cloud-contract-stub-runner-camel</module>-->
|
||||
<!-- <module>spring-cloud-contract-stub-runner-boot-eureka</module>-->
|
||||
<module>samples-messaging-jms</module>
|
||||
<module>spring-cloud-contract-stub-runner-camel</module>
|
||||
<module>spring-cloud-contract-stub-runner-boot-eureka</module>
|
||||
<module>spring-cloud-contract-stub-runner-boot-zookeeper</module>
|
||||
<module>spring-cloud-contract-stub-runner-context-path</module>
|
||||
<module>spring-cloud-contract-stub-runner-integration</module>
|
||||
<module>spring-cloud-contract-stub-runner-stream</module>
|
||||
<module>spring-cloud-contract-stub-runner-amqp</module>
|
||||
<!-- <module>spring-cloud-contract-stub-runner-jms</module>-->
|
||||
<module>spring-cloud-contract-stub-runner-jms</module>
|
||||
<module>spring-cloud-contract-stub-runner-kafka</module>
|
||||
</modules>
|
||||
</profile>
|
||||
@@ -68,12 +68,12 @@
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<!-- <module>samples-messaging-camel</module>-->
|
||||
<module>samples-messaging-camel</module>
|
||||
<module>samples-messaging-integration</module>
|
||||
<module>samples-messaging-amqp</module>
|
||||
<!-- <module>samples-messaging-jms</module>-->
|
||||
<!-- <module>spring-cloud-contract-stub-runner-camel</module>-->
|
||||
<!-- <module>spring-cloud-contract-stub-runner-boot-eureka</module>-->
|
||||
<module>samples-messaging-jms</module>
|
||||
<module>spring-cloud-contract-stub-runner-camel</module>
|
||||
<module>spring-cloud-contract-stub-runner-boot-eureka</module>
|
||||
<module>spring-cloud-contract-stub-runner-boot-zookeeper</module>
|
||||
<module>spring-cloud-contract-stub-runner-context-path</module>
|
||||
<module>spring-cloud-contract-stub-runner-moco-contract-jar</module>
|
||||
@@ -81,7 +81,7 @@
|
||||
<module>spring-cloud-contract-stub-runner-integration</module>
|
||||
<module>spring-cloud-contract-stub-runner-stream</module>
|
||||
<module>spring-cloud-contract-stub-runner-amqp</module>
|
||||
<!-- <module>spring-cloud-contract-stub-runner-jms</module>-->
|
||||
<module>spring-cloud-contract-stub-runner-jms</module>
|
||||
<module>spring-cloud-contract-stub-runner-kafka</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,23 +16,23 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-spring-boot-starter</artifactId>
|
||||
<artifactId>camel-rabbitmq-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-jms</artifactId>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-direct-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-activemq</artifactId>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-bean-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>activemq-pool</artifactId>
|
||||
<groupId>org.apache.camel.springboot</groupId>
|
||||
<artifactId>camel-jackson-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-jackson</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
@@ -50,8 +50,18 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-junit4</artifactId>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -31,4 +31,9 @@ public class BookReturned implements Serializable {
|
||||
this.bookName = bookName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BookReturned{" + "bookName='" + bookName + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
<logger name="com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire" level="OFF"/>
|
||||
</configuration>
|
||||
@@ -16,7 +16,15 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||
<artifactId>spring-boot-starter-artemis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>artemis-jms-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
@@ -25,7 +33,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -33,6 +41,11 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@@ -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 + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<Message> messageVerifier
|
||||
@Inject
|
||||
ContractVerifierMessaging contractVerifierMessaging
|
||||
@Inject
|
||||
ContractVerifierObjectMapper contractVerifierObjectMapper
|
||||
// ALL CASES
|
||||
@Autowired
|
||||
JmsTemplate jmsTemplate
|
||||
@Autowired
|
||||
BookDeleter bookDeleter
|
||||
@Inject
|
||||
MessageVerifier<Message> 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()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,20 +13,11 @@
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Contract Stub Runner Boot Eureka</name>
|
||||
<description>Spring Cloud Contract Stub Runner Boot Eureka</description>
|
||||
<properties>
|
||||
<jersey-apache-client4.version>1.19.1</jersey-apache-client4.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-stub-runner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey.contribs</groupId>
|
||||
<artifactId>jersey-apache-client4</artifactId>
|
||||
<version>${jersey-apache-client4.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner-jetty</artifactId>
|
||||
@@ -42,25 +33,11 @@
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
@@ -76,6 +53,11 @@
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@@ -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> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> 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 {
|
||||
|
||||
@@ -31,25 +31,11 @@
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
@@ -60,11 +46,6 @@
|
||||
<artifactId>camel-activemq</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>activemq-pool</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
@@ -14,38 +14,24 @@
|
||||
<name>Spring Cloud Contract Stub Runner JMS</name>
|
||||
<description>Spring Cloud Contract Stub Runner JMS</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-artemis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
<artifactId>artemis-jms-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-stub-runner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner-jetty</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-activemq</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-spring</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ org.springframework.cloud.contract.spec.Contract.make {
|
||||
sentTo('output')
|
||||
body('''{ "bookName" : "foo" }''')
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
header('BOOKNAME', 'foo')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ org.springframework.cloud.contract.spec.Contract.make {
|
||||
bookName: 'foo'
|
||||
])
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
header('BOOKNAME', 'foo')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user