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 2020/10/27 02:07:52 UTC

[james-project] branch master updated (7d94885 -> 513d2cb)

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

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


    from 7d94885  JAMES-3412 Keywords are case insentive, lower cased
     new 95918cc  JAMES-XXXX Webadmin CLI help commands
     new 339a016  JAMES-3436 Email/set create - mailboxIds & subject properties
     new d01113d  JAMES-3436 Email/set create - delegation testing
     new 513d2cb  JAMES-3436 Email/set create - syntax error validation

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 server/pom.xml                                     |   2 +
 .../rfc8621/contract/EmailSetMethodContract.scala  | 321 +++++++++++++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       |  18 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |  22 +-
 .../apache/james/jmap/method/EmailSetMethod.scala  | 120 +++++---
 .../webadmin-cli}/pom.xml                          |  55 ++--
 .../java/org/apache/james/cli/WebAdminCli.java}    |  86 +++---
 .../apache/james/cli/HelpVersionCommandTest.java   |  77 +++--
 8 files changed, 566 insertions(+), 135 deletions(-)
 copy server/{container/guice/testing/custom-mailets => protocols/webadmin-cli}/pom.xml (52%)
 copy server/protocols/{jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3ProvisioningTest.java => webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java} (59%)
 copy mpt/core/src/test/java/org/apache/james/mpt/TestScriptedUserAdder.java => server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/HelpVersionCommandTest.java (60%)


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


[james-project] 02/04: JAMES-3436 Email/set create - mailboxIds & subject properties

Posted by bt...@apache.org.
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 339a016f6608f38ce651aa4f0f958135c176d97f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Oct 23 15:39:45 2020 +0700

    JAMES-3436 Email/set create - mailboxIds & subject properties
---
 .../rfc8621/contract/EmailSetMethodContract.scala  |  58 ++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       |  18 +++-
 .../org/apache/james/jmap/mail/EmailSet.scala      |  22 +++-
 .../apache/james/jmap/method/EmailSetMethod.scala  | 118 +++++++++++++++------
 4 files changed, 179 insertions(+), 37 deletions(-)

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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 2898b92..d8a3e14 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -175,6 +175,64 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def createShouldAddAnEmailInTargetMailbox(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "subject": "Boredome comes from a boring mind!"
+         |        }
+         |      }
+         |    }, "c1"],
+         |    ["Email/get",
+         |     {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "ids": ["#aaaaaa"],
+         |       "properties": ["mailboxIds", "subject"]
+         |     },
+         |     "c2"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo("{}".stripMargin)
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].list[0].id")
+      .inPath(s"methodResponses[1][1].list")
+      .isEqualTo(
+        s"""[{
+          |  "mailboxIds": {
+          |    "${mailboxId.serialize}": true
+          |  },
+          |  "subject": "Boredome comes from a boring mind!"
+          |}]""".stripMargin)
+  }
+
+  @Test
   def shouldNotResetKeywordWhenInvalidKeyword(server: GuiceJamesServer): Unit = {
     val message: Message = Fixture.createTestMessage
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
index 2928c03..e36e3ca 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -22,8 +22,9 @@ package org.apache.james.jmap.json
 import cats.implicits._
 import eu.timepit.refined.refineV
 import javax.inject.Inject
-import org.apache.james.jmap.mail.EmailSet.{UnparsedMessageId, UnparsedMessageIdConstraint}
-import org.apache.james.jmap.mail.{DestroyIds, EmailSetRequest, EmailSetResponse, EmailSetUpdate, MailboxIds}
+import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId, UnparsedMessageIdConstraint}
+import org.apache.james.jmap.mail.{DestroyIds, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, MailboxIds, Subject}
+import org.apache.james.jmap.model.Id.IdConstraint
 import org.apache.james.jmap.model.KeywordsFactory.STRICT_KEYWORDS_FACTORY
 import org.apache.james.jmap.model.{Keyword, Keywords, SetError}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
@@ -192,6 +193,13 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
         case _ => JsError("Expecting a JsObject as an update entry")
       })
 
