Add the rabbitmq module
This commit is contained in:
100
rabbitmq/src/test/java/com/ankurm/rabbit/TopologyTrapsTest.java
Normal file
100
rabbitmq/src/test/java/com/ankurm/rabbit/TopologyTrapsTest.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.ankurm.rabbit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.amqp.AmqpIOException;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.QueueBuilder;
|
||||
import org.springframework.amqp.core.ReturnedMessage;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Two ways a RabbitMQ topology loses your work quietly.
|
||||
*
|
||||
* @see <a href="../../../../../docs/03-the-silent-drop.md">docs/03-the-silent-drop.md</a>
|
||||
* @see <a href="../../../../../docs/06-changing-your-mind.md">docs/06-changing-your-mind.md</a>
|
||||
*/
|
||||
@SpringBootTest
|
||||
class TopologyTrapsTest {
|
||||
|
||||
@Autowired
|
||||
RabbitTemplate template;
|
||||
|
||||
@Autowired
|
||||
RabbitAdmin admin;
|
||||
|
||||
@Test
|
||||
void anUnroutableMessageIsDiscardedUnlessYouAskForItBack() throws Exception {
|
||||
List<ReturnedMessage> returned = new CopyOnWriteArrayList<>();
|
||||
this.template.setReturnsCallback(returned::add);
|
||||
|
||||
// "amend" matches no binding on orders.direct. The broker has nowhere to put it.
|
||||
this.template.convertAndSend(Topology.DIRECT, "amend", OrderMessage.of("o-1"));
|
||||
for (int i = 0; i < 100 && returned.isEmpty(); i++) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
// spring.rabbitmq.template.mandatory=true is what turns a silent discard into a return.
|
||||
assertThat(this.template.isMandatoryFor(null)).isTrue();
|
||||
assertThat(returned).hasSize(1);
|
||||
ReturnedMessage message = returned.get(0);
|
||||
System.out.println("=== returned message ===");
|
||||
System.out.println(" replyCode " + message.getReplyCode());
|
||||
System.out.println(" replyText " + message.getReplyText());
|
||||
System.out.println(" exchange " + message.getExchange());
|
||||
System.out.println(" routingKey " + message.getRoutingKey());
|
||||
|
||||
assertThat(message.getReplyCode()).isEqualTo(312);
|
||||
assertThat(message.getReplyText()).isEqualTo("NO_ROUTE");
|
||||
assertThat(message.getRoutingKey()).isEqualTo("amend");
|
||||
// Note what did NOT happen: convertAndSend returned normally. Publishing is fire and
|
||||
// forget at the protocol level, so even with mandatory=true the failure arrives
|
||||
// asynchronously on another thread. Nothing throws.
|
||||
}
|
||||
|
||||
@Test
|
||||
void redeclaringAQueueWithDifferentArgumentsIsAPreconditionFailure() {
|
||||
// orders.ttl already exists with x-message-ttl=1500. Same name, different argument.
|
||||
Queue conflicting = QueueBuilder.durable(Topology.Q_TTL)
|
||||
.ttl(9999)
|
||||
.deadLetterExchange(Topology.DLX)
|
||||
.deadLetterRoutingKey("failed")
|
||||
.build();
|
||||
|
||||
assertThatExceptionOfType(AmqpIOException.class)
|
||||
.isThrownBy(() -> this.admin.declareQueue(conflicting))
|
||||
.satisfies((ex) -> {
|
||||
String detail = rootMessage(ex);
|
||||
System.out.println("=== redeclaring orders.ttl with x-message-ttl=9999 ===");
|
||||
System.out.println(" " + detail);
|
||||
assertThat(detail).contains("PRECONDITION_FAILED")
|
||||
.contains("inequivalent arg 'x-message-ttl'");
|
||||
});
|
||||
|
||||
// Queue arguments are immutable. There is no ALTER QUEUE. Changing a TTL, a max-length
|
||||
// or a dead-letter exchange on an existing queue means: declare a new queue, move the
|
||||
// consumers, drain the old one, delete it. Plan the rename into the change.
|
||||
}
|
||||
|
||||
private static String rootMessage(Throwable throwable) {
|
||||
// The useful text is on the cause. AmqpIOException's own message is just
|
||||
// "java.io.IOException", which is why this failure is so often reported as "IOException"
|
||||
// with no further detail.
|
||||
Throwable current = throwable;
|
||||
StringBuilder all = new StringBuilder();
|
||||
while (current != null) {
|
||||
all.append(current.getMessage()).append(" | ");
|
||||
current = current.getCause();
|
||||
}
|
||||
return all.toString();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user