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/05 04:55:46 UTC

[james-project] 07/24: JAMES-3486 Adapt MailboxChangesMethodContract for stability against distributed environment

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 b6fee54188409eb5a115468d52511f78cac08ca6
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 11:03:48 2020 +0700

    JAMES-3486 Adapt MailboxChangesMethodContract for stability against distributed environment
    
    Because changes in distributed environment do not happen instantaneously, we need to adapt the contract so that the tests behave in a more reliable way.
    
    Before, we were storing a state manually as a reference point, then the change(s) that we interested in would be conducted after that. This will not work
    in distributed environment, since the reference state might be stored even before the provisioning process complete and leads to unpredictable result.
    
    The fix:
    - We will wait for a new state to be recorded successfully each time there is a change happen
    - Fetch them sequentially until all the preparation steps are completed
    - Mark the latest stage
    - Conduct the change that we are interested in
    - fetch the result with the latest state as reference point.
---
 .../apache/james/jmap/draft/JmapGuiceProbe.java    |    6 +-
 .../contract/MailboxChangesMethodContract.scala    | 1613 +++++++++++---------
 .../memory/MemoryMailboxChangesMethodTest.java     |    7 +
 3 files changed, 869 insertions(+), 757 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
index 9c9affe..ca1616a 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
@@ -96,7 +96,11 @@ public class JmapGuiceProbe implements GuiceProbe {
         mailboxChangeRepository.save(change).block();
     }
 
-    public State latestState(AccountId accountId) {
+    public State getLastestState(AccountId accountId) {
         return mailboxChangeRepository.getLatestState(accountId).block();
     }
+
+    public State getLastestStateWithDelegation(AccountId accountId) {
+        return mailboxChangeRepository.getLatestStateWithDelegation(accountId).block();
+    }
 }
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/MailboxChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
index 85f88e6..7ecf86f 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
@@ -20,8 +20,7 @@
 package org.apache.james.jmap.rfc8621.contract
 
 import java.nio.charset.StandardCharsets
-import java.time.ZonedDateTime
-import java.util.UUID
+import java.util.concurrent.TimeUnit
 
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, requestSpecification}
@@ -33,8 +32,6 @@ import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
 import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
-import org.apache.james.core.Username
-import org.apache.james.jmap.api.change.MailboxChange
 import org.apache.james.jmap.api.change.State
 import org.apache.james.jmap.api.model.AccountId
 import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
@@ -48,22 +45,22 @@ 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.junit.jupiter.api.{BeforeEach, Nested, Test}
-import play.api.libs.json.{JsString, Json}
-
-import scala.jdk.CollectionConverters._
-
-object TestId {
-  def of(value: Long): MailboxId = TestId(value)
-}
-
-case class TestId(value: Long) extends MailboxId {
-  override def serialize(): String = String.valueOf(value)
-}
+import play.api.libs.json.{JsArray, JsString, Json}
 
 trait MailboxChangesMethodContract {
 
+  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)
+
   def stateFactory: State.Factory
+  def generateMailboxId: MailboxId
 
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
@@ -82,9 +79,7 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldReturnCreatedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val mailboxId1: String = mailboxProbe
       .createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -105,11 +100,12 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