+  private implicit val createsMapReads: Reads[Map[EmailCreationId, JsObject]] =
+    readMapEntry[EmailCreationId, JsObject](s => refineV[IdConstraint](s),
+      {
+        case o: JsObject => JsSuccess(o)
+        case _ => JsError("Expecting a JsObject as an update entry")
+      })
+
   private implicit val keywordReads: Reads[Keyword] = {
     case jsString: JsString => Keyword.parse(jsString.value)
       .fold(JsError(_),
@@ -218,10 +226,16 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
   }
   private implicit val destroyIdsWrites: Writes[DestroyIds] = Json.valueWrites[DestroyIds]
   private implicit val emailRequestSetReads: Reads[EmailSetRequest] = Json.reads[EmailSetRequest]
+  private implicit val emailCreationResponseWrites: Writes[EmailCreationResponse] = Json.writes[EmailCreationResponse]
   private implicit val emailResponseSetWrites: OWrites[EmailSetResponse] = Json.writes[EmailSetResponse]
 
+  private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject]
+  private implicit val emailCreationRequestReads: Reads[EmailCreationRequest] = Json.reads[EmailCreationRequest]
+
   def deserialize(input: JsValue): JsResult[EmailSetRequest] = Json.fromJson[EmailSetRequest](input)
 
+  def deserializeCreationRequest(input: JsValue): JsResult[EmailCreationRequest] = Json.fromJson[EmailCreationRequest](input)
+
   def deserializeEmailSetUpdate(input: JsValue): JsResult[EmailSetUpdate] = Json.fromJson[EmailSetUpdate](input)
 
   def serialize(response: EmailSetResponse): JsObject = Json.toJsObject(response)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index ddeac39..5985147 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -18,19 +18,24 @@
  ****************************************************************/
 package org.apache.james.jmap.mail
 
+import java.nio.charset.StandardCharsets
+
 import eu.timepit.refined
 import eu.timepit.refined.api.Refined
 import eu.timepit.refined.collection.NonEmpty
-import org.apache.james.jmap.mail.EmailSet.UnparsedMessageId
+import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
 import org.apache.james.jmap.method.WithAccountId
+import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model.State.State
 import org.apache.james.jmap.model.{AccountId, Keywords, SetError}
 import org.apache.james.mailbox.model.MessageId
