/* * The MIT License (MIT) * * Copyright (c) 2015, 2019 Patrick Reinhart * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package net.reini.rabbitmq.cdi; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.AMQP.BasicProperties.Builder; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; @ExtendWith(MockitoExtension.class) public class GenericPublisherTest { @Mock private ConnectionConfig config; @Mock private ConnectionRepository connectionRepository; @Mock private Connection connection; @Mock private Channel channel; @Mock private Encoder<TestEvent> encoder; @Mock private BiConsumer<TestEvent, PublishException> errorHandler; private List<ExchangeDeclaration> declarations = new ArrayList<>(); private GenericPublisher<TestEvent> publisher; private TestEvent event; private Function<TestEvent, String> routingKeyFunction; @BeforeEach public void setUp() throws Exception { publisher = new GenericPublisher<TestEvent>(connectionRepository) { @Override protected void sleepBeforeRetry() { // no delay } }; event = new TestEvent(); event.id = "theId"; event.booleanValue = true; routingKeyFunction = e -> "routingKey"; } @Test public void testPublish() throws Exception { Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, new JsonEncoder<>(), errorHandler, declarations); ArgumentCaptor<BasicProperties> propsCaptor = ArgumentCaptor.forClass(BasicProperties.class); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); publisher.publish(event, publisherConfiguration); verify(channel).basicPublish(eq("exchange"), eq("routingKey"), propsCaptor.capture(), eq("{\"id\":\"theId\",\"booleanValue\":true}".getBytes())); assertEquals("application/json", propsCaptor.getValue().getContentType()); } @Test public void testPublish_with_error() throws Exception { Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, new JsonEncoder<>(), errorHandler, declarations); ArgumentCaptor<BasicProperties> propsCaptor = ArgumentCaptor.forClass(BasicProperties.class); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); doThrow(new IOException("someError")).when(channel).basicPublish(eq("exchange"), eq("routingKey"), propsCaptor.capture(), eq("{\"id\":\"theId\",\"booleanValue\":true}".getBytes())); Throwable exception = assertThrows(PublishException.class, () -> { publisher.publish(event, publisherConfiguration); }); assertEquals("Unable to send message after 3 attempts", exception.getMessage()); assertEquals("application/json", propsCaptor.getValue().getContentType()); } @Test public void testPublish_withEncodeException() throws Exception { Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, encoder, errorHandler, declarations); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); doThrow(new EncodeException(new RuntimeException("someError"))).when(encoder).encode(event); Throwable exception = assertThrows(PublishException.class, () -> { publisher.publish(event, publisherConfiguration); }); assertEquals("Unable to serialize event", exception.getMessage()); } @Test public void testPublish_withTooManyAttempts() throws Exception { publisher = new GenericPublisher<TestEvent>(connectionRepository) { @Override protected void handleIoException(int attempt, Throwable cause) throws PublishException { // do not throw to allow attempts to overrun DEFAULT_RETRY_ATTEMPTS } @Override protected void sleepBeforeRetry() { // no delay } }; Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, new JsonEncoder<>(), errorHandler, declarations); ArgumentCaptor<BasicProperties> propsCaptor = ArgumentCaptor.forClass(BasicProperties.class); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); doThrow(new IOException("someError")).when(channel).basicPublish(eq("exchange"), eq("routingKey"), propsCaptor.capture(), eq("{\"id\":\"theId\",\"booleanValue\":true}".getBytes())); publisher.publish(event, publisherConfiguration); assertEquals("application/json", propsCaptor.getValue().getContentType()); } @Test public void testPublish_with_FatalError() throws Exception { Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, new JsonEncoder<>(), errorHandler, declarations); ArgumentCaptor<BasicProperties> propsCaptor = ArgumentCaptor.forClass(BasicProperties.class); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); doThrow(new IOException("someError")).when(channel).basicPublish(eq("exchange"), eq("routingKey"), propsCaptor.capture(), eq("{\"id\":\"theId\",\"booleanValue\":true}".getBytes())); Throwable exception = assertThrows(PublishException.class, () -> { publisher.publish(event, publisherConfiguration); }); assertEquals("Unable to send message after 3 attempts", exception.getMessage()); assertEquals("application/json", propsCaptor.getValue().getContentType()); } @Test public void testPublish_with_custom_MessageConverter() throws Exception { Builder builder = new Builder(); PublisherConfiguration<TestEvent> publisherConfiguration = new PublisherConfiguration(config, "exchange", routingKeyFunction, builder, new CustomEncoder(), errorHandler, declarations); ArgumentCaptor<BasicProperties> propsCaptor = ArgumentCaptor.forClass(BasicProperties.class); when(connectionRepository.getConnection(config)).thenReturn(connection); when(connection.createChannel()).thenReturn(channel); publisher.publish(event, publisherConfiguration); verify(channel).basicPublish(eq("exchange"), eq("routingKey"), propsCaptor.capture(), eq("Id: theId, BooleanValue: true".getBytes())); assertEquals("text/plain", propsCaptor.getValue().getContentType()); } @Test public void testClose() { publisher.close(); } @Test public void testHandleIoException_channel_null() throws PublishException { publisher.handleIoException(1, null); } @Test public void testSleepBeforeRetry_real_wait() { publisher = new GenericPublisher<>(connectionRepository); publisher.sleepBeforeRetry(); } @Test public void testSleepBeforeRetry_InterruptedException() throws InterruptedException { publisher = new GenericPublisher<>(connectionRepository); call_SleepBeforeRetry_InAnotherThread_AndInterrupt(); } public static class CustomEncoder implements Encoder<TestEvent> { @Override public String contentType() { return "text/plain"; } @Override @SuppressWarnings("boxing") public byte[] encode(TestEvent event) throws EncodeException { final String str = MessageFormat.format("Id: {0}, BooleanValue: {1}", event.getId(), event.isBooleanValue()); return str.getBytes(); } } private void call_SleepBeforeRetry_InAnotherThread_AndInterrupt() throws InterruptedException { Thread sleeper = new Thread("sleeper") { @Override public void run() { publisher.sleepBeforeRetry(); } }; sleeper.start(); Thread.sleep(GenericPublisher.DEFAULT_RETRY_INTERVAL / 4); sleeper.interrupt(); } }