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

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

commit 771c08f5e18fbdb574aef3f6e26fb09a8f288858
Author: Rene Cordier <[email protected]>
AuthorDate: Wed Nov 26 15:30:33 2025 +0700

    JAMES-3340 Add integration tests for collapseThreads with Email/query method
---
 .../DistributedEmailQueryMethodNoViewTest.java     |  33 ++
 .../contract/EmailQueryMethodContract.scala        | 589 +++++++++++++++++++--
 .../memory/MemoryEmailQueryMethodNoViewTest.java   |  28 +
 3 files changed, 593 insertions(+), 57 deletions(-)

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/DistributedEmailQueryMethodNoViewTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailQueryMethodNoViewTest.java
index c9cc85853e..9d348d5267 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailQueryMethodNoViewTest.java
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailQueryMethodNoViewTest.java
@@ -23,6 +23,7 @@ import org.apache.james.CassandraExtension;
 import org.apache.james.CassandraRabbitMQJamesConfiguration;
 import org.apache.james.CassandraRabbitMQJamesServerMain;
 import org.apache.james.DockerOpenSearchExtension;
+import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.JMAPConfiguration;
@@ -31,6 +32,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;
 
 public class DistributedEmailQueryMethodNoViewTest implements 
EmailQueryMethodContract {
@@ -60,4 +63,34 @@ public class DistributedEmailQueryMethodNoViewTest 
implements EmailQueryMethodCo
                     .disableEmailQueryView()
                     .build())))
         .build();
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxAfterSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxAfterSortedBySentAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void inMailboxSortedBySentAtShouldCollapseThreads(GuiceJamesServer 
server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxBeforeSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) 
{
+    }
 }
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/EmailQueryMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index eeff40dbdf..f90c310660 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -3646,63 +3646,6 @@ trait EmailQueryMethodContract {
        """)
   }
 
-  @ParameterizedTest
-  @ValueSource(strings = Array(
-    "true",
-    "false"
-  ))
-  def collapseThreadsParameterShouldNoop(collapseThreads: Boolean, server: 
GuiceJamesServer): Unit = {
-    val message: Message = Message.Builder
-      .of
-      .setSubject("test")
-      .setBody("testmail", StandardCharsets.UTF_8)
-      .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
-    val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
-    server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
-      .getMessageId
-    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
-      .getMessageId
-
-    val request =
-      s"""{
-         |  "using": [
-         |    "urn:ietf:params:jmap:core",
-         |    "urn:ietf:params:jmap:mail"],
-         |  "methodCalls": [[
-         |    "Email/query",
-         |    {
-         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "filter": {
-         |        "inMailbox": "${otherMailboxId.serialize}"
-         |       },
-         |       "collapseThreads": $collapseThreads
-         |    },
-         |    "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)
-        .inPath("$.methodResponses[0][1].ids")
-        .isEqualTo(s"""["${messageId2.serialize}"]""")
-    }
-  }
-
   @Test
   def listMailsShouldReturnInvalidArgumentsWhenAnchorParameterIsPresent(): 
Unit = {
     val request =
@@ -7527,6 +7470,538 @@ trait EmailQueryMethodContract {
     }
   }
 
+  @Test
+  def inMailboxAfterSortedByReceivedAtShouldCollapseThreads(server: 
GuiceJamesServer): Unit = {
+    val message1: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setMessageId("Message-ID")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message2: Message = Message.Builder
+      .of
+      .setSubject("BTW")
+      .setMessageId("Message-ID-2")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message3: Message = Message.Builder
+      .of
+      .setSubject("Hello again")
+      .setMessageId("Message-ID-3")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val beforeRequestDate1 = 
Date.from(ZonedDateTime.now().minusDays(3).toInstant)
+    val requestDate = ZonedDateTime.now().minusDays(1)
+    val afterRequestDate1 = Date.from(ZonedDateTime.now().toInstant)
+    val afterRequestDate2 = 
Date.from(ZonedDateTime.now().plusDays(1).toInstant)
+    val afterRequestDate3 = 
Date.from(ZonedDateTime.now().plusDays(2).toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate1)
+          .build(message1))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate1)
+        .build(message2))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(afterRequestDate2)
+          .build(message3))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate3)
+        .build(message3))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${mailboxId.serialize()}",
+         |        "after": 
"${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
+         |      },
+         |      "sort": [{
+         |        "property":"receivedAt",
+         |        "isAscending": false
+         |      }],
+         |      "collapseThreads": true
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId4, 
messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId4.serialize}", 
"${messageId2.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def inMailboxSortedByReceivedAtShouldCollapseThreads(server: 
GuiceJamesServer): Unit = {
+    val message1: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setMessageId("Message-ID")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message2: Message = Message.Builder
+      .of
+      .setSubject("BTW")
+      .setMessageId("Message-ID-2")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val beforeRequestDate1 = 
Date.from(ZonedDateTime.now().minusDays(3).toInstant)
+    val beforeRequestDate2 = 
Date.from(ZonedDateTime.now().minusDays(2).toInstant)
+    val afterRequestDate1 = Date.from(ZonedDateTime.now().toInstant)
+    val afterRequestDate2 = 
Date.from(ZonedDateTime.now().plusDays(1).toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate1)
+          .build(message1))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(beforeRequestDate2)
+        .build(message1))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(afterRequestDate1)
+          .build(message2))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate2)
+        .build(message2))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${mailboxId.serialize()}"
+         |       },
+         |      "sort": [{
+         |        "property":"receivedAt",
+         |        "isAscending": false
+         |      }],
+         |      "collapseThreads": true
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId4, 
messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId4.serialize}", 
"${messageId2.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def inMailboxAfterSortedBySentAtShouldCollapseThreads(server: 
GuiceJamesServer): Unit = {
+    val message1: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setMessageId("Message-ID")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message2: Message = Message.Builder
+      .of
+      .setSubject("BTW")
+      .setMessageId("Message-ID-2")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message3: Message = Message.Builder
+      .of
+      .setSubject("Hello again")
+      .setMessageId("Message-ID-3")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val beforeRequestDate1 = 
Date.from(ZonedDateTime.now().minusDays(3).toInstant)
+    val requestDate = ZonedDateTime.now().minusDays(1)
+    val afterRequestDate1 = Date.from(ZonedDateTime.now().toInstant)
+    val afterRequestDate2 = 
Date.from(ZonedDateTime.now().plusDays(1).toInstant)
+    val afterRequestDate3 = 
Date.from(ZonedDateTime.now().plusDays(2).toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate1)
+          .build(message1))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate1)
+        .build(message2))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(afterRequestDate2)
+          .build(message3))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate3)
+        .build(message3))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${mailboxId.serialize()}",
+         |        "after": 
"${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
+         |      },
+         |      "sort": [{
+         |        "property":"sentAt",
+         |        "isAscending": false
+         |      }],
+         |      "collapseThreads": true
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId4, 
messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId4.serialize}", 
"${messageId2.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def inMailboxSortedBySentAtShouldCollapseThreads(server: GuiceJamesServer): 
Unit = {
+    val message1: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setMessageId("Message-ID")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message2: Message = Message.Builder
+      .of
+      .setSubject("BTW")
+      .setMessageId("Message-ID-2")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val beforeRequestDate1 = 
Date.from(ZonedDateTime.now().minusDays(3).toInstant)
+    val beforeRequestDate2 = 
Date.from(ZonedDateTime.now().minusDays(2).toInstant)
+    val afterRequestDate1 = Date.from(ZonedDateTime.now().toInstant)
+    val afterRequestDate2 = 
Date.from(ZonedDateTime.now().plusDays(1).toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate1)
+          .build(message1))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(beforeRequestDate2)
+        .build(message1))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(afterRequestDate1)
+          .build(message2))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate2)
+        .build(message2))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${mailboxId.serialize()}"
+         |       },
+         |      "sort": [{
+         |        "property":"sentAt",
+         |        "isAscending": false
+         |      }],
+         |      "collapseThreads": true
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId4, 
messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId4.serialize}", 
"${messageId2.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def inMailboxBeforeSortedByReceivedAtShouldCollapseThreads(server: 
GuiceJamesServer): Unit = {
+    val message1: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setMessageId("Message-ID")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message2: Message = Message.Builder
+      .of
+      .setSubject("BTW")
+      .setMessageId("Message-ID-2")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val message3: Message = Message.Builder
+      .of
+      .setSubject("Hello again")
+      .setMessageId("Message-ID-3")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val beforeRequestDate1 = 
Date.from(ZonedDateTime.now().minusDays(3).toInstant)
+    val beforeRequestDate2 = 
Date.from(ZonedDateTime.now().minusDays(2).toInstant)
+    val beforeRequestDate3 = 
Date.from(ZonedDateTime.now().minusDays(1).toInstant)
+    val requestDate = ZonedDateTime.now()
+    val afterRequestDate1 = 
Date.from(ZonedDateTime.now().plusDays(1).toInstant)
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate1)
+          .build(message1))
+      .getMessageId
+
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(beforeRequestDate2)
+        .build(message2))
+      .getMessageId
+
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder()
+          .withInternalDate(beforeRequestDate3)
+          .build(message2))
+      .getMessageId
+
+    val messageId4: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder()
+        .withInternalDate(afterRequestDate1)
+        .build(message3))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${mailboxId.serialize()}",
+         |        "before": 
"${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
+         |      },
+         |      "sort": [{
+         |        "property":"receivedAt",
+         |        "isAscending": false
+         |      }],
+         |      "collapseThreads": true
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId3, 
messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId3.serialize}", 
"${messageId1.serialize}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
   private def sendMessageToBobInbox(server: GuiceJamesServer, message: 
Message, requestDate: Date): MessageId = {
     server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(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/MemoryEmailQueryMethodNoViewTest.java
 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodNoViewTest.java
index 72fb6a9caa..a6603bac6c 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodNoViewTest.java
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodNoViewTest.java
@@ -78,5 +78,33 @@ public class MemoryEmailQueryMethodNoViewTest implements 
EmailQueryMethodContrac
         
EmailQueryMethodContract.super.shouldListMailsReceivedAfterADate(server);
     }
 
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxAfterSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxAfterSortedBySentAtShouldCollapseThreads(GuiceJamesServer server) {
+    }
 
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void inMailboxSortedBySentAtShouldCollapseThreads(GuiceJamesServer 
server) {
+    }
+
+    @Test
+    @Override
+    @Disabled("JAMES-3340 Not supported for no email query view")
+    public void 
inMailboxBeforeSortedByReceivedAtShouldCollapseThreads(GuiceJamesServer server) 
{
+    }
 }


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

Reply via email to