You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2021/01/14 03:26:08 UTC
[james-project] 03/11: JAMES-3474 Email/set should handle state
property
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit ac22a265774b9962272d42f9a91548157b2557c3
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Fri Jan 8 11:21:55 2021 +0700
JAMES-3474 Email/set should handle state property
---
.../rfc8621/contract/EmailSetMethodContract.scala | 246 ++++++++++++++++++++-
.../org/apache/james/jmap/mail/EmailSet.scala | 1 +
.../apache/james/jmap/method/EmailSetMethod.scala | 21 +-
3 files changed, 264 insertions(+), 4 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 a4cff0d..2277986 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
@@ -25,7 +25,7 @@ import java.time.format.DateTimeFormatter
import java.util.Date
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
-import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.RestAssured.{`given`, `with`, requestSpecification}
import io.restassured.builder.ResponseSpecBuilder
import io.restassured.http.ContentType.JSON
import javax.mail.Flags
@@ -48,6 +48,7 @@ import org.apache.james.mime4j.dom.Message
import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
import org.apache.james.utils.DataProbeImpl
import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.Matchers.{equalTo, not}
import org.junit.jupiter.api.{BeforeEach, Test}
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
@@ -6289,6 +6290,249 @@ trait EmailSetMethodContract {
|}""".stripMargin)
}
+ @Test
+ def newStateShouldBeUpToDate(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+
+ val request =
+ s"""
+ |{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "create": {
+ | "aaaaaa": {
+ | "mailboxIds": {
+ | "${mailboxId.serialize()}": true
+ | }
+ | }
+ | }
+ | }, "c1"],
+ | ["Email/changes", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "#sinceState": {
+ | "resultOf":"c1",
+ | "name":"Email/set",
+ | "path":"newState"
+ | }
+ | }, "c2"]
+ | ]
+ |}
+ |""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .withOptions(new Options(Option.IGNORING_ARRAY_ORDER))
+ .whenIgnoringPaths("methodResponses[1][1].oldState",
+ "methodResponses[1][1].newState")
+ .inPath("methodResponses[1][1]")
+ .isEqualTo(
+ s"""{
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "hasMoreChanges": false,
+ | "created": [],
+ | "updated": [],
+ | "destroyed": []
+ |}""".stripMargin)
+ }
+
+ @Test
+ def oldStateShouldIncludeSetChanges(server: GuiceJamesServer): Unit = {
+ val path: MailboxPath = MailboxPath.forUser(BOB, "mailbox")
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+
+ val message: Message = Fixture.createTestMessage
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString(), path,
+ AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val request =
+ s"""
+ |{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${messageId.serialize}": {
+ | "keywords": {
+ | "music": true
+ | }
+ | }
+ | }
+ | }, "c1"],
+ | ["Email/changes", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "#sinceState": {
+ | "resultOf":"c1",
+ | "name":"Email/set",
+ | "path":"oldState"
+ | }
+ | }, "c2"]
+ | ]
+ |}
+ |""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .log().ifValidationFails()
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .withOptions(new Options(Option.IGNORING_ARRAY_ORDER))
+ .whenIgnoringPaths("methodResponses[1][1].oldState", "methodResponses[1][1].newState")
+ .inPath("methodResponses[1][1]")
+ .isEqualTo(
+ s"""{
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "hasMoreChanges": false,
+ | "created": [],
+ | "updated": ["${messageId.serialize}"],
+ | "destroyed": []
+ |}""".stripMargin)
+ }
+
+ @Test
+ def stateShouldNotTakeIntoAccountDelegationWhenNoCapability(server: GuiceJamesServer): Unit = {
+ val state: String = `with`()
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": []
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ .post
+ .`then`()
+ .extract()
+ .jsonPath()
+ .get("methodResponses[0][1].state")
+
+ val sharedMailboxName = "AndreShared"
+ val andreMailboxPath = MailboxPath.forUser(ANDRE, sharedMailboxName)
+ server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(andreMailboxPath)
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(andreMailboxPath, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Lookup))
+
+ val message: Message = Fixture.createTestMessage
+ server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(ANDRE.asString(), andreMailboxPath,
+ AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .body("methodResponses[0][1].oldState", equalTo(state))
+ }
+
+ @Test
+ def stateShouldTakeIntoAccountDelegationWhenCapability(server: GuiceJamesServer): Unit = {
+ val state: String = `with`()
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids":[]
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ .post
+ .`then`()
+ .extract()
+ .jsonPath()
+ .get("methodResponses[0][1].state")
+
+ val sharedMailboxName = "AndreShared"
+ val andreMailboxPath = MailboxPath.forUser(ANDRE, sharedMailboxName)
+ server.getProbe(classOf[MailboxProbeImpl])
+ .createMailbox(andreMailboxPath)
+
+ server.getProbe(classOf[ACLProbeImpl])
+ .replaceRights(andreMailboxPath, BOB.asString, new MailboxACL.Rfc4314Rights(Right.Lookup))
+
+ val message: Message = Fixture.createTestMessage
+ server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(ANDRE.asString(), andreMailboxPath,
+ AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail",
+ | "urn:apache:james:params:jmap:mail:shares"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+ | }, "c1"]
+ | ]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .body("methodResponses[0][1].oldState", not(equalTo(state)))
+ }
+
private def buildTestMessage = {
Message.Builder
.of
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 f56f5ba..a9fb1e9 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
@@ -338,6 +338,7 @@ case class EmailSetRequest(accountId: AccountId,
destroy: Option[DestroyIds]) extends WithAccountId
case class EmailSetResponse(accountId: AccountId,
+ oldState: Option[State],
newState: State,
created: Option[Map[EmailCreationId, EmailCreationResponse]],
notCreated: Option[Map[EmailCreationId, SetError]],
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 4a69b2c..bd52c8b 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
@@ -20,7 +20,9 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
+import org.apache.james.jmap.api.change.EmailChangeRepository
+import org.apache.james.jmap.api.model.{AccountId => JavaAccountId}
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_MAIL}
import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, State}
import org.apache.james.jmap.json.{EmailSetSerializer, ResponseSerializer}
@@ -39,21 +41,25 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
val sessionSupplier: SessionSupplier,
createPerformer: EmailSetCreatePerformer,
deletePerformer: EmailSetDeletePerformer,
- updatePerformer: EmailSetUpdatePerformer) extends MethodRequiringAccountId[EmailSetRequest] {
+ updatePerformer: EmailSetUpdatePerformer,
+ emailChangeRepository: EmailChangeRepository) extends MethodRequiringAccountId[EmailSetRequest] {
override val methodName: MethodName = MethodName("Email/set")
override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailSetRequest): SMono[InvocationWithContext] = {
for {
+ oldState <- retrieveState(capabilities, mailboxSession)
destroyResults <- deletePerformer.destroy(request, mailboxSession)
updateResults <- updatePerformer.update(request, mailboxSession)
created <- createPerformer.create(request, mailboxSession)
+ newState <- retrieveState(capabilities, mailboxSession)
} yield InvocationWithContext(
invocation = Invocation(
methodName = methodName,
arguments = Arguments(serializer.serialize(EmailSetResponse(
accountId = request.accountId,
- newState = State.INSTANCE,
+ oldState = Some(oldState),
+ newState = newState,
created = created.created,
notCreated = created.notCreated,
updated = updateResults.updated,
@@ -75,4 +81,13 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
case JsSuccess(emailSetRequest, _) => Right(emailSetRequest)
case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
+
+ private def retrieveState(capabilities: Set[CapabilityIdentifier], mailboxSession: MailboxSession): SMono[State] =
+ if (capabilities.contains(JAMES_SHARES)) {
+ SMono(emailChangeRepository.getLatestStateWithDelegation(JavaAccountId.fromUsername(mailboxSession.getUser)))
+ .map(State.fromJava)
+ } else {
+ SMono(emailChangeRepository.getLatestState(JavaAccountId.fromUsername(mailboxSession.getUser)))
+ .map(State.fromJava)
+ }
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org