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

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

commit 534980c8973ec3339cc66a54d25a835687a374a0
Author: Benoit TELLIER <[email protected]>
AuthorDate: Thu Sep 10 14:00:29 2026 +0200

    JAMES-4228 ValidRcptEmailSubmissionSetValidation integration test
---
 .../EmailSubmissionSetMethodContract.scala         |  53 +++++++
 .../EmailSubmissionSetValidateRcptContract.scala   | 162 +++++++++++++++++++++
 .../MemoryEmailSubmissionSetValidateRcptTest.java  |  48 ++++++
 3 files changed, 263 insertions(+)

diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
index 8cfe655dc8..879e41ffb4 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
@@ -2430,4 +2430,57 @@ trait EmailSubmissionSetMethodContract {
            |  "hasAttachment": true
            |}]""".stripMargin)
   }
+
+  @Test
+  def 
setShouldAcceptUnknownLocalRecipientWhenRecipientValidationIsDisabled(server: 
GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(bobUsername.asString)
+      .setFrom(bobUsername.asString)
+      .setTo(s"unknown@${DOMAIN.asString}")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val bobDraftsPath = MailboxPath.forUser(bobUsername, 
DefaultMailboxes.DRAFTS)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+    val messageId: MessageId = 
server.getProbe(classOf[MailboxProbeImpl]).appendMessage(bobUsername.asString(),
 bobDraftsPath, AppendCommand.builder()
+      .build(message))
+      .getMessageId
+
+    val requestBob =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", 
"urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+         |  "methodCalls": [
+         |     ["EmailSubmission/set", {
+         |       "accountId": "$bobAccountId",
+         |       "create": {
+         |         "k1490": {
+         |           "emailId": "${messageId.serialize}",
+         |           "envelope": {
+         |             "mailFrom": {"email": "${bobUsername.asString}"},
+         |             "rcptTo": [{"email": "unknown@${DOMAIN.asString}"}]
+         |           }
+         |         }
+         |    }
+         |  }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(requestBob)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+  }
 }
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetValidateRcptContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetValidateRcptContract.scala
new file mode 100644
index 0000000000..bf0067572c
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetValidateRcptContract.scala
@@ -0,0 +1,162 @@
+/****************************************************************
+ * 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.jmap.rfc8621.contract
+
+import java.nio.charset.StandardCharsets
+
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.core.JmapRfc8621Configuration
+import org.apache.james.jmap.http.UserCredential
+import 
org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, 
ACCOUNT_ID, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, 
baseRequestSpecBuilder}
+import org.apache.james.mailbox.DefaultMailboxes
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.mime4j.dom.Message
+import org.apache.james.modules.MailboxProbeImpl
+import org.apache.james.utils.DataProbeImpl
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+object EmailSubmissionSetValidateRcptContract {
+  val configuration: JmapRfc8621Configuration = JmapRfc8621Configuration(
+    urlPrefixString = "http://127.0.0.1";,
+    websocketPrefixString = "ws://127.0.0.1",
+    validateRecipientsOnSend = true)
+}
+
+/**
+ * `EmailSubmission/set` behaviour when `send.validate.rcpt` is turned on.
+ */
+trait EmailSubmissionSetValidateRcptContract {
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString, ANDRE_PASSWORD)
+
+    server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS))
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
+  }
+
+  @Test
+  def setShouldAcceptRecipientHavingALocalMailbox(server: GuiceJamesServer): 
Unit =
+    assertThatJson(submit(server, ANDRE.asString))
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+
+  @Test
+  def setShouldAcceptRecipientOfARemoteDomain(server: GuiceJamesServer): Unit =
+    assertThatJson(submit(server, "[email protected]"))
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+
+  @Test
+  def setShouldAcceptRecipientResolvedByRecipientRewriteTable(server: 
GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .addAddressMapping("alias", DOMAIN.asString, ANDRE.asString)
+
+    assertThatJson(submit(server, s"alias@${DOMAIN.asString}"))
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+  }
+
+  @Test
+  def setShouldRejectRecipientOfALocalDomainWithoutMailboxNorMapping(server: 
GuiceJamesServer): Unit =
+    assertThatJson(submit(server, s"unknown@${DOMAIN.asString}"))
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo(s"""{
+                    |  "k1490": {
+                    |    "type": "invalidRecipients",
+                    |    "description": "Invalid recipients: 
unknown@${DOMAIN.asString}",
+                    |    "properties": ["envelope.rcptTo"]
+                    |  }
+                    |}""".stripMargin)
+
+  @Test
+  def setShouldRejectTheWholeSubmissionWhenASingleRecipientIsInvalid(server: 
GuiceJamesServer): Unit =
+    assertThatJson(submit(server, ANDRE.asString, 
s"unknown@${DOMAIN.asString}"))
+      .inPath("methodResponses[0][1].notCreated")
+      .isEqualTo(s"""{
+                    |  "k1490": {
+                    |    "type": "invalidRecipients",
+                    |    "description": "Invalid recipients: 
unknown@${DOMAIN.asString}",
+                    |    "properties": ["envelope.rcptTo"]
+                    |  }
+                    |}""".stripMargin)
+
+  private def submit(server: GuiceJamesServer, recipients: String*): String = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(BOB.asString)
+      .setFrom(BOB.asString)
+      .setTo(recipients: _*)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val messageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.forUser(BOB, 
DefaultMailboxes.DRAFTS), AppendCommand.builder().build(message))
+      .getMessageId
+
+    val rcptTo = recipients.map(recipient => s"""{"email": 
"$recipient"}""").mkString(", ")
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", 
"urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+         |  "methodCalls": [
+         |     ["EmailSubmission/set", {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "create": {
+         |         "k1490": {
+         |           "emailId": "${messageId.serialize}",
+         |           "envelope": {
+         |             "mailFrom": {"email": "${BOB.asString}"},
+         |             "rcptTo": [$rcptTo]
+         |           }
+         |         }
+         |    }
+         |  }, "c1"]]
+         |}""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+  }
+}
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java
new file mode 100644
index 0000000000..9f24c1cc34
--- /dev/null
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSubmissionSetValidateRcptTest.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.jmap.rfc8621.memory;
+
+import static 
org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.MemoryJamesConfiguration;
+import org.apache.james.MemoryJamesServerMain;
+import org.apache.james.jmap.core.JmapRfc8621Configuration;
+import 
org.apache.james.jmap.rfc8621.contract.EmailSubmissionSetValidateRcptContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryEmailSubmissionSetValidateRcptTest implements 
EmailSubmissionSetValidateRcptContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new 
JamesServerBuilder<MemoryJamesConfiguration>(tmpDir ->
+        MemoryJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .enableJMAP()
+            .build())
+        .server(configuration -> 
MemoryJamesServerMain.createServer(configuration)
+            .overrideWith(binder -> binder.bind(JmapRfc8621Configuration.class)
+                
.toInstance(EmailSubmissionSetValidateRcptContract.configuration()))
+            .overrideWith(new TestJMAPServerModule()))
+        .lifeCycle(JamesServerExtension.Lifecycle.PER_TEST)
+        .build();
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to