+import org.apache.james.mime4j.dom.Message
 import play.api.libs.json.JsObject
 
 import scala.util.{Right, Try}
 
 object EmailSet {
+  type EmailCreationId = Id
   type UnparsedMessageIdConstraint = NonEmpty
   type UnparsedMessageId = String Refined UnparsedMessageIdConstraint
 
@@ -43,14 +48,27 @@ object EmailSet {
     Try(messageIdFactory.fromString(unparsed.value))
 }
 
+case class EmailCreationRequest(mailboxIds: MailboxIds,
+                                subject: Option[Subject]) {
+  def toMime4JMessage: Message = {
+    val builder = Message.Builder.of
+    subject.foreach(value => builder.setSubject(value.value))
+    builder.setBody("", StandardCharsets.UTF_8)
+    builder.build()
+  }
+}
+
 case class DestroyIds(value: Seq[UnparsedMessageId])
 
 case class EmailSetRequest(accountId: AccountId,
+                           create: Option[Map[EmailCreationId, JsObject]],
                            update: Option[Map[UnparsedMessageId, JsObject]],
                            destroy: Option[DestroyIds]) extends WithAccountId
 
 case class EmailSetResponse(accountId: AccountId,
                             newState: State,
+                            created: Option[Map[EmailCreationId, EmailCreationResponse]],
+                            notCreated: Option[Map[EmailCreationId, SetError]],
                             updated: Option[Map[MessageId, Unit]],
                             notUpdated: Option[Map[UnparsedMessageId, SetError]],
                             destroyed: Option[DestroyIds],
@@ -118,3 +136,5 @@ class EmailUpdateValidationException() extends IllegalArgumentException
 case class InvalidEmailPropertyException(property: String, cause: String) extends EmailUpdateValidationException
 case class InvalidEmailUpdateException(property: String, cause: String) extends EmailUpdateValidationException
 
+case class EmailCreationResponse(id: MessageId)
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
index 1d1b519..58f523d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
@@ -28,16 +28,20 @@ import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{EmailSetSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.EmailSet.UnparsedMessageId
 import org.apache.james.jmap.mail.{DestroyIds, EmailSet, EmailSetRequest, EmailSetResponse, MailboxIds, ValidatedEmailSetUpdate}
+import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
+import org.apache.james.jmap.mail.{DestroyIds, EmailCreationRequest, EmailCreationResponse, EmailSet, EmailSetRequest, EmailSetResponse, EmailSetUpdate, MailboxIds, ValidatedEmailSetUpdate}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY
 import org.apache.james.jmap.model.SetError.SetErrorDescription
-import org.apache.james.jmap.model.{Capabilities, Invocation, SetError, State}
-import org.apache.james.mailbox.MessageManager.FlagsUpdateMode
+import org.apache.james.jmap.model.{Capabilities, ClientId, Id, Invocation, ServerId, SetError, State}
+import org.apache.james.mailbox.MessageManager.{AppendCommand, FlagsUpdateMode}
 import org.apache.james.mailbox.exception.MailboxNotFoundException
 import org.apache.james.mailbox.model.{ComposedMessageIdWithMetaData, DeleteResult, MailboxId, MessageId, MessageRange}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession, MessageIdManager, MessageManager}
+import org.apache.james.mailbox.model.{ComposedMessageIdWithMetaData, DeleteResult, MailboxId, MessageId}
+import org.apache.james.mailbox.{MailboxManager, MailboxSession, MessageIdManager}
 import org.apache.james.metrics.api.MetricFactory
 import play.api.libs.json.{JsError, JsObject, JsSuccess}
 import reactor.core.scala.publisher.{SFlux, SMono}
@@ -56,28 +60,20 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
 
 
   case class DestroyResults(results: Seq[DestroyResult]) {
-    def destroyed: Option[DestroyIds] = {
-      Option(results.flatMap({
-        result => result match {
+    def destroyed: Option[DestroyIds] =
+      Option(results.flatMap{
           case result: DestroySuccess => Some(result.messageId)
           case _ => None
-        }
-      }).map(EmailSet.asUnparsed))
+        }.map(EmailSet.asUnparsed))
         .filter(_.nonEmpty)
         .map(DestroyIds)
-    }
 
-    def notDestroyed: Option[Map[UnparsedMessageId, SetError]] = {
-      Option(results.flatMap({
-        result => result match {
-          case failure: DestroyFailure => Some(failure)
+    def notDestroyed: Option[Map[UnparsedMessageId, SetError]] =
+      Option(results.flatMap{
+          case failure: DestroyFailure => Some((failure.unparsedMessageId, failure.asMessageSetError))
           case _ => None
-        }
-      })
-        .map(failure => (failure.unparsedMessageId, failure.asMessageSetError))
-        .toMap)
+        }.toMap)
         .filter(_.nonEmpty)
-    }
   }
 
   object DestroyResult {
@@ -101,6 +97,31 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
     }
   }
 
+  case class CreationResults(results: Seq[CreationResult]) {
+    def created: Option[Map[EmailCreationId, EmailCreationResponse]] =
+      Option(results.flatMap{
+          case result: CreationSuccess => Some((result.clientId, result.response))
+          case _ => None
+        }.toMap)
+        .filter(_.nonEmpty)
+
+    def notCreated: Option[Map[EmailCreationId, SetError]] = {
+      Option(results.flatMap{
+          case failure: CreationFailure => Some((failure.clientId, failure.asMessageSetError))
+          case _ => None
+        }
+        .toMap)
+        .filter(_.nonEmpty)
+    }
+  }
+  trait CreationResult
+  case class CreationSuccess(clientId: EmailCreationId, response: EmailCreationResponse) extends CreationResult
+  case class CreationFailure(clientId: EmailCreationId, e: Throwable) extends CreationResult {
+    def asMessageSetError: SetError = e match {
+      case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
+    }
+  }
+
   trait UpdateResult
   case class UpdateSuccess(messageId: MessageId) extends UpdateResult
   case class UpdateFailure(unparsedMessageId: UnparsedMessageId, e: Throwable) extends UpdateResult {
@@ -112,26 +133,19 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
     }
   }
   case class UpdateResults(results: Seq[UpdateResult]) {
-    def updated: Option[Map[MessageId, Unit]] = {
-      Option(results.flatMap({
-        result => result match {
+    def updated: Option[Map[MessageId, Unit]] =
+      Option(results.flatMap{
           case result: UpdateSuccess => Some(result.messageId, ())
           case _ => None
-        }
-      }).toMap).filter(_.nonEmpty)
-    }
+        }.toMap)
+        .filter(_.nonEmpty)
 
-    def notUpdated: Option[Map[UnparsedMessageId, SetError]] = {
-      Option(results.flatMap({
-        result => result match {
-          case failure: UpdateFailure => Some(failure)
+    def notUpdated: Option[Map[UnparsedMessageId, SetError]] =
+      Option(results.flatMap{
+          case failure: UpdateFailure => Some((failure.unparsedMessageId, failure.asMessageSetError))
           case _ => None
-        }
-      })
-        .map(failure => (failure.unparsedMessageId, failure.asMessageSetError))
-        .toMap)
+        }.toMap)
         .filter(_.nonEmpty)
-    }
   }
 
   override val methodName: MethodName = MethodName("Email/set")
