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:05 UTC

[james-project] branch master updated (6d899ce -> b929cdb)

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

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


    from 6d899ce  JAMES-2543 Junit 4 -> 5 public access modifier cleanups
     new c2cd494  JAMES-3436 Email/set method contract fix up
     new 5acfb98  JAMES-3436 Email/set create should reject headers properties
     new ac22a26  JAMES-3474 Email/set should handle state property
     new 716b303  JAMES-3474 New/old state should be ignored in irrelevant Email/set calls
     new c4d9933  JAMES-3474 Disable asynchronous state tests for distributed environment
     new 0c6015b  JAMES-3470 CassandraEmailChangeRepository implementation
     new fcc9ef9  JAMES-3470 Binding for CassandraEmailChangeModule
     new 4c16e1f  JAMES-3470 EmailChangeRepositoryContract should stop relying on TestMessageId
     new 5de9000  JAMES-3470 Adapt Email/set test for more stability
     new a046adf  JAMES-3472 Remove unused method
     new b929cdb  JAMES-3472 Add EmailChangesMethod integration test for distributed environment

The 11 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:
 .../mailbox/cassandra/ids/CassandraMessageId.java  |   2 +-
 .../james/modules/data/CassandraJmapModule.java    |   2 +
 ...Module.java => CassandraEmailChangeModule.java} |  22 +-
 .../change/CassandraEmailChangeRepository.java     |  56 +-
 ...itoryDAO.java => EmailChangeRepositoryDAO.java} |  58 +-
 ...geTable.java => CassandraEmailChangeTable.java} |   6 +-
 ...ava => CassandraEmailChangeRepositoryTest.java} |  33 +-
 .../api/change/EmailChangeRepositoryContract.java  | 699 +++++++++++++--------
 .../change/MemoryEmailChangeRepositoryTest.java    |  16 +-
 ....java => DistributedEmailChangeMethodTest.java} |  11 +-
 .../distributed/DistributedEmailSetMethodTest.java |  13 +
 .../contract/EmailChangesMethodContract.scala      |   5 +-
 .../rfc8621/contract/EmailSetMethodContract.scala  | 555 ++++++++++++----
 .../EmailSubmissionSetMethodContract.scala         |   4 +
 .../memory/MemoryEmailChangesMethodTest.java       |   9 -
 .../james/jmap/json/EmailSetSerializer.scala       |  21 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |   1 +
 .../apache/james/jmap/method/EmailSetMethod.scala  |  21 +-
 18 files changed, 1058 insertions(+), 476 deletions(-)
 copy server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/{CassandraMailboxChangeModule.java => CassandraEmailChangeModule.java} (85%)
 copy server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/{MailboxChangeRepositoryDAO.java => EmailChangeRepositoryDAO.java} (81%)
 copy server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/tables/{CassandraMailboxChangeTable.java => CassandraEmailChangeTable.java} (91%)
 copy server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/{CassandraMailboxChangeRepositoryTest.java => CassandraEmailChangeRepositoryTest.java} (66%)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedMailboxChangeMethodTest.java => DistributedEmailChangeMethodTest.java} (88%)


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


[james-project] 02/11: JAMES-3436 Email/set create should reject headers properties

Posted by rc...@apache.org.
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 5acfb985ef72403ca59cd53484172cac9227afea
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Jan 12 17:51:03 2021 +0700

    JAMES-3436 Email/set create should reject headers properties
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 54 ++++++++++++++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       | 21 +++++----
 2 files changed, 67 insertions(+), 8 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 b39614d..a4cff0d 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
