/*
* Copyright 2017 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
*
* http://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.amqp.rabbit.listener;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.amqp.rabbit.connection.ChannelProxy;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.impl.recovery.AutorecoveringChannel;
/**
* @author Gary Russell
* @since 2.0
*
*/
public class DirectMessageListenerContainerMockTests {
@Test
public void testAlwaysCancelAutoRecoverConsumer() throws Exception {
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
Connection connection = mock(Connection.class);
ChannelProxy channel = mock(ChannelProxy.class);
Channel rabbitChannel = mock(AutorecoveringChannel.class);
given(channel.getTargetChannel()).willReturn(rabbitChannel);
given(connectionFactory.createConnection()).willReturn(connection);
given(connection.createChannel(anyBoolean())).willReturn(channel);
final AtomicBoolean isOpen = new AtomicBoolean(true);
willAnswer(i -> isOpen.get()).given(channel).isOpen();
given(channel.queueDeclarePassive(Mockito.anyString()))
.willAnswer(invocation -> mock(AMQP.Queue.DeclareOk.class));
given(channel.basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(),
anyMap(), any(Consumer.class))).willReturn("consumerTag");
final CountDownLatch latch1 = new CountDownLatch(1);
final AtomicInteger qos = new AtomicInteger();
willAnswer(i -> {
qos.set(i.getArgument(0));
latch1.countDown();
return null;
}).given(channel).basicQos(anyInt());
final CountDownLatch latch2 = new CountDownLatch(1);
willAnswer(i -> {
latch2.countDown();
return null;
}).given(channel).basicCancel("consumerTag");
DirectMessageListenerContainer container = new DirectMessageListenerContainer(connectionFactory);
container.setQueueNames("test");
container.setPrefetchCount(2);
container.setMonitorInterval(100);
container.afterPropertiesSet();
container.start();
assertTrue(latch1.await(10, TimeUnit.SECONDS));
assertThat(qos.get(), equalTo(2));
isOpen.set(false);
assertTrue(latch2.await(10, TimeUnit.SECONDS));
container.stop();
}
@Test
public void testDeferredAcks() throws Exception {
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
Connection connection = mock(Connection.class);
ChannelProxy channel = mock(ChannelProxy.class);
Channel rabbitChannel = mock(AutorecoveringChannel.class);
given(channel.getTargetChannel()).willReturn(rabbitChannel);
given(connectionFactory.createConnection()).willReturn(connection);
given(connection.createChannel(anyBoolean())).willReturn(channel);
given(channel.isOpen()).willReturn(true);
given(channel.queueDeclarePassive(Mockito.anyString()))
.willAnswer(invocation -> mock(AMQP.Queue.DeclareOk.class));
final AtomicReference<Consumer> consumer = new AtomicReference<>();
final CountDownLatch latch1 = new CountDownLatch(1);
willAnswer(i -> {
consumer.set(i.getArgument(6));
consumer.get().handleConsumeOk("consumerTag");
latch1.countDown();
return "consumerTag";
})
.given(channel).basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(),
anyMap(), any(Consumer.class));
final AtomicInteger qos = new AtomicInteger();
willAnswer(i -> {
qos.set(i.getArgument(0));
return null;
}).given(channel).basicQos(anyInt());
final CountDownLatch latch2 = new CountDownLatch(2);
final CountDownLatch latch3 = new CountDownLatch(1);
willAnswer(i -> {
if (i.getArgument(0).equals(10L) || i.getArgument(0).equals(16L)) {
latch2.countDown();
}
else if (i.getArgument(0).equals(17L)) {
latch3.countDown();
}
return null;
}).given(channel).basicAck(anyLong(), anyBoolean());
final CountDownLatch latch4 = new CountDownLatch(1);
willAnswer(i -> {
latch4.countDown();
return null;
}).given(channel).basicNack(19L, true, true);
DirectMessageListenerContainer container = new DirectMessageListenerContainer(connectionFactory);
container.setQueueNames("test");
container.setPrefetchCount(2);
container.setMonitorInterval(100);
container.setMessagesPerAck(10);
container.setAckTimeout(100);
container.setMessageListener(m -> {
if (m.getMessageProperties().getDeliveryTag() == 19L) {
throw new RuntimeException("TestNackAndPendingAcks");
}
});
container.afterPropertiesSet();
container.start();
assertTrue(latch1.await(10, TimeUnit.SECONDS));
assertThat(qos.get(), equalTo(10));
BasicProperties props = new BasicProperties();
byte[] body = new byte[1];
for (long i = 1; i < 16; i++) {
consumer.get().handleDelivery("consumerTag", envelope(i), props, body);
}
Thread.sleep(200);
consumer.get().handleDelivery("consumerTag", envelope(16), props, body);
// should get 2 acks #10 and #6 (timeout)
assertTrue(latch2.await(10, TimeUnit.SECONDS));
consumer.get().handleDelivery("consumerTag", envelope(17), props, body);
verify(channel).basicAck(10L, true);
verify(channel).basicAck(16L, true);
assertTrue(latch3.await(10, TimeUnit.SECONDS));
// monitor task timeout
verify(channel).basicAck(17L, true);
consumer.get().handleDelivery("consumerTag", envelope(18), props, body);
consumer.get().handleDelivery("consumerTag", envelope(19), props, body);
assertTrue(latch4.await(10, TimeUnit.SECONDS));
// pending acks before nack
verify(channel).basicAck(18L, true);
verify(channel).basicNack(19L, true, true);
consumer.get().handleDelivery("consumerTag", envelope(20), props, body);
final CountDownLatch latch5 = new CountDownLatch(1);
willAnswer(i -> {
consumer.get().handleCancelOk("consumerTag");
latch5.countDown();
return null;
}).given(channel).basicCancel("consumerTag");
Executors.newSingleThreadExecutor().execute(container::stop);
assertTrue(latch5.await(10, TimeUnit.SECONDS));
// pending acks on stop
verify(channel).basicAck(20L, true);
}
private Envelope envelope(long tag) {
return new Envelope(tag, false, "", "");
}
}