diff --git a/build.gradle b/build.gradle index c3d6cf487a..b052a841ac 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,7 @@ ext { derbyVersion = '10.14.2.0' ftpServerVersion = '1.1.1' googleJsr305Version = '3.0.2' + greenmailVersion = '1.6.4' groovyVersion = '3.0.8' hamcrestVersion = '2.2' hazelcastVersion = '4.2' @@ -641,9 +642,9 @@ project('spring-integration-mail') { api project(':spring-integration-core') api 'org.springframework:spring-context-support' - providedImplementation "jakarta.mail:jakarta.mail-api:$mailVersion" - providedImplementation "com.sun.mail:imap:$mailVersion" providedImplementation "com.sun.mail:jakarta.mail:$mailVersion" + testImplementation "com.icegreen:greenmail:$greenmailVersion" + testRuntimeOnly 'org.apache.logging.log4j:log4j-jul' } } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java index 25156779f7..e0abe2ad78 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java @@ -31,12 +31,18 @@ import static org.mockito.Mockito.verify; import java.io.IOException; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; import javax.mail.Flags; import javax.mail.Flags.Flag; @@ -72,8 +78,6 @@ import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.mail.support.DefaultMailHeaderMapper; -import org.springframework.integration.test.mail.TestMailServer; -import org.springframework.integration.test.mail.TestMailServer.ImapServer; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.PollableChannel; @@ -84,6 +88,11 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.util.MimeTypeUtils; +import com.icegreen.greenmail.user.GreenMailUser; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.GreenMailUtil; +import com.icegreen.greenmail.util.ServerSetup; +import com.icegreen.greenmail.util.ServerSetupTest; import com.sun.mail.imap.IMAPFolder; /** @@ -100,31 +109,43 @@ public class ImapMailReceiverTests { private AtomicInteger failed; - private ImapServer imapIdleServer; + private GreenMail imapIdleServer; + + private GreenMailUser user; @Autowired private ApplicationContext context; + static { + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + } + + private final ImapSearchLoggingHandler imapSearches = new ImapSearchLoggingHandler(); + @BeforeEach - public void setup() throws InterruptedException { + public void setup() { + LogManager.getLogManager().getLogger("").setLevel(Level.ALL); + imapSearches.searches.clear(); + imapSearches.stores.clear(); + LogManager.getLogManager().getLogger("").addHandler(imapSearches); failed = new AtomicInteger(0); - this.imapIdleServer = TestMailServer.imap(0); - int n = 0; - while (n++ < 100 && (!this.imapIdleServer.isListening())) { - Thread.sleep(100); - } - assertThat(n < 100).isTrue(); + ServerSetup imap = ServerSetupTest.IMAP.dynamicPort(); + imap.setServerStartupTimeout(10000); + imapIdleServer = new GreenMail(imap); + user = imapIdleServer.setUser("user", "pw"); + imapIdleServer.start(); } @AfterEach public void tearDown() { this.imapIdleServer.stop(); + LogManager.getLogManager().getLogger("").removeHandler(imapSearches); } @Test public void testIdleWithServerCustomSearch() throws Exception { ImapMailReceiver receiver = - new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getPort() + "/INBOX"); + new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getImap().getPort() + "/INBOX"); receiver.setSearchTermStrategy((supportedFlags, folder) -> { try { FromTerm fromTerm = new FromTerm(new InternetAddress("bar@baz")); @@ -140,15 +161,15 @@ public class ImapMailReceiverTests { @Test public void testIdleWithServerDefaultSearch() throws Exception { ImapMailReceiver receiver = - new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getPort() + "/INBOX"); + new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getImap().getPort() + "/INBOX"); testIdleWithServerGuts(receiver, false); - assertThat(this.imapIdleServer.assertReceived("searchWithUserFlag")).isTrue(); + assertThat(imapSearches.searches.get(0)).contains("testSIUserFlag"); } @Test public void testIdleWithMessageMapping() throws Exception { ImapMailReceiver receiver = - new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getPort() + "/INBOX"); + new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getImap().getPort() + "/INBOX"); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); testIdleWithServerGuts(receiver, true); } @@ -156,16 +177,16 @@ public class ImapMailReceiverTests { @Test public void testIdleWithServerDefaultSearchSimple() throws Exception { ImapMailReceiver receiver = - new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getPort() + "/INBOX"); + new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getImap().getPort() + "/INBOX"); receiver.setSimpleContent(true); testIdleWithServerGuts(receiver, false, true); - assertThat(this.imapIdleServer.assertReceived("searchWithUserFlag")).isTrue(); + assertThat(imapSearches.searches.get(0)).contains("testSIUserFlag"); } @Test public void testIdleWithMessageMappingSimple() throws Exception { ImapMailReceiver receiver = - new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getPort() + "/INBOX"); + new ImapMailReceiver("imap://user:pw@localhost:" + this.imapIdleServer.getImap().getPort() + "/INBOX"); receiver.setSimpleContent(true); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); testIdleWithServerGuts(receiver, true, true); @@ -176,10 +197,6 @@ public class ImapMailReceiverTests { } public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped, boolean simple) throws Exception { - Properties mailProps = new Properties(); - mailProps.put("mail.debug", "true"); - mailProps.put("mail.imap.connectionpool.debug", "true"); - receiver.setJavaMailProperties(mailProps); receiver.setMaxFetchSize(1); receiver.setShouldDeleteMessages(false); receiver.setShouldMarkMessagesAsRead(true); @@ -193,6 +210,14 @@ public class ImapMailReceiverTests { adapter.setTaskScheduler(taskScheduler); adapter.setReconnectDelay(1); adapter.start(); + MimeMessage message = + GreenMailUtil.createTextEmail("Foo ", "Bar ", "Test Email", "foo\r\n", + imapIdleServer.getImap().getServerSetup()); + message.setRecipients(Message.RecipientType.CC, "a@b, c@d"); + message.setRecipients(Message.RecipientType.BCC, "e@f, g@h"); + user.deliver(message); + user.deliver(GreenMailUtil.createTextEmail("to", "Bar ", "subject", "body", + imapIdleServer.getImap().getServerSetup())); if (!mapped) { @SuppressWarnings("unchecked") org.springframework.messaging.Message received = @@ -202,11 +227,11 @@ public class ImapMailReceiverTests { assertThat(received.getPayload().getLineCount() > -1).isTrue(); if (simple) { assertThat(received.getPayload().getContent()) - .isEqualTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n"); + .isEqualTo("foo\r\n"); } else { assertThat(received.getPayload().getContent()) - .isEqualTo(TestMailServer.MailServer.MailHandler.MESSAGE + "\r\n"); + .isEqualTo("foo"); } } else { @@ -214,7 +239,7 @@ public class ImapMailReceiverTests { assertThat(received).isNotNull(); MessageHeaders headers = received.getHeaders(); assertThat(headers.get(MailHeaders.RAW_HEADERS)).isNotNull(); - assertThat(headers.get(MailHeaders.CONTENT_TYPE)).isEqualTo("TEXT/PLAIN; charset=ISO-8859-1"); + assertThat(headers.get(MailHeaders.CONTENT_TYPE)).isEqualTo("TEXT/PLAIN; charset=us-ascii"); assertThat(headers.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar "); String[] toHeader = headers.get(MailHeaders.TO, String[].class); @@ -224,10 +249,10 @@ public class ImapMailReceiverTests { assertThat(Arrays.toString(headers.get(MailHeaders.BCC, String[].class))).isEqualTo("[e@f, g@h]"); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); if (simple) { - assertThat(received.getPayload()).isEqualTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n"); + assertThat(received.getPayload()).isEqualTo("foo\r\n"); } else { - assertThat(received.getPayload()).isEqualTo(TestMailServer.MailServer.MailHandler.MESSAGE + "\r\n"); + assertThat(received.getPayload()).isEqualTo("foo"); } } assertThat(channel.receive(20000)).isNotNull(); // new message after idle @@ -235,11 +260,13 @@ public class ImapMailReceiverTests { adapter.stop(); taskScheduler.shutdown(); - assertThat(this.imapIdleServer.assertReceived("storeUserFlag")).isTrue(); + assertThat(imapSearches.stores.get(0)).contains("testSIUserFlag"); } @Test public void receiveAndMarkAsReadDontDelete() throws Exception { + user.deliver(GreenMailUtil.createTextEmail("user", "sender", "subject", "body", + imapIdleServer.getImap().getServerSetup())); AbstractMailReceiver receiver = new ImapMailReceiver(); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); @@ -898,4 +925,40 @@ public class ImapMailReceiverTests { mailReceiver.setBeanFactory(bf); } + + private static class ImapSearchLoggingHandler extends Handler { + + private final List searches = new ArrayList<>(); + + private final List stores = new ArrayList<>(); + + private static final String SEARCH = " SEARCH "; + + private static final String STORE = " STORE "; + + @Override + public void publish(LogRecord record) { + if ("com.sun.mail.imap.protocol".equals(record.getLoggerName())) { + String message = record.getMessage(); + if (!message.startsWith("*")) { + if (message.contains(SEARCH) && !message.contains(" OK ")) { + searches.add(message.substring(message.indexOf(SEARCH) + SEARCH.length())); + } + else if (message.contains(STORE) && !message.contains(" OK ")) { + stores.add(message.substring(message.indexOf(STORE) + STORE.length())); + } + } + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + + } + } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/Pop3Tests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/Pop3Tests.java index da91d9bbfd..bc68ee17bb 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/Pop3Tests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/Pop3Tests.java @@ -20,6 +20,10 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; +import javax.mail.Message.RecipientType; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -28,29 +32,36 @@ import org.springframework.integration.mail.MailHeaders; import org.springframework.integration.mail.MailReceivingMessageSource; import org.springframework.integration.mail.Pop3MailReceiver; import org.springframework.integration.mail.support.DefaultMailHeaderMapper; -import org.springframework.integration.test.mail.TestMailServer; -import org.springframework.integration.test.mail.TestMailServer.Pop3Server; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; +import com.icegreen.greenmail.user.GreenMailUser; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.GreenMailUtil; +import com.icegreen.greenmail.util.ServerSetup; +import com.icegreen.greenmail.util.ServerSetupTest; + /** * @author Gary Russell * @author Artem Bilan + * @author Alexander Pinske * * @since 5.0 * */ public class Pop3Tests { - private static final Pop3Server pop3Server = TestMailServer.pop3(0); + private static GreenMail pop3Server; + + private static GreenMailUser user; @BeforeAll - public static void setup() throws InterruptedException { - int n = 0; - while (n++ < 100 && (!pop3Server.isListening())) { - Thread.sleep(100); - } - assertThat(n < 100).isTrue(); + public static void setup() { + ServerSetup pop3 = ServerSetupTest.POP3.dynamicPort(); + pop3.setServerStartupTimeout(10000); + pop3Server = new GreenMail(pop3); + user = pop3Server.setUser("user", "pw"); + pop3Server.start(); } @AfterAll @@ -59,8 +70,15 @@ public class Pop3Tests { } @Test - public void testPop3() { - Pop3MailReceiver receiver = new Pop3MailReceiver("localhost", pop3Server.getPort(), "user", "pw"); + public void testPop3() throws MessagingException { + MimeMessage mimeMessage = + GreenMailUtil.createTextEmail("Foo ", "Bar , Bar2 ", "Test Email", + "foo\r\n", pop3Server.getPop3().getServerSetup()); + mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); + mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); + user.deliver(mimeMessage); + + Pop3MailReceiver receiver = new Pop3MailReceiver("localhost", pop3Server.getPop3().getPort(), "user", "pw"); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); MailReceivingMessageSource source = new MailReceivingMessageSource(receiver); Message message = source.receive(); @@ -71,7 +89,7 @@ public class Pop3Tests { assertThat(Arrays.toString(headers.get(MailHeaders.BCC, String[].class))).isEqualTo("[e@f, g@h]"); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar ,Bar2 "); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); - assertThat(message.getPayload()).isEqualTo("foo\r\n\r\n"); + assertThat(message.getPayload()).isEqualTo("foo\r\n"); } } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/SmtpTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/SmtpTests.java index bc9c5f49ba..e1a46ffb1d 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/SmtpTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/SmtpTests.java @@ -18,6 +18,10 @@ package org.springframework.integration.mail.config; import static org.assertj.core.api.Assertions.assertThat; +import javax.mail.Message.RecipientType; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -25,28 +29,31 @@ import org.junit.jupiter.api.Test; import org.springframework.integration.mail.MailHeaders; import org.springframework.integration.mail.MailSendingMessageHandler; import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.mail.TestMailServer; -import org.springframework.integration.test.mail.TestMailServer.SmtpServer; import org.springframework.mail.javamail.JavaMailSenderImpl; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; +import com.icegreen.greenmail.util.ServerSetupTest; + /** * @author Gary Russell * @author Artem Bilan + * @author Alexander Pinske * * @since 5.0 * */ public class SmtpTests { - private static final SmtpServer smtpServer = TestMailServer.smtp(0); + private static GreenMail smtpServer; @BeforeAll - public static void setup() throws InterruptedException { - int n = 0; - while (n++ < 100 && (!smtpServer.isListening())) { - Thread.sleep(100); - } - assertThat(n < 100).isTrue(); + public static void setup() { + ServerSetup smtp = ServerSetupTest.SMTP.dynamicPort(); + smtp.setServerStartupTimeout(10000); + smtpServer = new GreenMail(smtp); + smtpServer.setUser("user", "pw"); + smtpServer.start(); } @AfterAll @@ -58,7 +65,7 @@ public class SmtpTests { public void testSmtp() throws Exception { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); mailSender.setHost("localhost"); - mailSender.setPort(smtpServer.getPort()); + mailSender.setPort(smtpServer.getSmtp().getPort()); mailSender.setUsername("user"); mailSender.setPassword("pw"); MailSendingMessageHandler handler = new MailSendingMessageHandler(mailSender); @@ -69,19 +76,14 @@ public class SmtpTests { .setHeader(MailHeaders.SUBJECT, "foo") .build()); - int n = 0; - while (n++ < 100 && smtpServer.getMessages().size() == 0) { - Thread.sleep(100); - } + assertThat(smtpServer.waitForIncomingEmail(10000, 1)).isTrue(); - assertThat(smtpServer.getMessages().size() > 0).isTrue(); - String message = smtpServer.getMessages().get(0); - assertThat(message) - .endsWith("foo\n") - .contains("foo@bar") - .contains("bar@baz") - .contains("user:user") - .contains("password:pw"); + assertThat(smtpServer.getReceivedMessages().length > 0).isTrue(); + MimeMessage message = smtpServer.getReceivedMessages()[0]; + assertThat(message.getFrom()).containsOnly(new InternetAddress("foo@bar")); + assertThat(message.getRecipients(RecipientType.TO)).containsOnly(new InternetAddress("bar@baz")); + assertThat(message.getSubject()).isEqualTo("foo"); + assertThat(message.getContent()).asString().isEqualTo("foo\r\n"); } } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java index 696c1df789..409fc02276 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/dsl/MailTests.java @@ -19,7 +19,6 @@ package org.springframework.integration.mail.dsl; import static org.assertj.core.api.Assertions.assertThat; import java.io.Closeable; -import java.io.IOException; import java.util.Properties; import javax.mail.Flags; @@ -33,12 +32,12 @@ import javax.mail.search.FlagTerm; import javax.mail.search.FromTerm; import javax.mail.search.SearchTerm; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.IntegrationMessageHeaderAccessor; @@ -51,10 +50,6 @@ import org.springframework.integration.mail.MailHeaders; import org.springframework.integration.mail.support.DefaultMailHeaderMapper; import org.springframework.integration.mapping.HeaderMapper; import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.mail.TestMailServer; -import org.springframework.integration.test.mail.TestMailServer.ImapServer; -import org.springframework.integration.test.mail.TestMailServer.Pop3Server; -import org.springframework.integration.test.mail.TestMailServer.SmtpServer; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -64,31 +59,41 @@ import org.springframework.messaging.PollableChannel; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.GreenMailUtil; +import com.icegreen.greenmail.util.ServerSetup; +import com.icegreen.greenmail.util.ServerSetupTest; + /** * @author Gary Russell * @author Artem Bilan + * @author Alexander Pinske */ @SpringJUnitConfig @DirtiesContext public class MailTests { - private static final SmtpServer smtpServer = TestMailServer.smtp(0); - - private static final Pop3Server pop3Server = TestMailServer.pop3(0); - - private static final ImapServer imapServer = TestMailServer.imap(0); - - private static final ImapServer imapIdleServer = TestMailServer.imap(0); - + private static GreenMail mailServer; @BeforeAll - public static void setup() throws InterruptedException { - int n = 0; - while (n++ < 100 && (!smtpServer.isListening() || !pop3Server.isListening() - || !imapServer.isListening()) || !imapIdleServer.isListening()) { - Thread.sleep(100); - } - assertThat(n < 100).isTrue(); + public static void setup() { + ServerSetup smtp = ServerSetupTest.SMTP.dynamicPort(); + smtp.setServerStartupTimeout(10000); + ServerSetup imap = ServerSetupTest.IMAP.dynamicPort(); + imap.setServerStartupTimeout(10000); + ServerSetup pop3 = ServerSetupTest.POP3.dynamicPort(); + pop3.setServerStartupTimeout(10000); + mailServer = new GreenMail(new ServerSetup[]{ smtp, pop3, imap }); + mailServer.setUser("bar@baz", "smtpuser", "pw"); + mailServer.setUser("popuser", "pw"); + mailServer.setUser("imapuser", "pw"); + mailServer.setUser("imapidleuser", "pw"); + mailServer.start(); + } + + @AfterAll + static void tearDown() { + mailServer.stop(); } @Autowired @@ -120,56 +125,72 @@ public class MailTests { this.sendMailChannel.send(MessageBuilder.withPayload("foo").build()); - int n = 0; - while (n++ < 100 && smtpServer.getMessages().size() == 0) { - Thread.sleep(100); - } + mailServer.waitForIncomingEmail(10000, 1); - assertThat(smtpServer.getMessages().size() > 0).isTrue(); - String message = smtpServer.getMessages().get(0); - assertThat(message).endsWith("foo\n"); - assertThat(message).contains("foo@bar"); - assertThat(message).contains("bar@baz"); - assertThat(message).contains("user:user"); - assertThat(message).contains("password:pw"); + assertThat(mailServer.getReceivedMessagesForDomain("baz").length > 0).isTrue(); + MimeMessage message = mailServer.getReceivedMessagesForDomain("baz")[0]; + assertThat(message.getFrom()).containsOnly(new InternetAddress("foo@bar")); + assertThat(message.getRecipients(RecipientType.TO)).containsOnly(new InternetAddress("bar@baz")); + assertThat(message.getSubject()).isEqualTo("foo"); + assertThat(message.getContent()).asString().isEqualTo("foo\r\n"); } @Test - public void testPop3() throws IOException { + public void testPop3() throws Exception { + MimeMessage mimeMessage = + GreenMailUtil.createTextEmail("Foo ", "Bar , Bar2 ", "Test Email", + "foo\r\n", mailServer.getPop3().getServerSetup()); + mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); + mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); + mailServer.getUserManager().getUser("popuser").deliver(mimeMessage); + Message message = this.pop3Channel.receive(10000); assertThat(message).isNotNull(); MessageHeaders headers = message.getHeaders(); assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar ,Bar2 "); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); - assertThat(message.getPayload()).isEqualTo("foo\r\n\r\n"); - assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); - message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class).close(); + assertThat(message.getPayload()).isEqualTo("foo\r\n"); + assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isFalse(); } @Test public void testImap() throws Exception { + MimeMessage mimeMessage = + GreenMailUtil.createTextEmail("Foo ", "Bar ", "Test Email", "foo\r\n", + mailServer.getImap().getServerSetup()); + mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); + mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); + mailServer.getUserManager().getUser("imapuser").deliver(mimeMessage); + Message message = this.imapChannel.receive(10000); assertThat(message).isNotNull(); MimeMessage mm = (MimeMessage) message.getPayload(); assertThat(mm.getRecipients(RecipientType.TO)[0].toString()).isEqualTo("Foo "); assertThat(mm.getFrom()[0].toString()).isEqualTo("Bar "); assertThat(mm.getSubject()).isEqualTo("Test Email"); - assertThat(mm.getContent()).isEqualTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n"); + assertThat(mm.getContent()).isEqualTo("foo\r\n"); assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class).close(); } @Test - public void testImapIdle() { + public void testImapIdle() throws Exception { + MimeMessage mimeMessage = + GreenMailUtil.createTextEmail("Foo ", "Bar ", "Test Email", "foo\r\n", + mailServer.getImap().getServerSetup()); + mimeMessage.setRecipients(RecipientType.CC, "a@b, c@d"); + mimeMessage.setRecipients(RecipientType.BCC, "e@f, g@h"); + mailServer.getUserManager().getUser("imapidleuser").deliver(mimeMessage); + Message message = this.imapIdleChannel.receive(10000); assertThat(message).isNotNull(); MessageHeaders headers = message.getHeaders(); assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar "); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); - assertThat(message.getPayload()).isEqualTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n"); + assertThat(message.getPayload()).isEqualTo("foo\r\n"); assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); this.imapIdleAdapter.stop(); assertThat(TestUtils.getPropertyValue(this.imapIdleAdapter, "shouldReconnectAutomatically", Boolean.class)) @@ -180,35 +201,6 @@ public class MailTests { @EnableIntegration public static class ContextConfiguration { - @Bean - public SmartLifecycle serverStopper() { - return new SmartLifecycle() { - - @Override - public int getPhase() { - return Integer.MAX_VALUE; - } - - @Override - public void stop() { - smtpServer.stop(); - pop3Server.stop(); - imapServer.stop(); - imapIdleServer.stop(); - } - - @Override - public void start() { - } - - @Override - public boolean isRunning() { - return true; - } - - }; - } - @Bean public IntegrationFlow sendMailFlow() { return IntegrationFlows.from("sendMailChannel") @@ -217,8 +209,8 @@ public class MailTests { .from("foo@bar") .toFunction(m -> new String[]{ "bar@baz" })) .handle(Mail.outboundAdapter("localhost") - .port(smtpServer.getPort()) - .credentials("user", "pw") + .port(mailServer.getSmtp().getPort()) + .credentials("smtpuser", "pw") .protocol("smtp") .javaMailProperties(p -> p.put("mail.debug", "false")), e -> e.id("sendMailEndpoint")) @@ -228,9 +220,9 @@ public class MailTests { @Bean public IntegrationFlow pop3MailFlow() { return IntegrationFlows - .from(Mail.pop3InboundAdapter("localhost", pop3Server.getPort(), "user", "pw") + .from(Mail.pop3InboundAdapter("localhost", mailServer.getPop3().getPort(), "popuser", "pw") .javaMailProperties(p -> p.put("mail.debug", "false")) - .autoCloseFolder(false) + .autoCloseFolder(true) .headerMapper(mailHeaderMapper()), e -> e.autoStartup(true).poller(p -> p.fixedDelay(1000))) .enrichHeaders(s -> s.headerExpressions(c -> c.put(MailHeaders.SUBJECT, "payload.subject") @@ -242,7 +234,7 @@ public class MailTests { @Bean public IntegrationFlow imapMailFlow() { return IntegrationFlows - .from(Mail.imapInboundAdapter("imap://user:pw@localhost:" + imapServer.getPort() + "/INBOX") + .from(Mail.imapInboundAdapter("imap://imapuser:pw@localhost:" + mailServer.getImap().getPort() + "/INBOX") .searchTermStrategy(this::fromAndNotSeenTerm) .userFlag("testSIUserFlag") .autoCloseFolder(false) @@ -257,7 +249,7 @@ public class MailTests { @Bean public IntegrationFlow imapIdleFlow() { return IntegrationFlows - .from(Mail.imapIdleAdapter("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX") + .from(Mail.imapIdleAdapter("imap://imapidleuser:pw@localhost:" + mailServer.getImap().getPort() + "/INBOX") .autoStartup(true) .searchTermStrategy(this::fromAndNotSeenTerm) .userFlag("testSIUserFlag") diff --git a/spring-integration-mail/src/test/resources/log4j2-test.xml b/spring-integration-mail/src/test/resources/log4j2-test.xml index c07e0edf61..eacbafb1d7 100644 --- a/spring-integration-mail/src/test/resources/log4j2-test.xml +++ b/spring-integration-mail/src/test/resources/log4j2-test.xml @@ -8,6 +8,8 @@ + + diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/TestMailServer.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/TestMailServer.java index b3d3a1bcb5..b57efdbe98 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/TestMailServer.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/mail/TestMailServer.java @@ -48,7 +48,10 @@ import org.springframework.util.Base64Utils; * * @since 5.0 * + * @deprecated since 5.5 in favor of GreenMail library for mail testing. + * */ +@Deprecated public final class TestMailServer { public static SmtpServer smtp(int port) { diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index aac93902ca..2fd5b5e6f3 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -119,6 +119,7 @@ It now allows body-only rendering when a header mapper is provided. Starting with version 5.2, the `autoCloseFolder` option is provided on the mail receiver. Setting it to `false` doesn't close the folder automatically after a fetch, but instead an `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` header (see <<./message.adoc#message-header-accessor,`MessageHeaderAccessor` API>> for more information) is populated into every message to producer from the channel adapter. +This does not work with `Pop3MailReceiver` as it relies on opening and closing the folder to get new messages. It is the target application's responsibility to call the `close()` on this header whenever it is necessary in the downstream flow: ==== diff --git a/src/reference/asciidoc/testing.adoc b/src/reference/asciidoc/testing.adoc index 16a52b1864..026bb9ee24 100644 --- a/src/reference/asciidoc/testing.adoc +++ b/src/reference/asciidoc/testing.adoc @@ -33,6 +33,7 @@ Examples of such tools include the following: * Gemfire and Hazelcast can be run as real-data grid nodes in the tests. * The Curator Framework provides a `TestingServer` for Zookeeper interaction. * Apache Kafka provides admin tools to embed a Kafka Broker in the tests. +* The GreenMail is an open source, intuitive and easy-to-use test suite of email servers for testing purposes. Most of these tools and libraries are used in Spring Integration tests. Also, from the GitHub https://github.com/spring-projects/spring-integration[repository] (in the `test` directory of each module), you can discover ideas for how to build your own tests for integration solutions. @@ -97,7 +98,7 @@ private UdpSyslogReceivingChannelAdapter adapter; @Autowired private PollableChannel sysLogs; -... + @Test public void testSimplestUdp() throws Exception { int port = TestUtils.getPropertyValue(adapter1, "udpAdapter.port", Integer.class); @@ -145,7 +146,7 @@ PollerMetadata poller; @Autowired OnlyOnceTrigger testTrigger; -... + @Test @DirtiesContext public void testWithEntityClass() throws Exception { @@ -363,7 +364,7 @@ assertEquals("FOO", receive.getPayload()); Unlike the Mockito `MessageSource` mock object, the `MockMessageHandler` is a regular `AbstractMessageProducingHandler` extension with a chain API to stub handling for incoming messages. The `MockMessageHandler` provides `handleNext(Consumer>)` to specify a one-way stub for the next request message. It is used to mock message handlers that do not produce replies. -`handleNextAndReply(Function, ?>)` is provided for performing the same stub logic for the next request message and producing a reply for it. +The `handleNextAndReply(Function, ?>)` is provided for performing the same stub logic for the next request message and producing a reply for it. They can be chained to simulate any arbitrary request-reply scenarios for all expected request messages variants. These consumers and functions are applied to the incoming messages, one at a time from the stack, until the last, which is then used for all remaining messages. The behavior is similar to the Mockito `Answer` or `doReturn()` API.