Brings back Eureka support, updates samples, removes mocked kafka & rabbit samples

This commit is contained in:
Marcin Grzejszczak
2022-11-15 12:20:56 +01:00
parent eeda962d5d
commit fa51d6a076
58 changed files with 611 additions and 2434 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
};
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")
}
}

View File

@@ -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("#")
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}