@@ -404,6 +404,60 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def createShouldFailWhenEmailContainsHeadersProperties(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
+         |          },
+         |          "headers": [
+         |            {
+         |              "name": "Content-Type",
+         |              "value": " text/plain; charset=utf-8; format=flowed"
+         |            },
+         |            {
+         |              "name": "Content-Transfer-Encoding",
+         |              "value": " 7bit"
+         |            }
+         |          ]
+         |        }
+         |      }
+         |    }, "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")
+      .isEqualTo(
+        s"""{
+           |  "aaaaaa": {
+           |    "type": "invalidArguments",
+           |    "description": "List((,List(JsonValidationError(List('headers' is not allowed),ArraySeq()))))"
+           |  }
+           |}""".stripMargin)
+  }
+
+  @Test
   def shouldNotResetKeywordWhenFalseValue(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 0ad72f6..83fa593 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
@@ -355,12 +355,15 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
   private implicit val emailCreationRequestWithoutHeadersReads: Reads[EmailCreationRequestWithoutHeaders] = Json.reads[EmailCreationRequestWithoutHeaders]
   private implicit val emailCreationRequestReads: Reads[EmailCreationRequest] = {
     case o: JsObject =>
-      val withoutHeader = emailCreationRequestWithoutHeadersReads.reads(o)
-
-      val specificHeadersEither: Either[IllegalArgumentException, List[EmailHeader]] = o.value.toList
-        .filter {
-          case (name, _) => name.startsWith("header:")
-        }.map {
+      if(o.value.contains("headers")) {
+        JsError("'headers' is not allowed")
+      } else {
+        val withoutHeader = emailCreationRequestWithoutHeadersReads.reads(o)
+
+        val specificHeadersEither: Either[IllegalArgumentException, List[EmailHeader]] = o.value.toList
+          .filter {
+            case (name, _) => name.startsWith("header:")
+          }.map {
           case (name, value) =>
             val refinedName: Either[String, NonEmptyString] = refineV[NonEmpty](name)
             refinedName.left.map(e => new IllegalArgumentException(e))
@@ -372,8 +375,10 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
                 .map(headerValue => EmailHeader(EmailHeaderName(specificHeaderRequest.headerName), headerValue)))
         }.sequence
 
-      specificHeadersEither.fold(e => JsError(e.getMessage),
-        specificHeaders => withoutHeader.map(_.toCreationRequest(specificHeaders)))
+        specificHeadersEither.fold(e => JsError(e.getMessage),
+          specificHeaders => withoutHeader.map(_.toCreationRequest(specificHeaders)))
+      }
+
     case _ => JsError("Expecting a JsObject to represent a creation request")
   }
 


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


[james-project] 03/11: JAMES-3474 Email/set should handle state property

Posted by rc...@apache.org.
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


[james-project] 06/11: JAMES-3470 CassandraEmailChangeRepository implementation

Posted by rc...@apache.org.
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 0c6015b92358d0844750652b5dd5c8f049c26698
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Jan 11 10:10:15 2021 +0700

    JAMES-3470 CassandraEmailChangeRepository implementation
---
 .../change/CassandraEmailChangeModule.java         |  58 +++++++
 .../change/CassandraEmailChangeRepository.java     |  56 ++++++-
 .../cassandra/change/EmailChangeRepositoryDAO.java | 172 +++++++++++++++++++++
 .../change/tables/CassandraEmailChangeTable.java   |  31 ++++
 .../change/CassandraEmailChangeRepositoryTest.java |  53 +++++++
 5 files changed, 363 insertions(+), 7 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeModule.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeModule.java
new file mode 100644
index 0000000..abe9f3d
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeModule.java
@@ -0,0 +1,58 @@
+/****************************************************************
+ * 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.cassandra.change;
+
+import static com.datastax.driver.core.DataType.cboolean;
+import static com.datastax.driver.core.DataType.frozenSet;
+import static com.datastax.driver.core.DataType.text;
+import static com.datastax.driver.core.DataType.timeuuid;
+import static com.datastax.driver.core.schemabuilder.SchemaBuilder.Direction.ASC;
+import static com.datastax.driver.core.schemabuilder.SchemaBuilder.KeyCaching.ALL;
+import static com.datastax.driver.core.schemabuilder.SchemaBuilder.frozen;
+import static com.datastax.driver.core.schemabuilder.SchemaBuilder.rows;
+import static org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule.ZONED_DATE_TIME;
+import static org.apache.james.backends.cassandra.utils.CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.ACCOUNT_ID;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.CREATED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.DATE;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.DESTROYED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.IS_DELEGATED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.STATE;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.TABLE_NAME;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.UPDATED;
+
+import org.apache.james.backends.cassandra.components.CassandraModule;
+
+public interface CassandraEmailChangeModule {
+    CassandraModule MODULE = CassandraModule.table(TABLE_NAME)
+        .comment("Holds EmailChange definition. Used to manage Email state in JMAP.")
+        .options(options -> options
+            .clusteringOrder(STATE, ASC)
+            .caching(ALL, rows(DEFAULT_CACHED_ROW_PER_PARTITION)))
+        .statement(statement -> statement
+            .addPartitionKey(ACCOUNT_ID, text())
+            .addClusteringColumn(STATE, timeuuid())
+            .addUDTColumn(DATE, frozen(ZONED_DATE_TIME))
+            .addColumn(IS_DELEGATED, cboolean())
+            .addColumn(CREATED, frozenSet(timeuuid()))
+            .addColumn(UPDATED, frozenSet(timeuuid()))
+            .addColumn(DESTROYED, frozenSet(timeuuid())))
+        .build();
+}
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java
index 1f67589..59b1f77 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepository.java
@@ -21,39 +21,81 @@ package org.apache.james.jmap.cassandra.change;
 
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 import org.apache.james.jmap.api.change.EmailChange;
 import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.EmailChanges;
 import org.apache.james.jmap.api.change.Limit;
 import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.api.exception.ChangeNotFoundException;
 import org.apache.james.jmap.api.model.AccountId;
 
+import com.google.common.base.Preconditions;
+
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class CassandraEmailChangeRepository implements EmailChangeRepository {
+    public static final Limit DEFAULT_NUMBER_OF_CHANGES = Limit.of(5);
+
+    private final EmailChangeRepositoryDAO emailChangeRepositoryDAO;
+
+    @Inject
+    public CassandraEmailChangeRepository(EmailChangeRepositoryDAO emailChangeRepositoryDAO) {
+        this.emailChangeRepositoryDAO = emailChangeRepositoryDAO;
+    }
 
     @Override
     public Mono<Void> save(EmailChange change) {
-        return Mono.empty();
+        return emailChangeRepositoryDAO.insert(change);
     }
 
     @Override
-    public Mono<EmailChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxIdsToReturn) {
-        return Mono.empty();
+    public Mono<EmailChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxChanges) {
+        Preconditions.checkNotNull(accountId);
+        Preconditions.checkNotNull(state);
+        maxChanges.ifPresent(limit -> Preconditions.checkArgument(limit.getValue() > 0, "maxChanges must be a positive integer"));
+
+        if (state.equals(State.INITIAL)) {
+            return emailChangeRepositoryDAO.getAllChanges(accountId)
+                .filter(change -> !change.isDelegated())
+                .collect(new EmailChanges.Builder.EmailChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
+        }
+
+        return emailChangeRepositoryDAO.getChangesSince(accountId, state)
+            .switchIfEmpty(Flux.error(new ChangeNotFoundException(state, String.format("State '%s' could not be found", state.getValue()))))
+            .filter(change -> !change.isDelegated())
+            .filter(change -> !change.getState().equals(state))
+            .collect(new EmailChanges.Builder.EmailChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
     }
 
     @Override
-    public Mono<EmailChanges> getSinceStateWithDelegation(AccountId accountId, State state, Optional<Limit> maxIdsToReturn) {
-        return Mono.empty();
+    public Mono<EmailChanges> getSinceStateWithDelegation(AccountId accountId, State state, Optional<Limit> maxChanges) {
+        Preconditions.checkNotNull(accountId);
+        Preconditions.checkNotNull(state);
+        maxChanges.ifPresent(limit -> Preconditions.checkArgument(limit.getValue() > 0, "maxChanges must be a positive integer"));
+
+        if (state.equals(State.INITIAL)) {
+            return emailChangeRepositoryDAO.getAllChanges(accountId)
+                .collect(new EmailChanges.Builder.EmailChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
+        }
+
+        return emailChangeRepositoryDAO.getChangesSince(accountId, state)
+            .switchIfEmpty(Flux.error(new ChangeNotFoundException(state, String.format("State '%s' could not be found", state.getValue()))))
+            .filter(change -> !change.getState().equals(state))
+            .collect(new EmailChanges.Builder.EmailChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
     }
 
     @Override
     public Mono<State> getLatestState(AccountId accountId) {
-        return Mono.just(State.INITIAL);
+        return emailChangeRepositoryDAO.latestStateNotDelegated(accountId)
+            .switchIfEmpty(Mono.just(State.INITIAL));
     }
 
     @Override
     public Mono<State> getLatestStateWithDelegation(AccountId accountId) {
-        return Mono.just(State.INITIAL);
+        return emailChangeRepositoryDAO.latestState(accountId)
+            .switchIfEmpty(Mono.just(State.INITIAL));
     }
 }
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/EmailChangeRepositoryDAO.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/EmailChangeRepositoryDAO.java
new file mode 100644
index 0000000..4799908
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/EmailChangeRepositoryDAO.java
@@ -0,0 +1,172 @@
+/****************************************************************
+ * 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.cassandra.change;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.asc;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.desc;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.gte;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.ACCOUNT_ID;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.CREATED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.DATE;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.DESTROYED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.IS_DELEGATED;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.STATE;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.TABLE_NAME;
+import static org.apache.james.jmap.cassandra.change.tables.CassandraEmailChangeTable.UPDATED;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
+import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
+import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
+import org.apache.james.jmap.api.change.EmailChange;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.model.MessageId;
+
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.UserType;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class EmailChangeRepositoryDAO {
+    private final CassandraAsyncExecutor executor;
+    private final UserType zonedDateTimeUserType;
+    private final PreparedStatement insertStatement;
+    private final PreparedStatement selectAllStatement;
+    private final PreparedStatement selectFromStatement;
+    private final PreparedStatement selectLatestStatement;
+    private final PreparedStatement selectLatestNotDelegatedStatement;
+
+    @Inject
+    public EmailChangeRepositoryDAO(Session session, CassandraTypesProvider cassandraTypesProvider) {
+        executor = new CassandraAsyncExecutor(session);
+        zonedDateTimeUserType = cassandraTypesProvider.getDefinedUserType(CassandraZonedDateTimeModule.ZONED_DATE_TIME);
+
+        insertStatement = session.prepare(insertInto(TABLE_NAME)
+            .value(ACCOUNT_ID, bindMarker(ACCOUNT_ID))
+            .value(STATE, bindMarker(STATE))
+            .value(DATE, bindMarker(DATE))
+            .value(IS_DELEGATED, bindMarker(IS_DELEGATED))
+            .value(CREATED, bindMarker(CREATED))
+            .value(UPDATED, bindMarker(UPDATED))
+            .value(DESTROYED, bindMarker(DESTROYED)));
+
+        selectAllStatement = session.prepare(select().from(TABLE_NAME)
+            .where(eq(ACCOUNT_ID, bindMarker(ACCOUNT_ID)))
+            .orderBy(asc(STATE)));
+
+        selectFromStatement = session.prepare(select().from(TABLE_NAME)
+            .where(eq(ACCOUNT_ID, bindMarker(ACCOUNT_ID)))
+            .and(gte(STATE, bindMarker(STATE)))
+            .orderBy(asc(STATE)));
+
+        selectLatestStatement = session.prepare(select(STATE)
+            .from(TABLE_NAME)
+            .where(eq(ACCOUNT_ID, bindMarker(ACCOUNT_ID)))
+            .orderBy(desc(STATE))
+            .limit(1));
+
+        selectLatestNotDelegatedStatement = session.prepare(select(STATE)
+            .from(TABLE_NAME)
+            .where(eq(ACCOUNT_ID, bindMarker(ACCOUNT_ID)))
+            .and(eq(IS_DELEGATED, false))
+            .orderBy(desc(STATE))
+            .limit(1)
+            .allowFiltering());
+    }
+
+    Mono<Void> insert(EmailChange change) {
+        return executor.executeVoid(insertStatement.bind()
+            .setString(ACCOUNT_ID, change.getAccountId().getIdentifier())
+            .setUUID(STATE, change.getState().getValue())
+            .setBool(IS_DELEGATED, change.isDelegated())
+            .setSet(CREATED, toUuidSet(change.getCreated()), UUID.class)
+            .setSet(UPDATED, toUuidSet(change.getUpdated()), UUID.class)
+            .setSet(DESTROYED, toUuidSet(change.getDestroyed()), UUID.class)
+            .setUDTValue(DATE, CassandraZonedDateTimeModule.toUDT(zonedDateTimeUserType, change.getDate())));
+    }
+
+    private ImmutableSet<UUID> toUuidSet(List<MessageId> idSet) {
+        return idSet.stream()
+            .filter(CassandraMessageId.class::isInstance)
+            .map(CassandraMessageId.class::cast)
+            .map(CassandraMessageId::get)
+            .collect(Guavate.toImmutableSet());
+    }
+
+    Flux<EmailChange> getAllChanges(AccountId accountId) {
+        return executor.executeRows(selectAllStatement.bind()
+            .setString(ACCOUNT_ID, accountId.getIdentifier()))
+            .map(this::readRow);
+    }
+
+    Flux<EmailChange> getChangesSince(AccountId accountId, State state) {
+        return executor.executeRows(selectFromStatement.bind()
+            .setString(ACCOUNT_ID, accountId.getIdentifier())
+            .setUUID(STATE, state.getValue()))
+            .map(this::readRow);
+    }
+
+    Mono<State> latestState(AccountId accountId) {
+        return executor.executeSingleRow(selectLatestStatement.bind()
+            .setString(ACCOUNT_ID, accountId.getIdentifier()))
+            .map(row -> State.of(row.getUUID(STATE)));
+    }
+
+    Mono<State> latestStateNotDelegated(AccountId accountId) {
+        return executor.executeSingleRow(selectLatestNotDelegatedStatement.bind()
+            .setString(ACCOUNT_ID, accountId.getIdentifier()))
+            .map(row -> State.of(row.getUUID(STATE)));
+    }
+
+    private EmailChange readRow(Row row) {
+        return EmailChange.builder()
+            .accountId(AccountId.fromString(row.getString(ACCOUNT_ID)))
+            .state(State.of(row.getUUID(STATE)))
+            .date(CassandraZonedDateTimeModule.fromUDT(row.getUDTValue(DATE)))
+            .isDelegated(row.getBool(IS_DELEGATED))
+            .created(toIdSet(row.getSet(CREATED, UUID.class)))
+            .updated(toIdSet(row.getSet(UPDATED, UUID.class)))
+            .destroyed(toIdSet(row.getSet(DESTROYED, UUID.class)))
+            .build();
+    }
+
+    private ImmutableList<MessageId> toIdSet(Set<UUID> uuidSet) {
+        return uuidSet.stream()
+            .map(CassandraMessageId.Factory::of)
+            .collect(Guavate.toImmutableList());
+    }
+}
diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/tables/CassandraEmailChangeTable.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/tables/CassandraEmailChangeTable.java
new file mode 100644
index 0000000..945f038
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/tables/CassandraEmailChangeTable.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * 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.cassandra.change.tables;
+
+public interface CassandraEmailChangeTable {
+    String TABLE_NAME = "email_change";
+    String ACCOUNT_ID = "account_id";
+    String STATE = "state";
+    String DATE = "date";
+    String IS_DELEGATED = "is_delegated";
+    String CREATED = "created";
+    String UPDATED = "updated";
+    String DESTROYED = "destroyed";
+}
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java
new file mode 100644
index 0000000..50af3ff
--- /dev/null
+++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.cassandra.change;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.jmap.api.change.EmailChangeRepository;
+import org.apache.james.jmap.api.change.EmailChangeRepositoryContract;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class CassandraEmailChangeRepositoryTest implements EmailChangeRepositoryContract {
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
+        CassandraModule.aggregateModules(CassandraEmailChangeModule.MODULE,
+            CassandraSchemaVersionModule.MODULE,
+            CassandraZonedDateTimeModule.MODULE));
+
+    EmailChangeRepository emailChangeRepository;
+    EmailChangeRepositoryDAO emailChangeRepositoryDAO;
+
+    @BeforeEach
+    public void setUp(CassandraCluster cassandra) {
+        emailChangeRepositoryDAO = new EmailChangeRepositoryDAO(cassandra.getConf(), cassandra.getTypesProvider());
+        emailChangeRepository = new CassandraEmailChangeRepository(emailChangeRepositoryDAO);
+    }
+
+    @Override
+    public EmailChangeRepository emailChangeRepository() {
+        return emailChangeRepository;
+    }
+}


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


[james-project] 04/11: JAMES-3474 New/old state should be ignored in irrelevant Email/set calls

Posted by rc...@apache.org.
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 716b3037bc1e69aa39bd2fe66a4856cf72e3671f
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Fri Jan 8 11:22:40 2021 +0700

    JAMES-3474 New/old state should be ignored in irrelevant Email/set calls
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 239 ++++++++++++---------
 .../EmailSubmissionSetMethodContract.scala         |   4 +
 2 files changed, 140 insertions(+), 103 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 2277986..55f1b10 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
@@ -30,8 +30,11 @@ import io.restassured.builder.ResponseSpecBuilder
 import io.restassured.http.ContentType.JSON
 import javax.mail.Flags
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import net.javacrumbs.jsonunit.core.Option
+import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.{SC_CREATED, SC_OK}
 import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.api.change.State
 import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
 import org.apache.james.jmap.core.State.INSTANCE
 import org.apache.james.jmap.core.UTCDate
@@ -3076,7 +3079,9 @@ trait EmailSetMethodContract {
            |}""".stripMargin)
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |  "sessionState": "${SESSION_STATE.value}",
@@ -3188,14 +3193,15 @@ trait EmailSetMethodContract {
       .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |  "sessionState": "${SESSION_STATE.value}",
            |  "methodResponses": [
            |    ["Email/set", {
            |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "${INSTANCE.value}",
+           |      "oldState": "${State.INITIAL.getValue}",
            |      "created": {
            |        "aaaaaa": {
            |          "id": "$messageId",
@@ -4703,14 +4709,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "destroyed": ["${messageId.serialize}"]
            |      }, "c1"],
            |      ["Email/get", {
@@ -4749,21 +4756,23 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
-      s"""{
-         |    "sessionState": "${SESSION_STATE.value}",
-         |    "methodResponses": [
-         |      ["Email/set", {
-         |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "newState": "${INSTANCE.value}",
-         |        "notDestroyed": {
-         |          "invalid": {
-         |            "type": "invalidArguments",
-         |            "description": "invalid is not a messageId: ${invalidMessageIdMessage("invalid")}"
-         |          }
-         |        }
-         |      }, "c1"]]
-         |}""".stripMargin)
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |      ["Email/set", {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "notDestroyed": {
+           |          "invalid": {
+           |            "type": "invalidArguments",
+           |            "description": "invalid is not a messageId: ${invalidMessageIdMessage("invalid")}"
+           |          }
+           |        }
+           |      }, "c1"]]
+           |}""".stripMargin)
   }
 
   @Test
@@ -4794,21 +4803,23 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
-      s"""{
-         |    "sessionState": "${SESSION_STATE.value}",
-         |    "methodResponses": [
-         |      ["Email/set", {
-         |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "newState": "${INSTANCE.value}",
-         |        "notDestroyed": {
-         |          "${messageId.serialize}": {
-         |            "type": "notFound",
-         |            "description": "Cannot find message with messageId: ${messageId.serialize}"
-         |          }
-         |        }
-         |      }, "c1"]]
-         |}""".stripMargin)
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |      ["Email/set", {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "notDestroyed": {
+           |          "${messageId.serialize}": {
+           |            "type": "notFound",
+           |            "description": "Cannot find message with messageId: ${messageId.serialize}"
+           |          }
+           |        }
+           |      }, "c1"]]
+           |}""".stripMargin)
   }
 
   @Test
@@ -4846,22 +4857,24 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
-      s"""{
-         |    "sessionState": "${SESSION_STATE.value}",
-         |    "methodResponses": [
-         |      ["Email/set", {
-         |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "newState": "${INSTANCE.value}",
-         |        "notDestroyed": {
-         |          "${messageId.serialize}": {
-         |            "type": "notFound",
-         |            "description": "Cannot find message with messageId: ${messageId.serialize}"
-         |          }
-         |        }
-         |      }, "c1"]
-         |    ]
-         |}""".stripMargin)
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |      ["Email/set", {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "notDestroyed": {
+           |          "${messageId.serialize}": {
+           |            "type": "notFound",
+           |            "description": "Cannot find message with messageId: ${messageId.serialize}"
+           |          }
+           |        }
+           |      }, "c1"]
+           |    ]
+           |}""".stripMargin)
   }
 
   @Test
@@ -4905,22 +4918,24 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
-      s"""{
-         |    "sessionState": "${SESSION_STATE.value}",
-         |    "methodResponses": [
-         |      ["Email/set", {
-         |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "newState": "${INSTANCE.value}",
-         |        "notDestroyed": {
-         |          "${messageId.serialize}": {
-         |            "type": "notFound",
-         |            "description": "Cannot find message with messageId: ${messageId.serialize}"
-         |          }
-         |        }
-         |      }, "c1"]
-         |    ]
-         |}""".stripMargin)
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |      ["Email/set", {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "notDestroyed": {
+           |          "${messageId.serialize}": {
+           |            "type": "notFound",
+           |            "description": "Cannot find message with messageId: ${messageId.serialize}"
+           |          }
+           |        }
+           |      }, "c1"]
+           |    ]
+           |}""".stripMargin)
   }
 
   @Test
@@ -4970,14 +4985,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "destroyed": ["${messageId.serialize}"]
            |      }, "c1"],
            |      ["Email/get", {
@@ -5041,14 +5057,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "destroyed": ["${messageId.serialize}"]
            |      }, "c1"],
            |      ["Email/get", {
@@ -5364,23 +5381,25 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
-      s"""{
-         |    "sessionState": "${SESSION_STATE.value}",
-         |    "methodResponses": [
-         |      ["Email/set", {
-         |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "newState": "${INSTANCE.value}",
-         |        "destroyed": ["${messageId.serialize}"],
-         |        "notDestroyed": {
-         |          "invalid": {
-         |            "type": "invalidArguments",
-         |            "description": "invalid is not a messageId: ${invalidMessageIdMessage("invalid")}"
-         |          }
-         |        }
-         |      }, "c1"]
-         |    ]
-         |}""".stripMargin)
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |      ["Email/set", {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "destroyed": ["${messageId.serialize}"],
+           |        "notDestroyed": {
+           |          "invalid": {
+           |            "type": "invalidArguments",
+           |            "description": "invalid is not a messageId: ${invalidMessageIdMessage("invalid")}"
+           |          }
+           |        }
+           |      }, "c1"]
+           |    ]
+           |}""".stripMargin)
   }
 
   @Test
@@ -5434,7 +5453,9 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
@@ -5523,14 +5544,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "updated": {
            |          "${messageId1.serialize}": null,
            |          "${messageId2.serialize}": null
@@ -5603,13 +5625,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |        ["Email/set", {
            |          "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |          "newState": "${INSTANCE.value}",
            |          "notUpdated": {
            |            "${messageId.serialize}": {
            |              "type": "notFound",
@@ -5675,7 +5698,9 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
@@ -5760,13 +5785,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "notFound",
@@ -5846,14 +5872,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "updated": {
            |          "${messageId.serialize}": null
            |        }
@@ -5929,14 +5956,15 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].state")
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState",
+        "methodResponses[1][1].state")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "updated": {
            |          "${messageId.serialize}": null
            |        }
@@ -6006,13 +6034,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "updated": {
            |          "${messageId.serialize}": null
            |        }
@@ -6069,13 +6098,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "invalidPatch",
@@ -6130,13 +6160,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "invalidPatch",
@@ -6191,13 +6222,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "invalidPatch",
@@ -6269,13 +6301,14 @@ trait EmailSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].oldState",
+        "methodResponses[0][1].newState")
       .isEqualTo(
         s"""{
            |    "sessionState": "${SESSION_STATE.value}",
            |    "methodResponses": [
            |      ["Email/set", {
            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "newState": "${INSTANCE.value}",
            |        "updated": {
            |          "${messageId1.serialize}": null
            |        },
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 4ea493c..6e27196 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
@@ -886,6 +886,8 @@ trait EmailSubmissionSetMethodContract {
     assertThatJson(response)
       // Ids are randomly generated, and not stored, let's ignore it
       .whenIgnoringPaths("methodResponses[0][1].created.k1490",
+        "methodResponses[1][1].newState",
+        "methodResponses[1][1].oldState",
         "methodResponses[2][1].state")
       .isEqualTo(s"""{
                    |    "sessionState": "${SESSION_STATE.value}",
@@ -988,6 +990,8 @@ trait EmailSubmissionSetMethodContract {
     assertThatJson(response)
       // Ids are randomly generated, and not stored, let's ignore it
       .whenIgnoringPaths("methodResponses[0][1].created.k1490",
+        "methodResponses[1][1].newState",
+        "methodResponses[1][1].oldState",
         "methodResponses[2][1].state")
       .isEqualTo(s"""{
                    |    "sessionState": "${SESSION_STATE.value}",


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


[james-project] 01/11: JAMES-3436 Email/set method contract fix up

Posted by rc...@apache.org.
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 c2cd494742565bb6d62c7f5a115ca78555bf7889
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Wed Jan 13 11:34:46 2021 +0700

    JAMES-3436 Email/set method contract fix up
---
 .../apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala   | 4 ++--
 1 file changed, 2 insertions(+), 2 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 57285ac..b39614d 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
@@ -6085,7 +6085,7 @@ trait EmailSetMethodContract {
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "invalidPatch",
-           |            "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${messageId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
+           |            "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${mailboxId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
            |          }
            |        }
            |      }, "c1"]
@@ -6146,7 +6146,7 @@ trait EmailSetMethodContract {
            |        "notUpdated": {
            |          "${messageId.serialize}": {
            |            "type": "invalidPatch",
-           |            "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${messageId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
+           |            "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${mailboxId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
            |          }
            |        }
            |      }, "c1"]


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


[james-project] 05/11: JAMES-3474 Disable asynchronous state tests for distributed environment

Posted by rc...@apache.org.
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 c4d993331ee071b25584cbe12940ba2ee5728a7a
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Jan 12 11:02:46 2021 +0700

    JAMES-3474 Disable asynchronous state tests for distributed environment
---
 .../rfc8621/distributed/DistributedEmailSetMethodTest.java  | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSetMethodTest.java
index ec07ea6..b103eac 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSetMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSetMethodTest.java
@@ -23,6 +23,7 @@ import org.apache.james.CassandraExtension;
 import org.apache.james.CassandraRabbitMQJamesConfiguration;
 import org.apache.james.CassandraRabbitMQJamesServerMain;
 import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.rfc8621.contract.EmailSetMethodContract;
@@ -32,6 +33,8 @@ import org.apache.james.modules.AwsS3BlobStoreExtension;
 import org.apache.james.modules.RabbitMQExtension;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import com.datastax.driver.core.utils.UUIDs;
@@ -67,4 +70,14 @@ public class DistributedEmailSetMethodTest implements EmailSetMethodContract {
     public String invalidMessageIdMessage(String invalid) {
         return String.format("Invalid UUID string: %s", invalid);
     }
+
+    @Override
+    @Test
+    @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Email/set call")
+    public void newStateShouldBeUpToDate(GuiceJamesServer server) {}
+
+    @Override
+    @Test
+    @Disabled("Distributed event bus is asynchronous, we cannot expect the newState to be returned immediately after Email/set call")
+    public void oldStateShouldIncludeSetChanges(GuiceJamesServer server) {}
 }


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


[james-project] 09/11: JAMES-3470 Adapt Email/set test for more stability

Posted by rc...@apache.org.
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 5de9000d9a8f7eff427ff9fb64c76f7a7e1361ce
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Tue Jan 12 14:23:39 2021 +0700

    JAMES-3470 Adapt Email/set test for more stability
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 48 ++++++++++++++--------
 1 file changed, 30 insertions(+), 18 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 55f1b10..9056fdd 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
@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
 import java.util.Date
+import java.util.concurrent.TimeUnit
 
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, `with`, requestSpecification}
@@ -51,6 +52,8 @@ 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.awaitility.Awaitility
+import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS
 import org.hamcrest.Matchers.{equalTo, not}
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.junit.jupiter.params.ParameterizedTest
@@ -60,6 +63,12 @@ import play.api.libs.json.{JsNumber, JsString, Json}
 import scala.jdk.CollectionConverters._
 
 trait EmailSetMethodContract {
+  private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
+  private lazy val calmlyAwait = Awaitility.`with`
+    .pollInterval(slowPacedPollInterval)
+    .and.`with`.pollDelay(slowPacedPollInterval)
+    .await
+  private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
   private lazy val UTC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")
 
   @BeforeEach
@@ -6546,24 +6555,27 @@ trait EmailSetMethodContract {
           .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)))
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      `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 = {


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


[james-project] 11/11: JAMES-3472 Add EmailChangesMethod integration test for distributed environment

Posted by rc...@apache.org.
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 b929cdbdbb544c4fc800609b790f7e12efc628a0
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Jan 11 11:54:41 2021 +0700

    JAMES-3472 Add EmailChangesMethod integration test for distributed environment
---
 .../DistributedEmailChangeMethodTest.java          | 60 ++++++++++++++++++++++
 .../contract/EmailChangesMethodContract.scala      |  4 +-
 2 files changed, 63 insertions(+), 1 deletion(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailChangeMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailChangeMethodTest.java
new file mode 100644
index 0000000..d471d81
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailChangeMethodTest.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * 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.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.cassandra.change.CassandraStateFactory;
+import org.apache.james.jmap.rfc8621.contract.EmailChangesMethodContract;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DistributedEmailChangeMethodTest implements EmailChangesMethodContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .s3()
+                .disableCache()
+                .deduplication())
+            .build())
+        .extension(new DockerElasticSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+
+    @Override
+    public State.Factory stateFactory() {
+        return new CassandraStateFactory();
+    }
+}
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/EmailChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
index 2f65896..83d050d 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
@@ -40,7 +40,7 @@ import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.model.MailboxACL.Right
-import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageId}
+import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
 import org.apache.james.mime4j.dom.Message
 import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
 import org.apache.james.utils.DataProbeImpl
@@ -971,6 +971,8 @@ trait EmailChangesMethodContract {
       .build
     mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message))
 
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+
     val request1 =
       s"""{
          |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],


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


[james-project] 10/11: JAMES-3472 Remove unused method

Posted by rc...@apache.org.
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 a046adfc27f8b6cfd5517e47e053b6d3c472ae0d
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Jan 11 11:53:42 2021 +0700

    JAMES-3472 Remove unused method
---
 .../james/jmap/rfc8621/contract/EmailChangesMethodContract.scala | 1 -
 .../james/jmap/rfc8621/memory/MemoryEmailChangesMethodTest.java  | 9 ---------
 2 files changed, 10 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/EmailChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
index 0817ebb..2f65896 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
@@ -60,7 +60,6 @@ trait EmailChangesMethodContract {
   private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
 
   def stateFactory: State.Factory
-  def generateMailboxId: MailboxId
 
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
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/MemoryEmailChangesMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailChangesMethodTest.java
index a0ce35a..79634b0 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailChangesMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailChangesMethodTest.java
@@ -21,15 +21,11 @@ package org.apache.james.jmap.rfc8621.memory;
 
 import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
 
-import java.util.concurrent.ThreadLocalRandom;
-
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.api.change.State;
 import org.apache.james.jmap.rfc8621.contract.EmailChangesMethodContract;
-import org.apache.james.mailbox.inmemory.InMemoryId;
-import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -45,9 +41,4 @@ public class MemoryEmailChangesMethodTest implements EmailChangesMethodContract
     public State.Factory stateFactory() {
         return State.Factory.DEFAULT;
     }
-
-    @Override
-    public MailboxId generateMailboxId() {
-        return InMemoryId.of(ThreadLocalRandom.current().nextInt());
-    }
 }


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


[james-project] 08/11: JAMES-3470 EmailChangeRepositoryContract should stop relying on TestMessageId

Posted by rc...@apache.org.
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 4c16e1ff84299fefea9ffe58bcea54ff2e7f0029
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Jan 11 11:39:37 2021 +0700

    JAMES-3470 EmailChangeRepositoryContract should stop relying on TestMessageId
---
 .../mailbox/cassandra/ids/CassandraMessageId.java  |   2 +-
 .../change/CassandraEmailChangeRepositoryTest.java |  13 +
 .../api/change/EmailChangeRepositoryContract.java  | 699 +++++++++++++--------
 .../change/MemoryEmailChangeRepositoryTest.java    |  16 +-
 4 files changed, 462 insertions(+), 268 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/CassandraMessageId.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/CassandraMessageId.java
index cf7cb63..12be3fd 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/CassandraMessageId.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/ids/CassandraMessageId.java
@@ -36,7 +36,7 @@ public class CassandraMessageId implements MessageId {
             return of(UUIDs.timeBased());
         }
 
-        public CassandraMessageId of(UUID uuid) {
+        public static CassandraMessageId of(UUID uuid) {
             return new CassandraMessageId(uuid);
         }
 
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java
index 50af3ff..8094155 100644
--- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java
+++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/change/CassandraEmailChangeRepositoryTest.java
@@ -26,6 +26,9 @@ import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.EmailChangeRepositoryContract;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.model.MessageId;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -50,4 +53,14 @@ public class CassandraEmailChangeRepositoryTest implements EmailChangeRepository
     public EmailChangeRepository emailChangeRepository() {
         return emailChangeRepository;
     }
+
+    @Override
+    public State generateNewState() {
+        return new CassandraStateFactory().generate();
+    }
+
+    @Override
+    public MessageId generateNewMessageId() {
+        return new CassandraMessageId.Factory().generate();
+    }
 }
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/EmailChangeRepositoryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/EmailChangeRepositoryContract.java
index fc0ac24..bf0ca99 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/EmailChangeRepositoryContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/EmailChangeRepositoryContract.java
@@ -26,20 +26,20 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.ZonedDateTime;
 import java.util.Optional;
-import java.util.UUID;
 
 import org.apache.james.jmap.api.exception.ChangeNotFoundException;
 import org.apache.james.jmap.api.model.AccountId;
-import org.apache.james.mailbox.model.TestMessageId;
+import org.apache.james.mailbox.model.MessageId;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Test;
 
 public interface EmailChangeRepositoryContract {
     AccountId ACCOUNT_ID = AccountId.fromUsername(BOB);
     ZonedDateTime DATE = ZonedDateTime.now();
-    State STATE_0 = State.of(UUID.randomUUID());
 
     EmailChangeRepository emailChangeRepository();
+    MessageId generateNewMessageId();
+    State generateNewState();
 
     @Test
     default void saveChangeShouldSuccess() {
@@ -47,10 +47,10 @@ public interface EmailChangeRepositoryContract {
 
         EmailChange change = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
 
         assertThatCode(() -> repository.save(change).block())
@@ -69,26 +69,30 @@ public interface EmailChangeRepositoryContract {
     default void getLatestStateShouldReturnLastPersistedState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId1)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId2)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId3)
             .build();
         repository.save(change1).block();
         repository.save(change2).block();
@@ -102,26 +106,30 @@ public interface EmailChangeRepositoryContract {
     default void getLatestStateShouldReturnLastNonDelegatedPersistedState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId1)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId2)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(true)
-            .created(TestMessageId.of(4))
+            .created(messageId3)
             .build();
         repository.save(change1).block();
         repository.save(change2).block();
@@ -135,41 +143,45 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldSuccess() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        MessageId messageId = generateNewMessageId();
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId)
             .build();
         EmailChange change = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(1))
+            .updated(messageId)
             .build();
         repository.save(oldState).block();
         repository.save(change).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change.getUpdated());
     }
 
     @Test
     default void getChangesShouldReturnEmptyWhenNoNewerState() {
         EmailChangeRepository repository = emailChangeRepository();
+        State state = generateNewState();
 
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         repository.save(oldState).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .isEmpty();
     }
 
@@ -177,16 +189,18 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldReturnCurrentStateWhenNoNewerState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         repository.save(oldState).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.empty()).block().getNewState())
             .isEqualTo(oldState.getState());
     }
 
@@ -194,40 +208,47 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldLimitChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId3)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId4)
             .build();
         EmailChange change4 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.plusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(5))
+            .created(messageId5)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
@@ -235,41 +256,47 @@ public interface EmailChangeRepositoryContract {
         repository.save(change3).block();
         repository.save(change4).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4));
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block().getCreated())
+            .containsExactlyInAnyOrder(messageId2, messageId3, messageId4);
     }
 
     @Test
     default void getChangesShouldReturnAllFromInitial() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId3)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId4)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
@@ -277,83 +304,102 @@ public interface EmailChangeRepositoryContract {
         repository.save(change3).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(1), TestMessageId.of(2), TestMessageId.of(3));
+            .containsExactlyInAnyOrder(messageId1, messageId2, messageId3);
     }
 
     @Test
     default void getChangesFromInitialShouldReturnNewState() {
         EmailChangeRepository repository = emailChangeRepository();
-        State state2 = State.of(UUID.randomUUID());
 
-        EmailChange oldState = EmailChange.builder()
+        State state = generateNewState();
+        State state2 = generateNewState();
+        State state3 = generateNewState();
+        State state4 = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+
+        EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
-        EmailChange change1 = EmailChange.builder()
+        EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(state2)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId2)
             .build();
-        EmailChange change2 = EmailChange.builder()
+        EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(state2)
+            .state(state3)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId3)
             .build();
-        EmailChange change3 = EmailChange.builder()
+        EmailChange change4 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(state4)
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId4)
             .build();
 
-        repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
         repository.save(change3).block();
+        repository.save(change4).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getNewState())
-            .isEqualTo(state2);
+            .isEqualTo(state3);
     }
 
     @Test
     default void getChangesShouldLimitChangesWhenMaxChangesOmitted() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+        MessageId messageId6 = generateNewMessageId();
+        MessageId messageId7 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5), TestMessageId.of(6))
+            .created(messageId2, messageId3, messageId4, messageId5, messageId6)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(7))
+            .created(messageId7)
             .build();
 
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
     }
 
@@ -361,32 +407,40 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldNotReturnMoreThanMaxChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(messageId2, messageId3)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId4, messageId5)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getAllChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
     }
 
@@ -394,31 +448,39 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldReturnEmptyWhenNumberOfChangesExceedMaxChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(messageId2, messageId3)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId4, messageId5)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().getAllChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(1))).block().getAllChanges())
             .isEmpty();
     }
 
@@ -426,32 +488,38 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldReturnNewState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(messageId2, messageId3)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(messageId2, messageId3)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.empty()).block().getNewState())
             .isEqualTo(change2.getState());
     }
 
@@ -459,32 +527,38 @@ public interface EmailChangeRepositoryContract {
     default void hasMoreChangesShouldBeTrueWhenMoreChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(messageId2, messageId3)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(messageId2, messageId3)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().hasMoreChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(1))).block().hasMoreChanges())
             .isTrue();
     }
 
@@ -492,32 +566,38 @@ public interface EmailChangeRepositoryContract {
     default void hasMoreChangesShouldBeFalseWhenNoMoreChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(messageId2, messageId3)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(messageId2, messageId3)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(4))).block().hasMoreChanges())
+        assertThat(repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(4))).block().hasMoreChanges())
             .isFalse();
     }
 
@@ -525,50 +605,62 @@ public interface EmailChangeRepositoryContract {
     default void changesShouldBeStoredInTheirRespectiveType() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+        MessageId messageId6 = generateNewMessageId();
+        MessageId messageId7 = generateNewMessageId();
+        MessageId messageId8 = generateNewMessageId();
+        MessageId messageId9 = generateNewMessageId();
+        MessageId messageId10 = generateNewMessageId();
 
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1), TestMessageId.of(9), TestMessageId.of(10))
+            .created(messageId1, messageId9, messageId10)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId2, messageId3, messageId4, messageId5)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(6), TestMessageId.of(7))
-            .updated(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(10))
-            .destroyed(TestMessageId.of(4), TestMessageId.of(9))
+            .created(messageId6, messageId7)
+            .updated(messageId2, messageId3, messageId10)
+            .destroyed(messageId4, messageId9)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(8))
-            .updated(TestMessageId.of(6), TestMessageId.of(7), TestMessageId.of(1))
-            .destroyed(TestMessageId.of(5), TestMessageId.of(10))
+            .created(messageId8)
+            .updated(messageId6, messageId7, messageId1)
+            .destroyed(messageId5, messageId10)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
         repository.save(change3).block();
 
-        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
+        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(20))).block();
 
         SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(emailChanges.getCreated()).containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(6), TestMessageId.of(7), TestMessageId.of(8));
-            softly.assertThat(emailChanges.getUpdated()).containsExactlyInAnyOrder(TestMessageId.of(1));
-            softly.assertThat(emailChanges.getDestroyed()).containsExactlyInAnyOrder(TestMessageId.of(9), TestMessageId.of(10));
+            softly.assertThat(emailChanges.getCreated()).containsExactlyInAnyOrder(messageId2, messageId3, messageId6, messageId7, messageId8);
+            softly.assertThat(emailChanges.getUpdated()).containsExactlyInAnyOrder(messageId1);
+            softly.assertThat(emailChanges.getDestroyed()).containsExactlyInAnyOrder(messageId9, messageId10);
         });
     }
 
@@ -576,80 +668,96 @@ public interface EmailChangeRepositoryContract {
     default void changesShouldNotReturnDelegatedChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+        MessageId messageId6 = generateNewMessageId();
+        MessageId messageId7 = generateNewMessageId();
+        MessageId messageId8 = generateNewMessageId();
 
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId2, messageId3, messageId4, messageId5)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(true)
-            .created(TestMessageId.of(6), TestMessageId.of(7))
+            .created(messageId6, messageId7)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(8))
+            .created(messageId8)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
         repository.save(change3).block();
 
-        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
+        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(20))).block();
 
         assertThat(emailChanges.getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5), TestMessageId.of(8));
+            .containsExactlyInAnyOrder(messageId2, messageId3, messageId4, messageId5, messageId8);
     }
 
     @Test
     default void getChangesShouldIgnoreDuplicatedValues() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .updated(TestMessageId.of(1), TestMessageId.of(2))
+            .updated(messageId1, messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(1), TestMessageId.of(2))
-            .created(TestMessageId.of(3))
+            .updated(messageId1, messageId2)
+            .created(messageId3)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block();
+        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block();
         SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(emailChanges.getUpdated()).containsExactly(TestMessageId.of(1), TestMessageId.of(2));
-            softly.assertThat(emailChanges.getCreated()).containsExactly(TestMessageId.of(3));
+            softly.assertThat(emailChanges.getUpdated()).containsExactly(messageId1, messageId2);
+            softly.assertThat(emailChanges.getCreated()).containsExactly(messageId3);
         });
     }
 
@@ -657,7 +765,7 @@ public interface EmailChangeRepositoryContract {
     default void getChangesShouldFailWhenSinceStateNotFound() {
         EmailChangeRepository repository = emailChangeRepository();
 
-        assertThatThrownBy(() -> repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block())
+        assertThatThrownBy(() -> repository.getSinceState(ACCOUNT_ID, generateNewState(), Optional.empty()).block())
             .isInstanceOf(ChangeNotFoundException.class);
     }
 
@@ -675,24 +783,24 @@ public interface EmailChangeRepositoryContract {
 
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(generateNewMessageId())
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(generateNewMessageId())
             .build();
         repository.save(change1).block();
         repository.save(change2).block();
@@ -708,24 +816,24 @@ public interface EmailChangeRepositoryContract {
 
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(generateNewMessageId())
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(true)
-            .created(TestMessageId.of(4))
+            .created(generateNewMessageId())
             .build();
         repository.save(change1).block();
         repository.save(change2).block();
@@ -739,24 +847,26 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldSuccess() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(1))
+            .updated(generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change.getUpdated());
     }
 
@@ -764,16 +874,18 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnEmptyWhenNoNewerState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         repository.save(oldState).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .isEmpty();
     }
 
@@ -781,16 +893,18 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnCurrentStateWhenNoNewerState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         repository.save(oldState).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.empty()).block().getNewState())
             .isEqualTo(oldState.getState());
     }
 
@@ -798,40 +912,48 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldLimitChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId3)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId4)
             .build();
         EmailChange change4 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.plusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(5))
+            .created(messageId5)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
@@ -839,41 +961,46 @@ public interface EmailChangeRepositoryContract {
         repository.save(change3).block();
         repository.save(change4).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4));
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block().getCreated())
+            .containsExactlyInAnyOrder(messageId2, messageId3, messageId4);
     }
 
     @Test
     default void getSinceStateWithDelegationShouldReturnAllFromInitial() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(generateNewState())
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2))
+            .created(messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(3))
+            .created(messageId3)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4))
+            .created(messageId4)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
@@ -881,40 +1008,42 @@ public interface EmailChangeRepositoryContract {
         repository.save(change3).block();
 
         assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(1), TestMessageId.of(2), TestMessageId.of(3));
+            .containsExactlyInAnyOrder(messageId1, messageId2, messageId3);
     }
 
     @Test
     default void getSinceStateWithDelegationShouldLimitChangesWhenMaxChangesOmitted() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5), TestMessageId.of(6))
+            .created(generateNewMessageId(), generateNewMessageId(), generateNewMessageId(), generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(7))
+            .created(generateNewMessageId())
             .build();
 
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
     }
 
@@ -922,32 +1051,34 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldNotReturnMoreThanMaxChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4), TestMessageId.of(5))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getAllChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
     }
 
@@ -955,31 +1086,33 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnEmptyWhenNumberOfChangesExceedMaxChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(4), TestMessageId.of(5))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().getAllChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(1))).block().getAllChanges())
             .isEmpty();
     }
 
@@ -987,32 +1120,34 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnNewState() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(generateNewMessageId(), generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.empty()).block().getNewState())
             .isEqualTo(change2.getState());
     }
 
@@ -1020,32 +1155,34 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationHasMoreChangesShouldBeTrueWhenMoreChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(generateNewMessageId(), generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().hasMoreChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(1))).block().hasMoreChanges())
             .isTrue();
     }
 
@@ -1053,32 +1190,34 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationHasMoreChangesShouldBeFalseWhenNoMoreChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(generateNewMessageId())
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3))
+            .created(generateNewMessageId(), generateNewMessageId())
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(2), TestMessageId.of(3))
+            .updated(generateNewMessageId(), generateNewMessageId())
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(4))).block().hasMoreChanges())
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(4))).block().hasMoreChanges())
             .isFalse();
     }
 
@@ -1086,49 +1225,62 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnChangesInTheirRespectiveType() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+        MessageId messageId6 = generateNewMessageId();
+        MessageId messageId7 = generateNewMessageId();
+        MessageId messageId8 = generateNewMessageId();
+        MessageId messageId9 = generateNewMessageId();
+        MessageId messageId10 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1), TestMessageId.of(9), TestMessageId.of(10))
+            .created(messageId1, messageId9, messageId10)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId2, messageId3, messageId4, messageId5)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .created(TestMessageId.of(6), TestMessageId.of(7))
-            .updated(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(10))
-            .destroyed(TestMessageId.of(4), TestMessageId.of(9))
+            .created(messageId6, messageId7)
+            .updated(messageId2, messageId3, messageId10)
+            .destroyed(messageId4, messageId9)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(8))
-            .updated(TestMessageId.of(6), TestMessageId.of(7), TestMessageId.of(1))
-            .destroyed(TestMessageId.of(5), TestMessageId.of(10))
+            .created(messageId8)
+            .updated(messageId6, messageId7, messageId1)
+            .destroyed(messageId5, messageId10)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
         repository.save(change3).block();
 
-        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
+        EmailChanges emailChanges = repository.getSinceState(ACCOUNT_ID, state, Optional.of(Limit.of(20))).block();
 
         SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(emailChanges.getCreated()).containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(6), TestMessageId.of(7), TestMessageId.of(8));
-            softly.assertThat(emailChanges.getUpdated()).containsExactlyInAnyOrder(TestMessageId.of(1));
-            softly.assertThat(emailChanges.getDestroyed()).containsExactlyInAnyOrder(TestMessageId.of(9), TestMessageId.of(10));
+            softly.assertThat(emailChanges.getCreated()).containsExactlyInAnyOrder(messageId2, messageId3, messageId6, messageId7, messageId8);
+            softly.assertThat(emailChanges.getUpdated()).containsExactlyInAnyOrder(messageId1);
+            softly.assertThat(emailChanges.getDestroyed()).containsExactlyInAnyOrder(messageId9, messageId10);
         });
     }
 
@@ -1136,80 +1288,95 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldReturnDelegatedChanges() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+        MessageId messageId4 = generateNewMessageId();
+        MessageId messageId5 = generateNewMessageId();
+        MessageId messageId6 = generateNewMessageId();
+        MessageId messageId7 = generateNewMessageId();
+        MessageId messageId8 = generateNewMessageId();
 
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(3))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5))
+            .created(messageId2, messageId3, messageId4, messageId5)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(true)
-            .created(TestMessageId.of(6), TestMessageId.of(7))
+            .created(messageId6, messageId7)
             .build();
         EmailChange change3 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .created(TestMessageId.of(8))
+            .created(messageId8)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
         repository.save(change3).block();
 
-        EmailChanges emailChanges = repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
+        EmailChanges emailChanges = repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(20))).block();
 
         assertThat(emailChanges.getCreated())
-            .containsExactlyInAnyOrder(TestMessageId.of(2), TestMessageId.of(3), TestMessageId.of(4), TestMessageId.of(5), TestMessageId.of(6), TestMessageId.of(7), TestMessageId.of(8));
+            .containsExactlyInAnyOrder(messageId2, messageId3, messageId4, messageId5, messageId6, messageId7, messageId8);
     }
 
     @Test
     default void getSinceStateWithDelegationShouldIgnoreDuplicatedValues() {
         EmailChangeRepository repository = emailChangeRepository();
 
+        State state = generateNewState();
+
+        MessageId messageId1 = generateNewMessageId();
+        MessageId messageId2 = generateNewMessageId();
+        MessageId messageId3 = generateNewMessageId();
+
         EmailChange oldState = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(STATE_0)
+            .state(state)
             .date(DATE.minusHours(2))
             .isDelegated(false)
-            .created(TestMessageId.of(1))
+            .created(messageId1)
             .build();
         EmailChange change1 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE.minusHours(1))
             .isDelegated(false)
-            .updated(TestMessageId.of(1), TestMessageId.of(2))
+            .updated(messageId1, messageId2)
             .build();
         EmailChange change2 = EmailChange.builder()
             .accountId(ACCOUNT_ID)
-            .state(State.of(UUID.randomUUID()))
+            .state(generateNewState())
             .date(DATE)
             .isDelegated(false)
-            .updated(TestMessageId.of(1), TestMessageId.of(2))
-            .created(TestMessageId.of(3))
+            .updated(messageId1, messageId2)
+            .created(messageId3)
             .build();
         repository.save(oldState).block();
         repository.save(change1).block();
         repository.save(change2).block();
 
-        EmailChanges emailChanges = repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block();
+        EmailChanges emailChanges = repository.getSinceStateWithDelegation(ACCOUNT_ID, state, Optional.of(Limit.of(3))).block();
         SoftAssertions.assertSoftly(softly -> {
-            softly.assertThat(emailChanges.getUpdated()).containsExactly(TestMessageId.of(1), TestMessageId.of(2));
-            softly.assertThat(emailChanges.getCreated()).containsExactly(TestMessageId.of(3));
+            softly.assertThat(emailChanges.getUpdated()).containsExactly(messageId1, messageId2);
+            softly.assertThat(emailChanges.getCreated()).containsExactly(messageId3);
         });
     }
 
@@ -1217,7 +1384,7 @@ public interface EmailChangeRepositoryContract {
     default void getSinceStateWithDelegationShouldFailWhenSinceStateNotFound() {
         EmailChangeRepository repository = emailChangeRepository();
 
-        assertThatThrownBy(() -> repository.getSinceStateWithDelegation(ACCOUNT_ID, STATE_0, Optional.empty()).block())
+        assertThatThrownBy(() -> repository.getSinceStateWithDelegation(ACCOUNT_ID, generateNewState(), Optional.empty()).block())
             .isInstanceOf(ChangeNotFoundException.class);
     }
 }
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryEmailChangeRepositoryTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryEmailChangeRepositoryTest.java
index 3b1abc1..f7711db 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryEmailChangeRepositoryTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/change/MemoryEmailChangeRepositoryTest.java
@@ -19,9 +19,13 @@
 
 package org.apache.james.jmap.memory.change;
 
+import java.util.concurrent.ThreadLocalRandom;
+
 import org.apache.james.jmap.api.change.EmailChangeRepository;
 import org.apache.james.jmap.api.change.EmailChangeRepositoryContract;
-import org.apache.james.jmap.api.change.MailboxChangeRepository;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.TestMessageId;
 import org.junit.jupiter.api.BeforeEach;
 
 public class MemoryEmailChangeRepositoryTest implements EmailChangeRepositoryContract {
@@ -36,4 +40,14 @@ public class MemoryEmailChangeRepositoryTest implements EmailChangeRepositoryCon
     public EmailChangeRepository emailChangeRepository() {
         return emailChangeRepository;
     }
+
+    @Override
+    public State generateNewState() {
+        return State.Factory.DEFAULT.generate();
+    }
+
+    @Override
+    public MessageId generateNewMessageId() {
+        return TestMessageId.of(ThreadLocalRandom.current().nextLong());
+    }
 }


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


[james-project] 07/11: JAMES-3470 Binding for CassandraEmailChangeModule

Posted by rc...@apache.org.
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 fcc9ef94faf59b9a4ee45376573e281002a7d897
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Jan 11 10:10:56 2021 +0700

    JAMES-3470 Binding for CassandraEmailChangeModule
---
 .../main/java/org/apache/james/modules/data/CassandraJmapModule.java    | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
index dfcfad8..1f64753 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraJmapModule.java
@@ -34,6 +34,7 @@ import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.VacationRepository;
 import org.apache.james.jmap.cassandra.access.CassandraAccessModule;
 import org.apache.james.jmap.cassandra.access.CassandraAccessTokenRepository;
+import org.apache.james.jmap.cassandra.change.CassandraEmailChangeModule;
 import org.apache.james.jmap.cassandra.change.CassandraMailboxChangeModule;
 import org.apache.james.jmap.cassandra.filtering.FilteringRuleSetDefineDTOModules;
 import org.apache.james.jmap.cassandra.projections.CassandraEmailQueryView;
@@ -83,6 +84,7 @@ public class CassandraJmapModule extends AbstractModule {
         cassandraDataDefinitions.addBinding().toInstance(CassandraMessageFastViewProjectionModule.MODULE);
         cassandraDataDefinitions.addBinding().toInstance(CassandraEmailQueryViewModule.MODULE);
         cassandraDataDefinitions.addBinding().toInstance(CassandraMailboxChangeModule.MODULE);
+        cassandraDataDefinitions.addBinding().toInstance(CassandraEmailChangeModule.MODULE);
 
         Multibinder<EventDTOModule<? extends Event, ? extends EventDTO>> eventDTOModuleBinder = Multibinder.newSetBinder(binder(), new TypeLiteral<EventDTOModule<? extends Event, ? extends EventDTO>>() {});
         eventDTOModuleBinder.addBinding().toInstance(FilteringRuleSetDefineDTOModules.FILTERING_RULE_SET_DEFINED);


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