@@ -140,19 +154,28 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailSetRequest): SMono[InvocationWithContext] = {
     for {
       destroyResults <- destroy(request, mailboxSession)
-      updateResults <- update(request, mailboxSession).doOnError(e => e.printStackTrace())
+      updateResults <- update(request, mailboxSession)
+      created <- create(request, mailboxSession)
     } yield InvocationWithContext(
       invocation = Invocation(
         methodName = invocation.invocation.methodName,
         arguments = Arguments(serializer.serialize(EmailSetResponse(
           accountId = request.accountId,
           newState = State.INSTANCE,
+          created = created.created,
+          notCreated = created.notCreated,
           updated = updateResults.updated,
           notUpdated = updateResults.notUpdated,
           destroyed = destroyResults.destroyed,
           notDestroyed = destroyResults.notDestroyed))),
         methodCallId = invocation.invocation.methodCallId),
-      processingContext = invocation.processingContext)
+      processingContext = created.created.getOrElse(Map())
+          .foldLeft(invocation.processingContext)({
+            case (processingContext, (clientId, response)) =>
+              Id.validate(response.id.serialize)
+                  .fold(_ => processingContext,
+                    serverId => processingContext.recordCreatedId(ClientId(clientId), ServerId(serverId)))
+          }))
   }
 
   override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): SMono[EmailSetRequest] = asEmailSetRequest(invocation.arguments)
@@ -188,6 +211,33 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
     }
   }
 
