You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/01/25 02:58:25 UTC

[james-project] 02/03: JAMES-3432 DSN generation integration tests

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit ee50340dc0677475cdffed6dc4ccf7e2501f6d31
Author: RĂ©mi Kowalski <rk...@linagora.com>
AuthorDate: Mon Jan 18 15:05:16 2021 +0100

    JAMES-3432 DSN generation integration tests
    
     - DSN failures are enabled by default
    
     https://tools.ietf.org/html/rfc3461#section-4.1
    
     ```
        For compatibility with SMTP clients that do not use the NOTIFY
        facility, the absence of a NOTIFY parameter in a RCPT command may be
        interpreted as either NOTIFY=FAILURE or NOTIFY=FAILURE,DELAY.
     ```
    
     - Disable tests failing due to known limitations
       - We cannot generate relay success notifications as RemoteDelivery lacks
         a success callback
    
     - Use RemoteDelivery error callback (bounces processor)
---
 .../james/smtp/dsn/DSNLocalIntegrationTest.java    | 336 ++++++++++++++++++++
 .../james/smtp/dsn/DSNRemoteIntegrationTest.java   | 343 +++++++++++++++++++++
 2 files changed, 679 insertions(+)

diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNLocalIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNLocalIntegrationTest.java
new file mode 100644
index 0000000..7c6ac8a
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNLocalIntegrationTest.java
@@ -0,0 +1,336 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.apache.james.smtp.dsn;
+
+import static org.apache.james.MemoryJamesServerMain.SMTP_AND_IMAP_MODULE;
+import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN;
+import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
+import static org.apache.james.mailets.configuration.Constants.PASSWORD;
+import static org.apache.james.mailets.configuration.Constants.awaitAtMostOneMinute;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.mailets.configuration.SmtpConfiguration;
+import org.apache.james.modules.protocols.ImapGuiceProbe;
+import org.apache.james.modules.protocols.SmtpGuiceProbe;
+import org.apache.james.smtpserver.dsn.DSNEhloHook;
+import org.apache.james.smtpserver.dsn.DSNMailParameterHook;
+import org.apache.james.smtpserver.dsn.DSNMessageHook;
+import org.apache.james.smtpserver.dsn.DSNRcptParameterHook;
+
+import org.apache.james.transport.mailets.DSNBounce;
+import org.apache.james.transport.mailets.LocalDelivery;
+import org.apache.james.transport.mailets.RecipientRewriteTable;
+import org.apache.james.transport.mailets.ToProcessor;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.transport.matchers.DSNFailureRequested;
+import org.apache.james.transport.matchers.DSNSuccessRequested;
+import org.apache.james.transport.matchers.RecipientIs;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.james.utils.TestIMAPClient;
+import org.assertj.core.api.Assertions;
+import org.awaitility.Awaitility;
+import org.awaitility.Duration;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class DSNLocalIntegrationTest {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DSNLocalIntegrationTest.class);
+
+    private static final String FROM = "from@" + DEFAULT_DOMAIN;
+    private static final String RECIPIENT = "touser@" + DEFAULT_DOMAIN;
+    private static final String FAILING_RECIPIENT = "failing@" + DEFAULT_DOMAIN;
+    public static final ConditionFactory AWAIT_NO_MESSAGE = Awaitility.with().pollDelay(new Duration(2, TimeUnit.SECONDS)).timeout(Duration.FIVE_SECONDS);
+
+    private InMemoryDNSService inMemoryDNSService;
+
+    @RegisterExtension
+    public TestIMAPClient testIMAPClient = new TestIMAPClient();
+    @RegisterExtension
+    public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN);
+
+    private TemporaryJamesServer jamesServer;
+
+    @BeforeEach
+    void setUp(@TempDir File temporaryFolder) throws Exception {
+        inMemoryDNSService = new InMemoryDNSService()
+            .registerMxRecord(DEFAULT_DOMAIN, LOCALHOST_IP);
+
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withOverrides(binder -> binder.bind(DNSService.class).toInstance(inMemoryDNSService))
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(CommonProcessors.simpleRoot())
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(localDelivery())
+                .putProcessor(ProcessorConfiguration.bounces()
+                    .enableJmx(false)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(DSNFailureRequested.class)
+                        .mailet(DSNBounce.class)
+                        .addProperty("defaultStatus", "5.0.0")
+                        .addProperty("action", "failed")
+                        .addProperty("prefix", "[FAILURE]")
+                        .addProperty("messageString", "Your message failed to be delivered")
+                    )))
+            .withSmtpConfiguration(SmtpConfiguration.builder()
+                .addHook(DSNEhloHook.class.getName())
+                .addHook(DSNMailParameterHook.class.getName())
+                .addHook(DSNRcptParameterHook.class.getName())
+                .addHook(DSNMessageHook.class.getName()))
+            .build(temporaryFolder);
+        jamesServer.start();
+
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(FAILING_RECIPIENT, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+    }
+
+    private ProcessorConfiguration.Builder localDelivery() {
+        return ProcessorConfiguration.transport()
+            .addMailet(MailetConfiguration.BCC_STRIPPER)
+            .addMailet(MailetConfiguration.builder()
+                .matcher(All.class)
+                .mailet(RecipientRewriteTable.class))
+            .addMailet(MailetConfiguration.builder()
+                .matcher(RecipientIs.class).matcherCondition(FAILING_RECIPIENT)
+                .mailet(ToProcessor.class)
+                .addProperty("processor", ProcessorConfiguration.STATE_BOUNCES))
+            .addMailet(MailetConfiguration.builder()
+                .matcher(All.class)
+                .mailet(LocalDelivery.class)
+                .addProperty("consume", "false"))
+            .addMailet(MailetConfiguration.builder()
+                .matcher(DSNSuccessRequested.class)
+                .mailet(DSNBounce.class)
+                .addProperty("defaultStatus", "2.0.0")
+                .addProperty("action", "delivered")
+                .addProperty("prefix", "[SUCCESS]")
+                .addProperty("messageString", "Your message was successfully delivered")
+            );
+    }
+
+    @Test
+    void givenAMailWithNoNotifyWhenItSucceedsThenNoDsnIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + ">");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifyNeverWhenItSucceedThenNoDsnIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=NEVER");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifySuccessWhenItSucceedThenADsnSuccessIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=SUCCESS");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+            .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [SUCCESS]");
+        Assertions.assertThat(dsnMessage).contains("Status: 2.0.0");
+        Assertions.assertThat(dsnMessage).contains("Your message was successfully delivered\n" +
+            "Delivered recipient(s):\n" +
+            "touser@james.org");
+    }
+
+    @Test
+    void givenAMailWithNotifyFailureWhenItSucceedThenNoDsnIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNoNotifyWhenItFailsThenADSNBounceIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + FAILING_RECIPIENT + ">");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+            .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [FAILURE]");
+        Assertions.assertThat(dsnMessage).contains("Status: 5.0.0");
+        Assertions.assertThat(dsnMessage).contains("Your message failed to be delivered\n" +
+            "Failed recipient(s):\n" +
+            "failing@james.org");
+    }
+
+    @Test
+    void givenAMailWithNotifyNeverWhenItFailsThenNoEmailIsSentBack() throws IOException, InterruptedException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + FAILING_RECIPIENT + "> NOTIFY=NEVER");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifySuccessWhenItFailsThenNoBounceIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + FAILING_RECIPIENT + "> NOTIFY=SUCCESS");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifyFailureWhenItFailsThenADsnBounceIsSentBack() throws IOException {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + FAILING_RECIPIENT + "> NOTIFY=FAILURE");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+       String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+           .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [FAILURE]");
+        Assertions.assertThat(dsnMessage).contains("Status: 5.0.0");
+        Assertions.assertThat(dsnMessage).contains("Your message failed to be delivered\n" +
+            "Failed recipient(s):\n" +
+            "failing@james.org");
+    }
+
+    @AfterEach
+    void tearDown() {
+        jamesServer.shutdown();
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNRemoteIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNRemoteIntegrationTest.java
new file mode 100644
index 0000000..a99202c
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/dsn/DSNRemoteIntegrationTest.java
@@ -0,0 +1,343 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.apache.james.smtp.dsn;
+
+import static org.apache.james.MemoryJamesServerMain.SMTP_AND_IMAP_MODULE;
+import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN;
+import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
+import static org.apache.james.mailets.configuration.Constants.PASSWORD;
+import static org.apache.james.mailets.configuration.Constants.awaitAtMostOneMinute;
+import static org.apache.james.mock.smtp.server.ConfigurationClient.BehaviorsParamsBuilder.ResponseStep.doesNotAcceptAnyMail;
+import static org.apache.james.mock.smtp.server.model.Condition.MATCH_ALL;
+import static org.apache.james.mock.smtp.server.model.SMTPCommand.RCPT_TO;
+import static org.apache.james.util.docker.Images.MOCK_SMTP_SERVER;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.mailets.configuration.SmtpConfiguration;
+import org.apache.james.mock.smtp.server.ConfigurationClient;
+import org.apache.james.mock.smtp.server.model.SMTPExtension;
+import org.apache.james.mock.smtp.server.model.SMTPExtensions;
+import org.apache.james.modules.protocols.ImapGuiceProbe;
+import org.apache.james.modules.protocols.SmtpGuiceProbe;
+import org.apache.james.smtpserver.dsn.DSNEhloHook;
+import org.apache.james.smtpserver.dsn.DSNMailParameterHook;
+import org.apache.james.smtpserver.dsn.DSNMessageHook;
+import org.apache.james.smtpserver.dsn.DSNRcptParameterHook;
+import org.apache.james.transport.mailets.DSNBounce;
+import org.apache.james.transport.mailets.LocalDelivery;
+import org.apache.james.transport.mailets.RecipientRewriteTable;
+import org.apache.james.transport.mailets.RemoteDelivery;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.transport.matchers.DSNFailureRequested;
+import org.apache.james.transport.matchers.RecipientIsLocal;
+import org.apache.james.util.Host;
+import org.apache.james.util.docker.DockerContainer;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.james.utils.TestIMAPClient;
+import org.assertj.core.api.Assertions;
+import org.awaitility.Awaitility;
+import org.awaitility.Duration;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class DSNRemoteIntegrationTest {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DSNRemoteIntegrationTest.class);
+
+    private static final String ANOTHER_DOMAIN = "other.com";
+    private static final String FROM = "from@" + DEFAULT_DOMAIN;
+    private static final String RECIPIENT = "touser@" + ANOTHER_DOMAIN;
+    private static final ConditionFactory AWAIT_NO_MESSAGE = Awaitility.with().pollDelay(new Duration(2, TimeUnit.SECONDS)).timeout(Duration.FIVE_SECONDS);
+
+    private InMemoryDNSService inMemoryDNSService;
+    private ConfigurationClient mockSMTPConfiguration;
+
+    @RegisterExtension
+    public static DockerContainer mockSmtp = DockerContainer.fromName(MOCK_SMTP_SERVER)
+        .withLogConsumer(outputFrame -> LOGGER.debug("MockSMTP 1: " + outputFrame.getUtf8String()));
+
+    @RegisterExtension
+    public TestIMAPClient testIMAPClient = new TestIMAPClient();
+    @RegisterExtension
+    public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN);
+
+    private TemporaryJamesServer jamesServer;
+
+    @BeforeEach
+    void setUp(@TempDir File temporaryFolder) throws Exception {
+        inMemoryDNSService = new InMemoryDNSService()
+            .registerMxRecord(DEFAULT_DOMAIN, LOCALHOST_IP)
+            .registerMxRecord(ANOTHER_DOMAIN, mockSmtp.getContainerIp());
+
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withOverrides(binder -> binder.bind(DNSService.class).toInstance(inMemoryDNSService))
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(CommonProcessors.simpleRoot())
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(directResolutionTransport())
+                .putProcessor(ProcessorConfiguration.builder().state("relay-bounces")
+                    .enableJmx(false)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(DSNFailureRequested.class)
+                        .mailet(DSNBounce.class)
+                        .addProperty("defaultStatus", "5.0.0")
+                        .addProperty("action", "failed")
+                        .addProperty("prefix", "[FAILURE]")
+                        .addProperty("messageString", "Your message failed to be delivered")))
+                .putProcessor(CommonProcessors.bounces()))
+            .withSmtpConfiguration(SmtpConfiguration.builder()
+                .addHook(DSNEhloHook.class.getName())
+                .addHook(DSNMailParameterHook.class.getName())
+                .addHook(DSNRcptParameterHook.class.getName())
+                .addHook(DSNMessageHook.class.getName()))
+            .build(temporaryFolder);
+        jamesServer.start();
+
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD);
+        mockSMTPConfiguration = configurationClient(mockSmtp);
+        mockSMTPConfiguration.setSMTPExtensions(SMTPExtensions.of(SMTPExtension.of("dsn")));
+
+        assertThat(mockSMTPConfiguration.version()).isEqualTo("0.2");
+    }
+
+    private ProcessorConfiguration.Builder directResolutionTransport() {
+        return ProcessorConfiguration.transport()
+            .addMailet(MailetConfiguration.BCC_STRIPPER)
+            .addMailet(MailetConfiguration.builder()
+                .matcher(All.class)
+                .mailet(RecipientRewriteTable.class))
+            .addMailet(MailetConfiguration.builder()
+                .mailet(LocalDelivery.class)
+                .matcher(RecipientIsLocal.class))
+            .addMailet(MailetConfiguration.builder()
+                .mailet(RemoteDelivery.class)
+                .matcher(All.class)
+                .addProperty("sendpartial", "true")
+                .addProperty("bounceProcessor", "relay-bounces"));
+    }
+
+    @Test
+    void givenAMailWithNoNotifyWhenItSucceedsThenNoEmailIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + ">");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifyNeverWhenItSucceedThenNoDsnIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=NEVER");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Disabled("JAMES-3431 DSN relayed notifications cannot be generated as RemoteDelivery lacks a 'success' callback")
+    @Test
+    void givenAMailWithNotifySuccessWhenItSucceedThenADsnSuccessIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=SUCCESS");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+            .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [SUCCESS]");
+        Assertions.assertThat(dsnMessage).contains("Status: 2.0.0");
+        Assertions.assertThat(dsnMessage).contains("Your message was successfully delivered\n" +
+            "Delivered recipient(s):\n" +
+            "touser@other.com");
+    }
+
+    @Test
+    void givenAMailWithNotifyFailureWhenItSucceedThenNoEmailIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNoNotifyWhenItFailsThenADSNBounceIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+        mockSMTPConfiguration.addNewBehavior()
+            .expect(RCPT_TO)
+            .matching(MATCH_ALL)
+            .thenRespond(doesNotAcceptAnyMail("mock response"))
+            .anyTimes()
+            .post();
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + ">");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+            .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [FAILURE]");
+        Assertions.assertThat(dsnMessage).contains("Status: 521 mock response");
+        Assertions.assertThat(dsnMessage).contains("Your message failed to be delivered\n" +
+            "Failed recipient(s):\n" +
+            "touser@other.com");
+    }
+
+    @Test
+    void givenAMailWithNotifyNeverWhenItFailsThenNoEmailIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=NEVER");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitNoMessage(AWAIT_NO_MESSAGE);
+    }
+
+    @Test
+    void givenAMailWithNotifyFailureWhenItFailsThenADsnBounceIsSentBack() throws Exception {
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+        mockSMTPConfiguration.addNewBehavior()
+            .expect(RCPT_TO)
+            .matching(MATCH_ALL)
+            .thenRespond(doesNotAcceptAnyMail("mock response"))
+            .anyTimes()
+            .post();
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + ">");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        String dsnMessage = testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(FROM, PASSWORD)
+            .select(TestIMAPClient.INBOX)
+            .awaitMessageCount(awaitAtMostOneMinute, 1)
+            .readFirstMessage();
+
+        Assertions.assertThat(dsnMessage).contains("Subject: [FAILURE]");
+        Assertions.assertThat(dsnMessage).contains("Status: 521 mock response");
+        Assertions.assertThat(dsnMessage).contains("Your message failed to be delivered\n" +
+            "Failed recipient(s):\n" +
+            "touser@other.com");
+    }
+
+    @AfterEach
+    void tearDown() {
+        jamesServer.shutdown();
+        mockSMTPConfiguration.cleanServer();
+    }
+
+    private ConfigurationClient configurationClient(DockerContainer mockSmtp) {
+        return ConfigurationClient.from(
+            Host.from(mockSmtp.getHostIp(),
+                mockSmtp.getMappedPort(8000)));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org