diff --git a/notification-service/src/main/java/com/ecommerce/notification_service/consumer/DeadLetterConsumer.java b/notification-service/src/main/java/com/ecommerce/notification_service/consumer/DeadLetterConsumer.java index c900f32..0053646 100644 --- a/notification-service/src/main/java/com/ecommerce/notification_service/consumer/DeadLetterConsumer.java +++ b/notification-service/src/main/java/com/ecommerce/notification_service/consumer/DeadLetterConsumer.java @@ -14,7 +14,7 @@ public class DeadLetterConsumer { * Consumes messages rejected by the main listener after all retry attempts. * Logs a structured ERROR for Loki/Grafana alerting and acks the message. */ - @RabbitListener(queues = RabbitMQConfig.DLQ) + @RabbitListener(id = "dlqListener", queues = RabbitMQConfig.DLQ) public void consumeDeadLetter(OrderPlacedEvent event) { log.error("DEAD_LETTER_RECEIVED: orderReference={}, email={}", event.orderReference(), event.email()); diff --git a/notification-service/src/test/java/com/ecommerce/notification_service/DlqIntegrationTest.java b/notification-service/src/test/java/com/ecommerce/notification_service/DlqIntegrationTest.java index afc7c2c..32c8cf5 100644 --- a/notification-service/src/test/java/com/ecommerce/notification_service/DlqIntegrationTest.java +++ b/notification-service/src/test/java/com/ecommerce/notification_service/DlqIntegrationTest.java @@ -4,9 +4,11 @@ import com.ecommerce.notification_service.event.OrderPlacedEvent; import jakarta.mail.Session; import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.MailSendException; @@ -33,8 +35,8 @@ * - RabbitMQ → Testcontainers (real broker; DLX/DLQ declared by Spring context) * - JavaMailSender → @MockitoBean (throws MailSendException on every send) * - * Retry intervals are overridden via @DynamicPropertySource to keep the test fast - * (2 attempts × ~100 ms intervals instead of the production 3 × 10 s). + * The DeadLetterConsumer listener is stopped before the test so messages remain + * in the DLQ and can be polled directly via rabbitTemplate.receive(). */ @SpringBootTest @Testcontainers @@ -56,8 +58,14 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.mail.host", () -> "127.0.0.1"); registry.add("spring.mail.port", () -> "25"); + // @MockitoBean replaces JavaMailSenderImpl with a mock; the mail health + // contributor requires a concrete implementation, so disable it here. + registry.add("management.health.mail.enabled", () -> "false"); - // Fast retries so the test doesn't wait ~19 s (production: 3 attempts × 10 s) + // Retry is disabled by default in the test classpath application.yaml (which replaces + // the production yaml). Explicitly enable it and use fast intervals so the test + // exhausts retries quickly instead of waiting ~19 s (production: 3 attempts × 10 s). + registry.add("spring.rabbitmq.listener.simple.retry.enabled", () -> "true"); registry.add("spring.rabbitmq.listener.simple.retry.max-attempts", () -> "2"); registry.add("spring.rabbitmq.listener.simple.retry.initial-interval", () -> "100ms"); registry.add("spring.rabbitmq.listener.simple.retry.max-interval", () -> "200ms"); @@ -67,6 +75,18 @@ static void configureProperties(DynamicPropertyRegistry registry) { @Autowired private RabbitTemplate rabbitTemplate; + @Autowired + private RabbitListenerEndpointRegistry listenerRegistry; + + @BeforeEach + void stopDlqConsumer() { + // Stop the DeadLetterConsumer listener so messages stay in the DLQ + // and can be polled directly. Spring AMQP captures the original bean + // reference before any spy/mock wrapper, making spy-based verification + // unreliable — polling a paused queue is the reliable alternative. + listenerRegistry.getListenerContainer("dlqListener").stop(); + } + @Test void publishOrderEvent_emailAlwaysFails_messageDeadLetters() { // Arrange – simulate SMTP rejection after message construction succeeds @@ -84,7 +104,7 @@ void publishOrderEvent_emailAlwaysFails_messageDeadLetters() { event); // Assert – wait up to 15 s for the dead-lettered message to appear in the DLQ. - // rabbitTemplate.receive() does a non-blocking peek with a 1-second timeout. + // The DLQ consumer is stopped so the message stays until we receive it. await() .atMost(15, TimeUnit.SECONDS) .pollInterval(500, TimeUnit.MILLISECONDS)