+  private def create(request: EmailSetRequest, mailboxSession: MailboxSession): SMono[CreationResults] =
+    SFlux.fromIterable(request.create.getOrElse(Map()))
+        .concatMap {
+          case (clientId, json) => serializer.deserializeCreationRequest(json)
+            .fold(e => SMono.just[CreationResult](CreationFailure(clientId, new IllegalArgumentException(e.toString))),
+              creationRequest => create(clientId, creationRequest, mailboxSession))
+        }.collectSeq()
+        .map(CreationResults)
+
+  private def create(clientId: EmailCreationId, request: EmailCreationRequest, mailboxSession: MailboxSession): SMono[CreationResult] = {
+    if (request.mailboxIds.value.size != 1) {
+      SMono.just(CreationFailure(clientId, new IllegalArgumentException("mailboxIds need to have size 1")))
+    } else {
+      SMono.fromCallable[CreationResult](() => {
+        val mailboxId: MailboxId = request.mailboxIds.value.headOption.get
+        val appendResult = mailboxManager.getMailbox(mailboxId, mailboxSession)
+          .appendMessage(AppendCommand.builder()
+            .recent()
+            .build(request.toMime4JMessage),
+            mailboxSession)
+        CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId))
+      })
+        .subscribeOn(Schedulers.elastic())
+        .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e)))
+    }
+  }
+
   private def update(emailSetRequest: EmailSetRequest, mailboxSession: MailboxSession): SMono[UpdateResults] = {
     emailSetRequest.update
       .filter(_.nonEmpty)


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


[james-project] 04/04: JAMES-3436 Email/set create - syntax error validation

Posted by bt...@apache.org.
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 513d2cbe6d90b0f4f69ae4163d213af5dd4a49f5
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Oct 23 16:19:14 2020 +0700

    JAMES-3436 Email/set create - syntax error validation
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 167 +++++++++++++++++++++
 .../apache/james/jmap/method/EmailSetMethod.scala  |   1 +
 2 files changed, 168 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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 1730fd0..0e8595b 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -276,6 +276,173 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def createShouldRejectEmptyMailboxIds(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {},
+         |          "subject": "Boredome comes from a boring mind!"
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.aaaaaa")
+      .isEqualTo(
+        s"""{
+          |  "description": "mailboxIds need to have size 1",
+          |  "type": "invalidArguments"
+          |}""".stripMargin)
+  }
+
+  @Test
+  def createShouldRejectInvalidMailboxIds(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |            "invalid": true
+         |          },
+         |          "subject": "Boredome comes from a boring mind!"
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.aaaaaa")
+      .isEqualTo(
+        s"""{
+          |  "description": "List((/mailboxIds,List(JsonValidationError(List(For input string: \\"invalid\\"),ArraySeq()))))",
+          |  "type": "invalidArguments"
+          |}""".stripMargin)
+  }
+
+  @Test
+  def createShouldRejectNoMailboxIds(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "subject": "Boredome comes from a boring mind!"
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.aaaaaa")
+      .isEqualTo(
+        s"""{
+          |  "description": "List((/mailboxIds,List(JsonValidationError(List(error.path.missing),ArraySeq()))))",
+          |  "type": "invalidArguments"
+          |}""".stripMargin)
+  }
+
+  @Test
+  def createShouldRejectInvalidJson(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "subject": ["Boredome comes from a boring mind!"]
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.aaaaaa")
+      .isEqualTo(
+        s"""{
+          |  "description": "List((/subject,List(JsonValidationError(List(error.expected.jsstring),ArraySeq()))))",
+          |  "type": "invalidArguments"
+          |}""".stripMargin)
+  }
+
+  @Test
   def createShouldSucceedIfDelegated(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
     val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
index 7bb499b..e95abb9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
@@ -118,6 +118,7 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
   case class CreationSuccess(clientId: EmailCreationId, response: EmailCreationResponse) extends CreationResult
   case class CreationFailure(clientId: EmailCreationId, e: Throwable) extends CreationResult {
     def asMessageSetError: SetError = e match {
+      case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
       case e: MailboxNotFoundException => SetError.notFound(SetErrorDescription("Mailbox " + e.getMessage))
       case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
     }


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


[james-project] 01/04: JAMES-XXXX Webadmin CLI help commands

Posted by bt...@apache.org.
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 95918cca14cf9081aad782c4c82ab3b40ef4f74b
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Oct 12 15:21:35 2020 +0700

    JAMES-XXXX Webadmin CLI help commands
---
 server/pom.xml                                     |  2 +
 server/protocols/webadmin-cli/pom.xml              | 87 ++++++++++++++++++++++
 .../java/org/apache/james/cli/WebAdminCli.java     | 52 +++++++++++++
 .../apache/james/cli/HelpVersionCommandTest.java   | 54 ++++++++++++++
 4 files changed, 195 insertions(+)

diff --git a/server/pom.xml b/server/pom.xml
index a152c6a..cc7c860 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -96,6 +96,7 @@
         <module>protocols/protocols-pop3</module>
         <module>protocols/protocols-smtp</module>
         <module>protocols/webadmin</module>
+        <module>protocols/webadmin-cli</module>
         <module>protocols/webadmin-integration-test</module>
 
         <module>queue/queue-activemq</module>
@@ -111,6 +112,7 @@
         <module>task/task-memory</module>
 
         <module>testing</module>
+
     </modules>
 
     <properties>
diff --git a/server/protocols/webadmin-cli/pom.xml b/server/protocols/webadmin-cli/pom.xml
new file mode 100644
index 0000000..5b010ed
--- /dev/null
+++ b/server/protocols/webadmin-cli/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>james-server</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.6.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <name>Apache James :: Server :: Web Admin CLI</name>
+    <description>James Webadmin based CLI</description>
+    <artifactId>webadmin-cli</artifactId>
+
+
+
+    <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>testing-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli</artifactId>
+            <version>4.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <archive>
+                                <manifest>
+                                    <mainClass>
+                                        org.apache.james.cli.WebAdminCli
+                                    </mainClass>
+                                </manifest>
+                            </archive>
+                            <descriptorRefs>
+                                <descriptorRef>jar-with-dependencies</descriptorRef>
+                            </descriptorRefs>
+                            <finalName>james-cli</finalName>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
new file mode 100644
index 0000000..895d617
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
@@ -0,0 +1,52 @@
+/******************************************************************
+ * 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.cli;
+
+import java.util.concurrent.Callable;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+        name = "james-cli",
+        description = "James Webadmin CLI")
+public class WebAdminCli implements Callable<Integer> {
+
+    @Override
+    public Integer call() {
+        return 0;
+    }
+
+    public static void main(String[] args) {
+        int exitCode = execute(args);
+        System.exit(exitCode);
+    }
+
+    public static int execute(String[] args) {
+        WebAdminCli parent = new WebAdminCli();
+        return new CommandLine(parent)
+                .addSubcommand(new CommandLine.HelpCommand())
+                .execute(args);
+    }
+
+    public static int executeFluent(String... args) {
+        return execute(args);
+    }
+
+}
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/HelpVersionCommandTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/HelpVersionCommandTest.java
new file mode 100644
index 0000000..1d8c5cd
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/HelpVersionCommandTest.java
@@ -0,0 +1,54 @@
+/******************************************************************
+ * 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.cli;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HelpVersionCommandTest {
+
+    private final PrintStream standardOut = System.out;
+    private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+
+    @BeforeEach
+    void setup() {
+        System.setOut(new PrintStream(outputStreamCaptor));
+    }
+
+    @AfterEach
+    void tearDown() {
+        System.setOut(standardOut);
+    }
+
+    @Test
+    void helpCommandShouldShowHelpMessage() {
+        int exitCode = WebAdminCli.executeFluent("help");
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString()).contains("help");
+        assertThat(outputStreamCaptor.toString()).contains("Displays help information about the specified command");
+    }
+
+}


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


[james-project] 03/04: JAMES-3436 Email/set create - delegation testing

Posted by bt...@apache.org.
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 d01113d2a686db5022affdc7ad134713f636f4e1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Oct 23 16:03:59 2020 +0700

    JAMES-3436 Email/set create - delegation testing
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 96 ++++++++++++++++++++++
 .../apache/james/jmap/method/EmailSetMethod.scala  |  1 +
 2 files changed, 97 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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index d8a3e14..1730fd0 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -233,6 +233,102 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def createShouldFailIfForbidden(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "subject": "Boredome comes from a boring mind!"
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.aaaaaa")
+      .isEqualTo(
+        s"""{
+          |  "description": "Mailbox ${mailboxId.serialize} can not be found",
+          |  "type": "notFound"
+          |}""".stripMargin)
+  }
+
+  @Test
+  def createShouldSucceedIfDelegated(server: GuiceJamesServer): Unit = {
+    val andrePath = MailboxPath.inbox(ANDRE)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+
+    server.getProbe(classOf[ACLProbeImpl])
+      .replaceRights(andrePath, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Insert, Right.Lookup, Right.Read))
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          }
+         |        }
+         |      }
+         |    }, "c1"], ["Email/get",
+         |     {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "ids": ["#aaaaaa"],
+         |       "properties": ["mailboxIds"]
+         |     },
+         |     "c2"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].list[0].id")
+      .inPath(s"methodResponses[1][1].list")
+      .isEqualTo(
+        s"""[{
+           |  "mailboxIds": {
+           |    "${mailboxId.serialize}": true
+           |  }
+           |}]""".stripMargin)
+  }
+
+  @Test
   def shouldNotResetKeywordWhenInvalidKeyword(server: GuiceJamesServer): Unit = {
     val message: Message = Fixture.createTestMessage
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
index 58f523d..7bb499b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
@@ -118,6 +118,7 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
   case class CreationSuccess(clientId: EmailCreationId, response: EmailCreationResponse) extends CreationResult
   case class CreationFailure(clientId: EmailCreationId, e: Throwable) extends CreationResult {
     def asMessageSetError: SetError = e match {
+      case e: MailboxNotFoundException => SetError.notFound(SetErrorDescription("Mailbox " + e.getMessage))
       case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
     }
   }


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