+    awaitAtMostTenSeconds.untilAsserted { () =>
       val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
@@ -127,32 +123,35 @@ trait MailboxChangesMethodContract {
         .withOptions(new Options(IGNORING_ARRAY_ORDER))
         .isEqualTo(
           s"""{
-            |    "sessionState": "${SESSION_STATE.value}",
-            |    "methodResponses": [
-            |      [ "Mailbox/changes", {
-            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-            |        "oldState": "${oldState.getValue}",
-            |        "hasMoreChanges": false,
-            |        "updatedProperties":null,
-            |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3"],
-            |        "updated": [],
-            |        "destroyed": []
-            |      }, "c1"]
-            |    ]
-            |}""".stripMargin)
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenRenameMailbox(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
+
+    val provisioningState: State = provisionSystemMailboxes(server)
 
-    provisionSystemMailboxes(server)
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     JmapRequests.renameMailbox(mailboxId, "mailbox11")
 
@@ -168,50 +167,52 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenAppendMessageToMailbox(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, AccountId.fromUsername(BOB), provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -232,49 +233,54 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenAddSeenFlag(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -282,7 +288,7 @@ trait MailboxChangesMethodContract {
       .build
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state1)
 
     JmapRequests.markEmailAsSeen(messageId)
 
@@ -298,59 +304,66 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenRemoveSeenFlag(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     server.getProbe(classOf[ACLProbeImpl])
       .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+    val state2: State = waitForNextState(server, accountId, state1)
+
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
       AppendCommand.builder()
         .withFlags(new Flags(Flags.Flag.SEEN))
         .build("header: value\r\n\r\nbody"))
       .getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state2)
 
     JmapRequests.markEmailAsNotSeen(messageId)
 
@@ -366,7 +379,8 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
@@ -378,37 +392,41 @@ trait MailboxChangesMethodContract {
         .body
         .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |  "sessionState": "${SESSION_STATE.value}",
-           |  "methodResponses": [
-           |    ["Mailbox/changes", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "oldState": "${oldState.getValue}",
-           |      "hasMoreChanges": false,
-           |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |      "created": [],
-           |      "updated": ["$mailboxId"],
-           |      "destroyed": []
-           |    }, "c1"]
-           |  ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |  "sessionState": "${SESSION_STATE.value}",
+             |  "methodResponses": [
+             |    ["Mailbox/changes", {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "oldState": "${oldState.getValue}",
+             |      "hasMoreChanges": false,
+             |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |      "created": [],
+             |      "updated": ["$mailboxId"],
+             |      "destroyed": []
+             |    }, "c1"]
+             |  ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenDestroyEmail(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -416,7 +434,7 @@ trait MailboxChangesMethodContract {
       .build
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state1)
 
     JmapRequests.destroyEmail(messageId)
 
@@ -432,7 +450,8 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
@@ -444,24 +463,25 @@ trait MailboxChangesMethodContract {
         .body
         .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |  "sessionState": "${SESSION_STATE.value}",
-           |  "methodResponses": [
-           |    ["Mailbox/changes", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "oldState": "${oldState.getValue}",
-           |      "hasMoreChanges": false,
-           |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |      "created": [],
-           |      "updated": ["$mailboxId"],
-           |      "destroyed": []
-           |    }, "c1"]
-           |  ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |  "sessionState": "${SESSION_STATE.value}",
+             |  "methodResponses": [
+             |    ["Mailbox/changes", {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "oldState": "${oldState.getValue}",
+             |      "hasMoreChanges": false,
+             |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |      "created": [],
+             |      "updated": ["$mailboxId"],
+             |      "destroyed": []
+             |    }, "c1"]
+             |  ]
+             |}""".stripMargin)
+    }
   }
 
   @Nested
@@ -480,7 +500,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       val message: Message = Message.Builder
         .of
@@ -501,39 +521,41 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
-    }
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
+    }
 
     @Test
     def mailboxChangesShouldReturnUpdatedChangesWhenRenameMailbox(server: GuiceJamesServer): Unit = {
@@ -549,7 +571,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.renameMailbox(mailboxId, "mailbox11")
 
@@ -565,38 +587,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties":null,
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties":null,
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -613,6 +637,8 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val message: Message = Message.Builder
         .of
         .setSubject("test")
@@ -620,7 +646,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.markEmailAsSeen(messageId)
 
@@ -636,38 +662,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+          assertThatJson(response)
+            .whenIgnoringPaths("methodResponses[0][1].newState")
+            .withOptions(new Options(IGNORING_ARRAY_ORDER))
+            .isEqualTo(
+              s"""{
+                 |    "sessionState": "${SESSION_STATE.value}",
+                 |    "methodResponses": [
+                 |      [ "Mailbox/changes", {
+                 |        "accountId": "$ANDRE_ACCOUNT_ID",
+                 |        "oldState": "${oldState.getValue}",
+                 |        "hasMoreChanges": false,
+                 |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+                 |        "created": [],
+                 |        "updated": ["$mailboxId"],
+                 |        "destroyed": []
+                 |      }, "c1"]
+                 |    ]
+                 |}""".stripMargin)
+      }
     }
 
     @Test
@@ -684,13 +712,15 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
         AppendCommand.builder()
           .withFlags(new Flags(Flags.Flag.SEEN))
           .build("header: value\r\n\r\nbody"))
         .getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.markEmailAsNotSeen(messageId)
 
@@ -706,38 +736,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -754,6 +786,8 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val message: Message = Message.Builder
         .of
         .setSubject("test")
@@ -761,7 +795,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.destroyEmail(messageId)
 
@@ -777,38 +811,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -818,9 +854,7 @@ trait MailboxChangesMethodContract {
       provisionSystemMailboxes(server)
 
       val path = MailboxPath.forUser(BOB, "mailbox1")
-      val mailboxId: String = mailboxProbe
-        .createMailbox(path)
-        .serialize
+      mailboxProbe.createMailbox(path)
 
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
@@ -832,7 +866,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.destroyEmail(messageId)
 
@@ -843,47 +877,49 @@ trait MailboxChangesMethodContract {
            |    "Mailbox/changes",
            |    {
            |      "accountId": "$ANDRE_ACCOUNT_ID",
-           |      "sinceState": "${oldState.getValue}"
+           |      "sinceState": "${State.INITIAL.getValue}"
            |    },
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties":null,
-             |        "created": [],
-             |        "updated": [],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${State.INITIAL.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": null,
+               |        "created": [],
+               |        "updated": [],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
-    def mailboxChangesShouldReturnUpdatedChangesWhenDestroyDelegatedMailbox(server: GuiceJamesServer): Unit = {
+    def mailboxChangesShouldReturnDestroyedChangesWhenDestroyDelegatedMailbox(server: GuiceJamesServer): Unit = {
       val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
       provisionSystemMailboxes(server)
@@ -896,7 +932,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.destroyMailbox(mailboxId)
 
@@ -912,56 +948,58 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": null,
-             |        "created": [],
-             |        "updated": [],
-             |        "destroyed": ["$mailboxId"]
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": null,
+               |        "created": [],
+               |        "updated": [],
+               |        "destroyed": ["$mailboxId"]
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
   }
 
   @Test
   def mailboxChangesShouldReturnDestroyedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
-    mailboxProbe
-      .deleteMailbox(path.getNamespace, BOB.asString(), path.getName)
+    mailboxProbe.deleteMailbox(path.getNamespace, BOB.asString(), path.getName)
 
     val request =
       s"""{
@@ -975,58 +1013,51 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": [],
-           |        "destroyed": ["$mailboxId"]
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": [],
+             |        "updated": [],
+             |        "destroyed": ["$mailboxId"]
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
-  def mailboxChangesShouldReturnAllTypeOfChanges(server: GuiceJamesServer): Unit = {
+  def returnedIdsShouldNotReturnDuplicatesAccrossCreatedUpdatedOrDestroyed(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
-    val path2 = MailboxPath.forUser(BOB, "mailbox2")
-    val mailboxId2: String = mailboxProbe
-      .createMailbox(path2)
-      .serialize
-    val path3 = MailboxPath.forUser(BOB, "mailbox3")
-    val oldState: State = server.getProbe(classOf[JmapGuiceProbe]).latestState(AccountId.fromUsername(BOB))
 
-    val mailboxId3: String = mailboxProbe
-      .createMailbox(path3)
-      .serialize
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -1034,8 +1065,13 @@ trait MailboxChangesMethodContract {
       .build
     mailboxProbe.appendMessage(BOB.asString(), path1, AppendCommand.from(message))
 
-    server.getProbe(classOf[MailboxProbeImpl])
-      .deleteMailbox(path1.getNamespace, BOB.asString(), path2.getName)
+    val path2 = MailboxPath.forUser(BOB, "mailbox2")
+    val mailboxId2: String = mailboxProbe
+      .createMailbox(path2)
+      .serialize
+    JmapRequests.renameMailbox(mailboxId2, "mailbox22")
+
+    mailboxProbe.deleteMailbox(path1.getNamespace, BOB.asString(), path1.getName)
 
     val request =
       s"""{
@@ -1044,56 +1080,71 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": null,
-           |        "created": ["$mailboxId3"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": ["$mailboxId2"]
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
-  def returnedIdsShouldNotReturnDuplicatesAccrossCreatedUpdatedOrDestroyed(server: GuiceJamesServer): Unit = {
+  def mailboxChangesShouldReturnAllTypeOfChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
+    val path2 = MailboxPath.forUser(BOB, "mailbox2")
+    val mailboxId2: String = mailboxProbe
+      .createMailbox(path2)
+      .serialize
+
+    val oldState: State = waitForNextState(server, accountId, state1)
+
+    val path3 = MailboxPath.forUser(BOB, "mailbox3")
+    val mailboxId3: String = mailboxProbe
+      .createMailbox(path3)
+      .serialize
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -1101,14 +1152,7 @@ trait MailboxChangesMethodContract {
       .build
     mailboxProbe.appendMessage(BOB.asString(), path1, AppendCommand.from(message))
 
-    val path2 = MailboxPath.forUser(BOB, "mailbox2")
-    val mailboxId2: String = mailboxProbe
-      .createMailbox(path2)
-      .serialize
-    JmapRequests.renameMailbox(mailboxId2, "mailbox22")
-
-    server.getProbe(classOf[MailboxProbeImpl])
-      .deleteMailbox(path1.getNamespace, BOB.asString(), path1.getName)
+    mailboxProbe.deleteMailbox(path2.getNamespace, BOB.asString(), path2.getName)
 
     val request =
       s"""{
@@ -1122,44 +1166,45 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId3"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": ["$mailboxId2"]
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnHasMoreChangesWhenTrue(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val mailboxId1: String = mailboxProbe
       .createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -1192,46 +1237,49 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": true,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3", "$mailboxId4", "$mailboxId5"],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": true,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3", "$mailboxId4", "$mailboxId5"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldFailWhenAccountIdNotFound(server: GuiceJamesServer): Unit = {
-    val oldState: State = storeReferenceState(server, BOB)
+    val jmapGuiceProbe:JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    val oldState: State = jmapGuiceProbe.getLastestState(AccountId.fromUsername(BOB))
 
     val request =
       s"""{
@@ -1273,7 +1321,7 @@ trait MailboxChangesMethodContract {
   def mailboxChangesShouldFailWhenStateNotFound(server: GuiceJamesServer): Unit = {
     provisionSystemMailboxes(server)
 
-    val state: String = UUID.randomUUID().toString
+    val state: String = stateFactory.generate().getValue.toString
 
     val request =
       s"""{
@@ -1316,9 +1364,7 @@ trait MailboxChangesMethodContract {
 
   @Test
   def mailboxChangesShouldReturnNoChangesWhenNoNewerState(server: GuiceJamesServer): Unit = {
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val request =
       s"""{
@@ -1327,49 +1373,51 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "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)
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "newState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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)
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "newState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnDifferentStateThanOldState(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
 
@@ -1380,40 +1428,41 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+        .when
+        .post
+        .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    val newState = Json.parse(response)
-      .\("methodResponses")
-      .\(0).\(1)
-      .\("newState")
-      .get.asInstanceOf[JsString].value
+      val newState = Json.parse(response)
+        .\("methodResponses")
+        .\(0).\(1)
+        .\("newState")
+        .get.asInstanceOf[JsString].value
 
-    assertThat(oldState.getValue.toString).isNotEqualTo(newState)
+      assertThat(provisioningState.getValue.toString).isNotEqualTo(newState)
+    }
   }
 
   @Test
   def mailboxChangesShouldEventuallyReturnNoChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
-    mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
 
     val request1 =
       s"""{
@@ -1422,7 +1471,7 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
@@ -1492,15 +1541,16 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldReturnUpdatedPropertiesWhenOnlyCountChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -1561,15 +1611,16 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldNotReturnUpdatedPropertiesWhenMixedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val path2 = MailboxPath.forUser(BOB, "mailbox2")
     val mailboxId2: String = mailboxProbe
@@ -1595,50 +1646,53 @@ trait MailboxChangesMethodContract {
          |    "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
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldSupportBackReferenceWithUpdatedProperties(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -1646,9 +1700,6 @@ trait MailboxChangesMethodContract {
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
     mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val messageId2: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-
-    JmapRequests.destroyEmail(messageId2)
 
     val request =
       s"""{
@@ -1674,63 +1725,66 @@ trait MailboxChangesMethodContract {
          |  ]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"],
-           |      ["Mailbox/get", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "notFound": [],
-           |        "list": [
-           |          {
-           |            "id": "$mailboxId",
-           |            "totalEmails": 1,
-           |            "unreadEmails": 1,
-           |            "totalThreads": 1,
-           |            "unreadThreads": 1
-           |          }
-           |        ]
-           |      }, "c2"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"],
+             |      ["Mailbox/get", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "notFound": [],
+             |        "list": [
+             |          {
+             |            "id": "$mailboxId",
+             |            "totalEmails": 1,
+             |            "unreadEmails": 1,
+             |            "totalThreads": 1,
+             |            "unreadThreads": 1
+             |          }
+             |        ]
+             |      }, "c2"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldSupportBackReferenceWithNullUpdatedProperties(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val path2 = MailboxPath.forUser(BOB, "mailbox2")
     val mailboxId2: String = mailboxProbe
@@ -1768,76 +1822,86 @@ trait MailboxChangesMethodContract {
          |  ]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": []
-           |      }, "c1"],
-           |      ["Mailbox/get", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "notFound": [],
-           |        "list": [
-           |          {
-           |            "id": "$mailboxId1",
-           |            "name": "mailbox1",
-           |            "sortOrder": 1000,
-           |            "totalEmails": 1,
-           |            "unreadEmails": 1,
-           |            "totalThreads": 1,
-           |            "unreadThreads": 1,
-           |            "myRights": {
-           |              "mayReadItems": true,
-           |              "mayAddItems": true,
-           |              "mayRemoveItems": true,
-           |              "maySetSeen": true,
-           |              "maySetKeywords": true,
-           |              "mayCreateChild": true,
-           |              "mayRename": true,
-           |              "mayDelete": true,
-           |              "maySubmit": true
-           |            },
-           |            "isSubscribed": false
-           |          }
-           |        ]
-           |      }, "c2"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": []
+             |      }, "c1"],
+             |      ["Mailbox/get", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "notFound": [],
+             |        "list": [
+             |          {
+             |            "id": "$mailboxId1",
+             |            "name": "mailbox1",
+             |            "sortOrder": 1000,
+             |            "totalEmails": 1,
+             |            "unreadEmails": 1,
+             |            "totalThreads": 1,
+             |            "unreadThreads": 1,
+             |            "myRights": {
+             |              "mayReadItems": true,
+             |              "mayAddItems": true,
+             |              "mayRemoveItems": true,
+             |              "maySetSeen": true,
+             |              "maySetKeywords": true,
+             |              "mayCreateChild": true,
+             |              "mayRename": true,
+             |              "mayDelete": true,
+             |              "maySubmit": true
+             |            },
+             |            "isSubscribed": false
+             |          }
+             |        ]
+             |      }, "c2"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
-  private def storeReferenceState(server: GuiceJamesServer, username: Username): State = {
-    val state: State = stateFactory.generate()
+  private def waitForNextState(server: GuiceJamesServer, accountId: AccountId, initialState: State): State = {
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    awaitAtMostTenSeconds.untilAsserted {
+      () => assertThat(jmapGuiceProbe.getLastestState(accountId)).isNotEqualTo(initialState)
+    }
+
+    jmapGuiceProbe.getLastestState(accountId)
+  }
 
-    jmapGuiceProbe.saveMailboxChange(MailboxChange.builder.accountId(AccountId.fromUsername(username)).state(state).date(ZonedDateTime.now()).isCountChange(false).updated(List(TestId.of(0)).asJava).build)
+  private def waitForNextStateWithDelegation(server: GuiceJamesServer, accountId: AccountId, initialState: State): State = {
+    val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    awaitAtMostTenSeconds.untilAsserted{ () => assertThat(jmapGuiceProbe.getLastestStateWithDelegation(accountId)).isNotEqualTo(initialState) }
 
-    state
+    jmapGuiceProbe.getLastestStateWithDelegation(accountId)
   }
 
-  private def provisionSystemMailboxes(server: GuiceJamesServer): Unit = {
+  private def provisionSystemMailboxes(server: GuiceJamesServer): State = {
     val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
 
     val request =
       s"""{
@@ -1859,5 +1923,42 @@ trait MailboxChangesMethodContract {
     .`then`
       .statusCode(SC_OK)
       .contentType(JSON)
+
+    //Wait until all the system mailboxes are created
+    val request2 =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/changes",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "sinceState": "${State.INITIAL.getValue.toString}"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response1 = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request2)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      val createdSize = Json.parse(response1)
+        .\("methodResponses")
+        .\(0).\(1)
+        .\("created")
+        .get.asInstanceOf[JsArray].value.size
+
+      assertThat(createdSize).isEqualTo(5)
+    }
+
+    jmapGuiceProbe.getLastestState(AccountId.fromUsername(BOB))
   }
 }
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/MemoryMailboxChangesMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
index b01641a..df68a52 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
@@ -26,6 +26,8 @@ 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.MailboxChangesMethodContract;
+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;
 
@@ -42,4 +44,9 @@ public class MemoryMailboxChangesMethodTest implements MailboxChangesMethodContr
     public State.Factory stateFactory() {
         return new State.DefaultFactory();
     }
+
+    @Override
+    public MailboxId generateMailboxId() {
+        return InMemoryId.of(0);
+    }
 }
\ 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