Brings back Eureka support, updates samples, removes mocked kafka & rabbit samples
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user