blob: 3bec025475dffc800ce73771ddfb9d0c2bf6ab39 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.jmap.rfc8621.contract
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import java.time.format.DateTimeFormatter
import java.time.{Instant, ZonedDateTime}
import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.stream.Stream
import com.google.common.hash.Hashing
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
import jakarta.mail.Flags
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
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.jmap.core.ResponseObject.SESSION_STATE
import org.apache.james.jmap.core.UTCDate
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.FlagsBuilder
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
import org.apache.james.mailbox.model.MailboxPath.inbox
import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
import org.apache.james.mime4j.dom.Message
import org.apache.james.mime4j.field.address.DefaultAddressParser
import org.apache.james.mime4j.message.{DefaultMessageWriter, MultipartBuilder}
import org.apache.james.mime4j.stream.RawField
import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
import org.apache.james.util.ClassLoaderUtils
import org.apache.james.utils.DataProbeImpl
import org.awaitility.Awaitility
import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
import org.junit.jupiter.api.{BeforeEach, Test}
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.{Arguments, MethodSource, ValueSource}
import org.threeten.extra.Seconds
object EmailQueryMethodContract {
def jmapSystemKeywords : Stream[Arguments] = {
Stream.of(
Arguments.of(new Flags(Flags.Flag.SEEN), "$Seen"),
Arguments.of(new Flags(Flags.Flag.ANSWERED), "$Answered"),
Arguments.of(new Flags(Flags.Flag.FLAGGED), "$Flagged"),
Arguments.of(new Flags(Flags.Flag.DRAFT), "$Draft"),
Arguments.of(new Flags("$Forwarded"), "$Forwarded"))
}
}
trait EmailQueryMethodContract {
private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
private lazy val calmlyAwait = Awaitility.`with`
.pollInterval(slowPacedPollInterval)
.and.`with`.pollDelay(slowPacedPollInterval)
.await
private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
private lazy val UTC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[DataProbeImpl])
.fluent
.addDomain(DOMAIN.asString)
.addUser(BOB.asString, BOB_PASSWORD)
.addUser(ANDRE.asString, ANDRE_PASSWORD)
requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
.build
}
@Test
def emailQueryShouldFailWhenWrongAccountId(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "unknownAccountId"
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "error",
| {
| "type": "accountNotFound"
| },
| "c1"
| ]]
|}""".stripMargin)
}
private def buildTestMessage = {
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
}
@Test
def hasAttachmentShouldKeepMessageWithAttachmentWhenTrue(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.from(
buildTestMessage))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"hasAttachment":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(messageId2)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId2.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def messagesMarkedAsDeletedShouldNotBeExposedOverJMAP(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withFlags(new Flags(Flags.Flag.DELETED))
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"hasAttachment":true}
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": []
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def emailQueryShouldAcceptMailboxCreationIdForInMailboxOtherThan(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
| "methodCalls": [
| [
| "Mailbox/set",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "create": {
| "C42": {
| "name": "myMailbox"
| }
| }
| },
| "c1"
| ], [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "inMailboxOtherThan": ["#C42"]
| }
| },
| "c2"
| ]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
.getMailboxId("#private", BOB.asString(), "myMailbox")
.serialize
assertThatJson(response)
.whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState")
.isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "Mailbox/set",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "created": {
| "C42": {
| "id": "${mailboxId}",
| "sortOrder": 1000,
| "totalEmails": 0,
| "unreadEmails": 0,
| "totalThreads": 0,
| "unreadThreads": 0,
| "myRights": {
| "mayReadItems": true,
| "mayAddItems": true,
| "mayRemoveItems": true,
| "maySetSeen": true,
| "maySetKeywords": true,
| "mayCreateChild": true,
| "mayRename": true,
| "mayDelete": true,
| "maySubmit": true
| },
| "isSubscribed": true
| }
| }
| },
| "c1"
| ],
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "00000000",
| "canCalculateChanges": false,
| "ids": [
|
| ],
| "position": 0,
| "limit": 256
| },
| "c2"
| ]
| ]
|}""".stripMargin)
}
@Test
def emailQueryShouldAcceptMailboxCreationIdForInMailbox(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
| "methodCalls": [
| [
| "Mailbox/set",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "create": {
| "C42": {
| "name": "myMailbox"
| }
| }
| },
| "c1"
| ], [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "inMailbox": "#C42"
| }
| },
| "c2"
| ]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
.getMailboxId("#private", BOB.asString(), "myMailbox")
.serialize
assertThatJson(response)
.whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[0][1].oldState")
.isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "Mailbox/set",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "created": {
| "C42": {
| "id": "${mailboxId}",
| "sortOrder": 1000,
| "totalEmails": 0,
| "unreadEmails": 0,
| "totalThreads": 0,
| "unreadThreads": 0,
| "myRights": {
| "mayReadItems": true,
| "mayAddItems": true,
| "mayRemoveItems": true,
| "maySetSeen": true,
| "maySetKeywords": true,
| "mayCreateChild": true,
| "mayRename": true,
| "mayDelete": true,
| "maySubmit": true
| },
| "isSubscribed": true
| }
| }
| },
| "c1"
| ],
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "00000000",
| "canCalculateChanges": false,
| "ids": [
|
| ],
| "position": 0,
| "limit": 256
| },
| "c2"
| ]
| ]
|}""".stripMargin)
}
@Test
def emailInSharedMailboxesShouldNotBeDisplayedWhenNoExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
| },
| "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()}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": []
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def emailInSharedMailboxesShouldBeDisplayedWhenExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"inMailbox": "${andreInboxId.serialize()}"}
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [
|
| ],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def inMailboxFilterShouldAcceptSharedMailboxesWhenExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"inMailbox": "${andreInboxId.serialize()}"}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxFilterWithSortedByReceivedAtShouldReturnEmptyForSharedMailboxesWhenNoSharesExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}"
| },
| "sort": [{
| "isAscending": false,
| "property": "receivedAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [
|
| ],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def inMailboxFilterWithSortedByReceivedAtShouldAcceptSharedMailboxesWhenSharesExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}"
| },
| "sort": [{
| "isAscending": false,
| "property": "receivedAt"
| }]
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxFilterWithSortedBySentAtShouldReturnEmptyForSharedMailboxesWhenNoSharesExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}"
| },
| "sort": [{
| "isAscending": false,
| "property": "sentAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [
|
| ],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def inMailboxFilterWithSortedBySentAtShouldAcceptSharedMailboxesWhenSharesExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}"
| },
| "sort": [{
| "isAscending": false,
| "property": "sentAt"
| }]
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxOtherThanFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"inMailboxOtherThan": ["${andreInboxId.serialize()}"]}
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def inMailboxAfterWithSortedBySentAtShouldReturnEmptyForSharedMailboxesWhenNoSharesExtension(server: GuiceJamesServer): Unit = {
val requestDate = ZonedDateTime.now().minusDays(1)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}",
| "after": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| },
| "sort": [{
| "isAscending": false,
| "property": "sentAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def inMailboxAfterWithSortedBySentAtShouldAcceptSharedMailboxesWhenSharesExtension(server: GuiceJamesServer): Unit = {
val requestDate = ZonedDateTime.now().minusDays(1)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}",
| "after": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| },
| "sort": [{
| "isAscending": false,
| "property": "sentAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState(messageId1)}",
| "canCalculateChanges": false,
| "ids": ["${messageId1.serialize()}"],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def inMailboxAfterWithSortedByReceivedAtShouldReturnEmptyForSharedMailboxesWhenNoSharesExtension(server: GuiceJamesServer): Unit = {
val requestDate = ZonedDateTime.now().minusDays(1)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}",
| "after": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| },
| "sort": [{
| "isAscending": false,
| "property": "receivedAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState()}",
| "canCalculateChanges": false,
| "ids": [],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def inMailboxAfterWithSortedByReceivedAtShouldAcceptSharedMailboxesWhenSharesExtension(server: GuiceJamesServer): Unit = {
val requestDate = ZonedDateTime.now().minusDays(1)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
val messageId1: MessageId = mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.addRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read, Right.Lookup))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"
| ],
| "methodCalls": [
| [
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${andreInboxId.serialize()}",
| "after": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| },
| "sort": [{
| "isAscending": false,
| "property": "receivedAt"
| }]
| },
| "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).isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "queryState": "${generateQueryState(messageId1)}",
| "canCalculateChanges": false,
| "ids": ["${messageId1.serialize()}"],
| "position": 0,
| "limit": 256
| },
| "c1"
| ]]
|}""".stripMargin)
}
@Test
def inMailboxOtherThanFilterShouldAcceptSharedMailboxesWhenExtension(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
mailboxProbe.createMailbox(inbox(BOB))
mailboxProbe
.appendMessage(ANDRE.asString, inbox(ANDRE),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[ACLProbeImpl])
.replaceRights(inbox(ANDRE), BOB.asString, new MailboxACL.Rfc4314Rights(Right.Read))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"inMailboxOtherThan": ["${andreInboxId.serialize()}"]}
| },
| "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(messageId2)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId2.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
def bodyFilterShouldMatchTextPlainOnBody(server: GuiceJamesServer): Unit = {
val message: Message = simpleMessage("this message body are uniqueeee")
val mailboxPath = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(mailboxPath)
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDate)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, mailboxPath, AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "body":"this message"
| }
| },
| "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"""["${messageId1.serialize}"]}""")
}
}
@Test
def bodyFilterShouldMatchTextPlainOnAttachmentsContent(server: GuiceJamesServer): Unit = {
val message: Message = simpleMessage("this message body are uniqueeee")
val mailboxPath = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(mailboxPath)
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, requestDate)
val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "body":"RSA PRIVATE"
| }
| },
| "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 bodyFilterShouldMatchTextPlainOnAttachmentsFileName(server: GuiceJamesServer): Unit = {
val message: Message = simpleMessage("this message body are uniqueeee")
val mailboxPath = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(mailboxPath)
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, requestDate)
val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "body":"text2"
| }
| },
| "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 headerExistsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "value"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"header": ["X-SpEcIfIc"]}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def headerShouldAllowToMatchMailWithSpecificHeaderSet(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "value"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"header": ["X-Specific"]}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def headerShouldAllowToMatchMailWithSpecificValueHeaderSet(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "value"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "other"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"header": ["X-Specific", "value"]}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def headerContainsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "VaLuE"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "other"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"header": ["X-Specific", "value"]}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def headerShouldRejectWhenMoreThanTwoItems(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "value"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB),
AppendCommand.from(
Message.Builder
.of
.addField(new RawField("X-Specific", "other"))
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail",
| "urn:apache:james:params:jmap:mail:shares"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"header": ["X-Specific", "value", "invalid"]}
| },
| "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)
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "'/filter/header' property is not valid: header filter needs to be an array of one or two strings"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def hasAttachmentShouldKeepMessageWithoutAttachmentWhenFalse(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.from(
buildTestMessage))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {"hasAttachment":false}
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def queryShouldTakeAllFiltersIntoAccount(server: GuiceJamesServer): Unit = {
val beforeRequestDate = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val requestDate = ZonedDateTime.now().minusDays(1)
val afterRequestDate = Date.from(ZonedDateTime.now().toInstant)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(beforeRequestDate)
.build(buildTestMessage))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(beforeRequestDate)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(afterRequestDate)
.build(buildTestMessage))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(afterRequestDate)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "hasAttachment":false,
| "after": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| }
| },
| "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)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId3.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxAfterSortedByReceivedAtShouldYieldExpectedResult(server: GuiceJamesServer): Unit = {
val beforeRequestDate1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val beforeRequestDate2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val requestDate = ZonedDateTime.now().minusDays(1)
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(buildTestMessage))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(beforeRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(afterRequestDate1)
.build(buildTestMessage))
.getMessageId
val messageId4: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(afterRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.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
| }]
| },
| "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, messageId3)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId4.serialize}", "${messageId3.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxAfterSortedByReceivedAtShouldYieldExpectedResultWithOffsetAndLimit(server: GuiceJamesServer): Unit = {
val beforeRequestDate1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val beforeRequestDate2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val requestDate = ZonedDateTime.now().minusDays(1)
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(buildTestMessage))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(beforeRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(afterRequestDate1)
.build(buildTestMessage))
.getMessageId
val messageId4: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(afterRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.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
| }],
| "limit": 1,
| "position": 1
| },
| "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)}",
| "canCalculateChanges": false,
| "position": 1,
| "ids": ["${messageId3.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxSortedByReceivedAtShouldYieldExpectedResult(server: GuiceJamesServer): Unit = {
val beforeRequestDate1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val beforeRequestDate2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val requestDate = ZonedDateTime.now().minusDays(1)
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(buildTestMessage))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(beforeRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(afterRequestDate1)
.build(buildTestMessage))
.getMessageId
val messageId4: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(afterRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.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
| }]
| },
| "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, messageId3, messageId2, messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId4.serialize}", "${messageId3.serialize}", "${messageId2.serialize}", "${messageId1.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def inMailboxSortedByReceivedAtShouldYieldExpectedResultWithOffsetAndLimit(server: GuiceJamesServer): Unit = {
val beforeRequestDate1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val beforeRequestDate2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val requestDate = ZonedDateTime.now().minusDays(1)
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(buildTestMessage))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(beforeRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(afterRequestDate1)
.build(buildTestMessage))
.getMessageId
val messageId4: MessageId = mailboxProbe
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(afterRequestDate2)
.build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.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
| }],
| "limit": 2,
| "position": 1
| },
| "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, messageId2)}",
| "canCalculateChanges": false,
| "position": 1,
| "ids": ["${messageId3.serialize}", "${messageId2.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def shouldListMailsInAllUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDate)
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"
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(
s"""["${messageId2.serialize}", "${messageId1.serialize}"]""".stripMargin)
}
}
@Test
def shouldNotListMailsFromOtherUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(ANDRE, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(ANDRE.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"
| },
| "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(messageId1)}",
| "canCalculateChanges": false,
| "position": 0,
| "limit": 256,
| "ids": ["${messageId1.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def listMailsShouldBeSortedByDescendingOrderOfSentAt(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val message: Message = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage1)
.build(message))
.getMessageId
val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage2)
.build(message))
.getMessageId
val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage3)
.build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"sentAt",
| "isAscending": false
| }]
| },
| "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"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedByDescendingOrderOfSentAtAndInMailbox(server: GuiceJamesServer): Unit = {
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val message: Message = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage1)
.build(message))
.getMessageId
val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage2)
.build(message))
.getMessageId
val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withInternalDate(requestDateMessage3)
.build(message))
.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
| }]
| },
| "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"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedByAscendingOrderOfSentAt(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val message1: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage1)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val message2: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage2)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val message3: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage3)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"sentAt",
| "isAscending": 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)
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedByAscendingOrderWhenSortingBySentAtWithoutOrdering(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant)
val message1: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage1)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val message2: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage2)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val message3: Message = Message.Builder
.of
.setSubject("test")
.setDate(requestDateMessage3)
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"sentAt"
| }]
| },
| "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"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedWhenUsingSize(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val message1: Message = Message.Builder
.of
.setSubject("test")
.setBody("short", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val message2: Message = Message.Builder
.of
.setSubject("test")
.setBody("definitly looooooooooooooooooooooooooooooooooooong", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val message3: Message = Message.Builder
.of
.setSubject("test")
.setBody("not that short", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{"property":"size"}]
| },
| "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"""["${messageId1.serialize}", "${messageId3.serialize}", "${messageId2.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedWhenUsingSubject(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val message1: Message = Message.Builder
.of
.setSubject("aba")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val message2: Message = Message.Builder
.of
.setSubject("aaa")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val message3: Message = Message.Builder
.of
.setSubject("ccc")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{"property":"subject"}]
| },
| "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}", "${messageId1.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedWhenUsingTo(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val message1: Message = Message.Builder
.of
.setTo("\"aaa\" <aaa@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val message2: Message = Message.Builder
.of
.setTo("\"ccc\" <ccc@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val message3: Message = Message.Builder
.of
.setTo("\"aba\" <aba@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{"property":"to"}]
| },
| "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"""["${messageId1.serialize}", "${messageId3.serialize}", "${messageId2.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedWhenUsingFrom(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val message1: Message = Message.Builder
.of
.setFrom("\"aaa\" <aaa@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message1))
.getMessageId
val message2: Message = Message.Builder
.of
.setFrom("\"ccc\" <ccc@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message2))
.getMessageId
val message3: Message = Message.Builder
.of
.setFrom("\"aba\" <aba@domain.tld>")
.setSubject("subject")
.setBody("any body", StandardCharsets.UTF_8)
.build
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().build(message3))
.getMessageId
val request =
s"""{
| "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{"property":"from"}]
| },
| "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"""["${messageId1.serialize}", "${messageId3.serialize}", "${messageId2.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedByAscendingOrderOfInternalDateByDefaultWhenNoDateInHeader(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(2).toInstant)
val message: Message = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
.getMessageId
val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
.getMessageId
val requestDateMessage3 = Date.from(ZonedDateTime.now().toInstant)
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"sentAt"
| }]
| },
| "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"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeIdempotent(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
.getMessageId
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"
| },
| "c1"]]
|}""".stripMargin
awaitAtMostTenSeconds.untilAsserted { () =>
val responseFirstCall = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
val responseSecondCall = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(responseFirstCall).isEqualTo(responseSecondCall)
}
}
@Test
def listMailsShouldBeSortedInAscendingOrderByDefault(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val now: Instant = Instant.now()
val requestDateMessage1: Date = Date.from(now)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
.getMessageId
val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
.getMessageId
val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"receivedAt"
| }]
| },
| "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"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedInAscendingOrder(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val now: Instant = Instant.now()
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1: Date = Date.from(now)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
.getMessageId
val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
.getMessageId
val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"receivedAt",
| "isAscending": 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)
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def listMailsShouldBeSortedInDescendingOrder(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val now: Instant = Instant.now()
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1: Date = Date.from(now)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
.getMessageId
val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
.getMessageId
val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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"""["${messageId3.serialize}", "${messageId2.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsOmitted(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${otherMailboxId.serialize}"
| },
| "sort": [{
| "isAscending":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)
.inPath("$.methodResponses[0][1]")
.isEqualTo("""
{
"type": "invalidArguments",
"description": "Missing '/sort(0)/property' property"
}
""")
}
}
@Test
def shouldListMailsInASpecificUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, 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}"
| }
| },
| "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 listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsInvalid(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${otherMailboxId.serialize}"
| },
| "sort": [{
| "property":"unsupported",
| "isAscending":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)
.inPath("$.methodResponses[0][1]")
.isEqualTo("""
{
"type": "invalidArguments",
"description": "'/sort(0)/property' property is not valid: 'unsupported' is not a supported sort property"
}
""")
}
}
@ParameterizedTest
@ValueSource(strings = Array(
"allInThreadHaveKeyword",
"someInThreadHaveKeyword",
"hasKeyword"
))
def listMailsShouldReturnUnsupportedSortWhenPropertyFieldInComparatorIsValidButUnsupported(unsupported: String): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"$unsupported"
| }]
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response)
.inPath("$.methodResponses[0][1]")
.isEqualTo(s"""
{
"type": "unsupportedSort",
"description": "The sort $unsupported is syntactically valid, but it includes a property the server does not support sorting on or a collation method it does not recognise."
}
""")
}
@ParameterizedTest
@ValueSource(strings = Array(
"allInThreadHaveKeyword",
"someInThreadHaveKeyword",
"noneInThreadHaveKeyword"
))
def listMailsShouldReturnUnsupportedFilterWhenValidButUnsupported(unsupportedFilter: String): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "$unsupportedFilter": "abc"
| }
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response)
.inPath("$.methodResponses[0][1]")
.isEqualTo(s"""
{
"type": "unsupportedFilter",
"description": "The filter $unsupportedFilter is syntactically valid, but the server cannot process it. If the filter was the result of a user’s search input, the client SHOULD suggest that the user simplify their search."
}
""")
}
@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 =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "anchor": "123"
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response)
.inPath("$.methodResponses[0][1]")
.isEqualTo(s"""
{
"type": "invalidArguments",
"description": "The following parameter anchor is syntactically valid, but is not supported by the server."
}
""")
}
@Test
def listMailsShouldReturnInvalidArgumentsWhenAnchorOffsetParameterIsPresent(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "anchorOffset": 0
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response)
.inPath("$.methodResponses[0][1]")
.isEqualTo(s"""
{
"type": "invalidArguments",
"description": "The following parameter anchorOffset is syntactically valid, but is not supported by the server."
}
""")
}
@Test
def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.from(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl]).deleteMailbox(otherMailboxPath.getNamespace, BOB.asString(), otherMailboxPath.getName)
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "inMailbox": "${otherMailboxId.serialize}"
| }
| },
| "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": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "${otherMailboxId.serialize} can not be found"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
}
@Test
def minSizeShouldBeInclusive(server: GuiceJamesServer): Unit = {
val message1: Message = simpleMessage("short")
computeSize(message1)
// One char more than message1
val message2: Message = simpleMessage("short!")
val size2: Int = computeSize(message2)
// One char more than message2
val message3: Message = simpleMessage("short!!")
computeSize(message3)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message1)).getMessageId
val id2 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message2)).getMessageId
val id3 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message3)).getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "minSize": $size2
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(
s"""[
| "${id2.serialize}",
| "${id3.serialize}"
|]""".stripMargin)
}
}
@Test
def maxSizeShouldBeExclusive(server: GuiceJamesServer): Unit = {
val message1: Message = simpleMessage("looooooooooooooong")
computeSize(message1)
// One char more than message3
val message2: Message = simpleMessage("looooooooooooooong!")
val size2: Int = computeSize(message2)
// One char more than message4
val message3: Message = simpleMessage("looooooooooooooong!!")
computeSize(message3)
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val id1 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message1)).getMessageId
mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message2)).getMessageId
mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message3)).getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "maxSize": $size2
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(
s"""[
| "${id1.serialize}"
|]""".stripMargin)
}
}
@Test
def maxSizeShouldRejectNegative(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "maxSize": -1
| }
| },
| "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": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "'/filter/maxSize' property is not valid: Predicate (-1 < 0) did not fail."
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
}
@Test
def minSizeShouldRejectNegative(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "minSize": -1
| }
| },
| "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": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "'/filter/minSize' property is not valid: Predicate (-1 < 0) did not fail."
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
}
private def simpleMessage(message: String) = {
Message.Builder
.of
.setSubject("test")
.setBody(message, StandardCharsets.UTF_8)
.build
}
private def computeSize(message: Message): Int = {
val writer = new DefaultMessageWriter()
val stream = new ByteArrayOutputStream()
writer.writeMessage(message, stream)
stream.toByteArray.length
}
@Test
def shouldListMailsNotInASpecificUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
.getMessageId
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": {
| "inMailboxOtherThan": [ "${otherMailboxId.serialize}" ]
| }
| },
| "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"""["${messageId1.serialize}"]""")
}
}
@Test
def shouldListMailsNotInZeroMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDateMessage1)
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": {
| "inMailboxOtherThan": [ ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def listMailsInAFirstMailboxAndNotSomeOtherMailboxShouldReturnMailsInFirstMailbox(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val inbox = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(message))
.getMessageId
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": "${inbox.serialize}",
| "inMailboxOtherThan": [ "${otherMailboxId.serialize}" ]
| }
| },
| "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"""["${messageId1.serialize}"]""")
}
}
@Test
def listMailsInAFirstMailboxAndNotInTheSameMailboxShouldReturnEmptyResult(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val inbox = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(message))
.getMessageId
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": "${inbox.serialize}",
| "inMailboxOtherThan": [ "${inbox.serialize}" ]
| }
| },
| "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("[]")
}
}
@Test
def shouldListMailsReceivedBeforeADate(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val requestDate = ZonedDateTime.now().minusDays(1)
val messageId1 = sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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" : {
| "before": "${UTCDate(requestDate.plusHours(1)).asUTC.format(UTC_DATE_FORMAT)}"
| }
| },
| "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"""["${messageId1.serialize}"]""")
}
}
@Test
def shouldListMailsReceivedBeforeADateInclusively(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val requestDate = ZonedDateTime.now().minusDays(1)
val messageId1 = sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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" : {
| "before": "${UTCDate(requestDate).asUTC.format(UTC_DATE_FORMAT)}"
| }
| },
| "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"""["${messageId1.serialize}"]""")
}
}
@Test
def shouldListMailsReceivedBeforeInAMailbox(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val requestDate = ZonedDateTime.now().minusDays(1)
val messageId1 = sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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" : {
| "before": "${UTCDate(requestDate).asUTC.plusSeconds(1).format(UTC_DATE_FORMAT)}",
| "inMailbox": "${mailboxId.serialize()}"
| },
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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"""["${messageId1.serialize}"]""")
}
}
@Test
def shouldListMailsReceivedAfterADate(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
sendMessageToBobInbox(server, message, Date.from(receivedDateMessage1.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val receivedDateMessage2 = receivedDateMessage1.plusHours(2)
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage2.toInstant)).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "after": "${UTCDate(receivedDateMessage2.minusHours(1)).asUTC.format(UTC_DATE_FORMAT)}"
| }
| },
| "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 listMailsReceivedAfterADateShouldBeExclusive(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
sendMessageToBobInbox(server, message, Date.from(receivedDateMessage1.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
val messageId2 = 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" : {
| "after": "${UTCDate(receivedDateMessage1).asUTC.format(UTC_DATE_FORMAT)}"
| }
| },
| "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 shouldLimitResultByTheLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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",
| "limit": 1,
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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(messageId2)}",
| "canCalculateChanges": false,
| "position": 0,
| "ids": ["${messageId2.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def shouldLimitResultByLimitAndPosition(server: GuiceJamesServer): Unit = {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
val message: Message = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
.build
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
mailboxProbe.createMailbox(otherMailboxPath)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(4).toInstant))
.build(message))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(3).toInstant))
.build(message))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, otherMailboxPath, AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant))
.build(message))
.getMessageId
sendMessageToBobInbox(server, message, Date.from(Date.from(ZonedDateTime.now().toInstant).toInstant))
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "limit": 2,
| "position": 2,
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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, messageId2)}",
| "canCalculateChanges": false,
| "position": 2,
| "ids": [
| "${messageId3.serialize}",
| "${messageId2.serialize}"
| ]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def shouldReturnEmptyWhenPositionIsOutOfBoundAndWithLimit(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 requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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",
| "limit": 2,
| "position": 2
| },
| "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()}",
| "canCalculateChanges": false,
| "position": 2,
| "ids": []
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def shouldReturnAnIllegalArgumentExceptionIfTheLimitIsNegative(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "limit": -1
| },
| "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": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "The limit can not be negative. -1 was provided."
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
}
@Test
def theLimitshouldBeEnforcedByTheServerIfNoLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), ZonedDateTime.now().minusYears(1))((acc, _) => {
val (messageList, date) = acc
val dateForNewMessage = date.plusDays(1)
val messageId = sendMessageToBobInbox(server, message, Date.from(dateForNewMessage.toInstant))
(messageId :: messageList, dateForNewMessage)
})
val expectedMessages = allMessages._1.take(256)
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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("[" + expectedMessages.map(message => s""""${message.serialize}"""").mkString(", ") + "]")
assertThatJson(response)
.inPath("$.methodResponses[0][1].limit")
.isEqualTo("256")
}
}
@Test
def theLimitshouldBeEnforcedByTheServerIfAGreaterLimitProvidedByTheClient(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), ZonedDateTime.now().minusYears(1))((acc, _) => {
val (messageList, date) = acc
val dateForNewMessage = date.plusDays(1)
val messageId = sendMessageToBobInbox(server, message, Date.from(dateForNewMessage.toInstant))
(messageId :: messageList, dateForNewMessage)
})
val expectedMessages = allMessages._1.take(256)
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "limit": 2000,
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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("[" + expectedMessages.map(message => s""""${message.serialize}"""").mkString(", ") + "]")
assertThatJson(response)
.inPath("$.methodResponses[0][1].limit")
.isEqualTo("256")
}
}
@Test
def resultsShouldStartAtThePositionProvidedByTheClient(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant))
.build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "position": 1,
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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(messageId1)}",
| "position": 1,
| "limit": 256,
| "canCalculateChanges": false,
| "ids": ["${messageId1.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def zeroPositionQueryShouldReturnItemsFromTheStart(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant))
.build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "position": 0,
| "sort": [{
| "property":"receivedAt",
| "isAscending": false
| }]
| },
| "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(messageId2, messageId1)}",
| "position": 0,
| "limit": 256,
| "canCalculateChanges": false,
| "ids": ["${messageId2.serialize}", "${messageId1.serialize}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def resultsShouldBeEmptyWithoutErrorWhenThePositionProvidedByTheClientIsGreaterThanTheNumberOfResults(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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",
| "position": 2
| },
| "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()}",
| "position": 2,
| "limit": 256,
| "canCalculateChanges": false,
| "ids": []
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def combiningSortPositionAndLimitShouldYieldExpectedResult(server: GuiceJamesServer): Unit = {
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val message: Message = buildTestMessage
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
val messageId3: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
.build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "position": 2,
| "limit": 2,
| "filter": {
| "inMailbox": "${mailboxId.serialize()}"
| },
| "sort": [{
| "property":"sentAt",
| "isAscending": false
| }]
| },
| "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(messageId1)}",
| "position": 2,
| "canCalculateChanges": false,
| "ids": ["${messageId1.serialize()}"]
| },
| "c1"
| ]]
|}""".stripMargin)
}
}
@Test
def shouldReturnAnIllegalArgumentExceptionIfThePositionIsNegative(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
val otherMailboxPath = MailboxPath.forUser(BOB, "other")
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
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",
| "position": -1
| },
| "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": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "Negative position are not supported yet. -1 was provided."
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
}
@ParameterizedTest
@MethodSource(value = Array("jmapSystemKeywords"))
def listMailsBySystemKeywordShouldReturnOnlyMailsWithThisSystemKeyword(keywordFlag: Flags, keywordName: String, server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val messageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(keywordFlag).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "$keywordName"
| }
| },
| "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"""["${messageId.serialize}"]""")
}
}
@Test
def hasKeywordShouldRejectEmpty(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": ""
| }
| },
| "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)
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def hasKeywordShouldRejectTooLong(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "${"a".repeat(257)}"
| }
| },
| "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)
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def hasKeywordShouldNotAcceptIMAPDeletedKeyword(): Unit = {
val request =
"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "$Deleted"
| }
| },
| "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)
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def hasKeywordShouldNotAcceptIMAPRecentKeyword(): Unit = {
val request =
"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "$Recent"
| }
| },
| "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)
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def hasKeywordShouldRejectInvalid(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "custom&invalid"
| }
| },
| "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)
.whenIgnoringPaths("methodResponses[0][1].description")
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def listMailsByCustomKeywordShouldReturnOnlyMailsWithThisCustomKeyword(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val messageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "hasKeyword": "custom"
| }
| },
| "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"""["${messageId.serialize}"]""")
}
}
@Test
def fromShouldFilterResultsWhenAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("user@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("other@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("yet@other.tld")
.build))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("yet@other.tld", "user@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val messageId6 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("\"User\" <user@domain.tld>")
.build))
.getMessageId
val messageId7 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("\"user@domain.tld\" <other@domain.tld>")
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "from": "user@domain.tld"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId7.serialize}", "${messageId6.serialize}", "${messageId4.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def fromShouldFilterResultsWhenNotAnAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("user@domain.tld")
.build))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("\"Display\" <other@domain.tld>")
.build))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setFrom("display@other.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "from": "Display"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def toShouldFilterResultsWhenAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("user@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("other@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("yet@other.tld")
.build))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("yet@other.tld", "user@domain.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val messageId6 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("\"User\" <user@domain.tld>")
.build))
.getMessageId
val messageId7 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("\"user@domain.tld\" <other@domain.tld>")
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "to": "user@domain.tld"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId7.serialize}", "${messageId6.serialize}", "${messageId4.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def toShouldFilterResultsWhenNotAnAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("user@domain.tld")
.build))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("\"Display\" <other@domain.tld>")
.build))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setTo("display@other.tld")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "to": "Display"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def ccShouldFilterResultsWhenAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("other@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"))
.build))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"),
DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val messageId6 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("\"User\" <user@domain.tld>"))
.build))
.getMessageId
val messageId7 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("\"user@domain.tld\" <other@domain.tld>"))
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "cc": "user@domain.tld"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId7.serialize}", "${messageId6.serialize}", "${messageId4.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def ccShouldFilterResultsWhenNotAnAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("\"Display\" <other@domain.tld>"))
.build))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setCc(DefaultAddressParser.DEFAULT.parseMailbox("display@other.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "cc": "Display"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def bccShouldFilterResultsWhenAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("other@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"))
.build))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"),
DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val messageId6 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("\"User\" <user@domain.tld>"))
.build))
.getMessageId
val messageId7 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("\"user@domain.tld\" <other@domain.tld>"))
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "bcc": "user@domain.tld"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId7.serialize}", "${messageId6.serialize}", "${messageId4.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def bccShouldFilterResultsWhenNotAnAddress(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setSubject("test")
.setBody("testmail", StandardCharsets.UTF_8)
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
.build))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("\"Display\" <other@domain.tld>"))
.build))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setBcc(DefaultAddressParser.DEFAULT.parseMailbox("display@other.tld"))
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "bcc": "Display"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
@Test
def subjectShouldFilterEmails(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setSubject("Yet another day in paradise")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setSubject("Welcome to hell")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "subject": "paradise"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId1.serialize}"]""")
}
}
@Test
def subjectShouldBeCaseInsensitive(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
def messageBuilder = Message.Builder
.of
.setBody("testmail", StandardCharsets.UTF_8)
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setSubject("Yet another day in paradise")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setSubject("Welcome to hell")
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder.build))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
messageBuilder
.setSubject("Yet another day in PaRaDiSe")
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "subject": "paradise"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId1.serialize}", "${messageId4.serialize}"]""")
}
}
@ParameterizedTest
@MethodSource(value = Array("jmapSystemKeywords"))
def listMailsNotBySystemKeywordShouldReturnOnlyMailsWithoutThisSystemKeyword(keywordFlag: Flags, keywordName: String, server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(keywordFlag).build(message))
.getMessageId
val messageWithoudFlagId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "notKeyword": "$keywordName"
| }
| },
| "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"""["${messageWithoudFlagId.serialize}"]""")
}
}
@Test
def listMailsNotByCustomKeywordShouldReturnOnlyMailsWithoutThisCustomKeyword(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageWithoudFlagId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "notKeyword": "custom"
| }
| },
| "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"""["${messageWithoudFlagId.serialize}"]""")
}
}
@Test
def emailQueryShouldSupportTextFilterForHeaders(server: GuiceJamesServer): Unit = {
val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(inbox(BOB))
val messageId1: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("a mail")
.setFrom("bloblah@domain.tld")
.setFrom("test@other.tld")
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
val messageId2: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("another mail")
.setTo("bloblah@domain.tld")
.setTo("test@other.tld")
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
val messageId3: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("another mail")
.addField(new RawField("Cc", "<bloblah@domain.tld>, <test@other.tld>"))
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
val messageId4: MessageId = mailboxProbe
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("another mail")
.addField(new RawField("Bcc", "<bloblah@domain.tld>, <test@other.tld>"))
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
mailboxProbe
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("should not be found mail")
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "test@other.tld"
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(
s"""[
| "${messageId4.serialize}",
| "${messageId3.serialize}",
| "${messageId2.serialize}",
| "${messageId1.serialize}"
|]""".stripMargin)
}
}
@Test
def emailQueryShouldSupportTextFilterForTextBody(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("a mail")
.setBody("This is a test body", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("should not be found mail")
.setBody("lorem ipsum", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "test"
| }
| },
| "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"""[
| "${messageId1.serialize}"
|]""".stripMargin)
}
}
@Test
def emailQueryShouldSupportTextFilterForHtmlBody(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("a mail")
.setBody("<body>This is a test body</body>", "html", StandardCharsets.UTF_8)
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("should not be found mail")
.setBody("<body>This is another body</body>", "html", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "test"
| }
| },
| "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"""[
| "${messageId1.serialize}"
|]""".stripMargin)
}
}
@Test
def emailQueryShouldSupportTextFilterForMultipartMessage(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("a mail")
.setBody(MultipartBuilder.create()
.addTextPart("This is a test body", StandardCharsets.UTF_8)
.build())
.build))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("should not be found mail")
.setBody(MultipartBuilder.create()
.addTextPart("This is another body", StandardCharsets.UTF_8)
.build())
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "test"
| }
| },
| "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"""[
| "${messageId1.serialize}"
|]""".stripMargin)
}
}
@Test
def emailQueryFilterByTextShouldIgnoreMarkupsInHtmlBody(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(Message.Builder
.of
.setSubject("A mail")
.setBody("<body><test>This is a html body<test></body>", "html", StandardCharsets.UTF_8)
.build))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "test"
| }
| },
| "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"""[]""".stripMargin)
}
}
@Test
def emailQueryFilterByTextShouldIgnoreAttachmentName(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "text2"
| }
| },
| "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"""[]""".stripMargin)
}
}
@Test
def emailQueryShouldSupportAndOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| { "hasKeyword": "custom" }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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"""["${messageId3.serialize}"]""")
}
}
@Test
def emailQueryShouldSupportOrOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new Flags("custom"))
.build(message))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new Flags("another_custom"))
.build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new FlagsBuilder().add("custom", "another_custom").build())
.build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "OR",
| "conditions": [
| { "hasKeyword": "custom" }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId3.serialize}", "${messageId2.serialize}", "${messageId1.serialize}"]""")
}
}
@Test
def emailQueryShouldSupportNotOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new Flags("custom"))
.build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new Flags("another_custom"))
.build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
.withFlags(new FlagsBuilder().add("custom", "another_custom").build())
.build(message))
.getMessageId
val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "NOT",
| "conditions": [
| { "hasKeyword": "custom" }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId4.serialize}"]""")
}
}
@Test
def emailQueryShouldRejectFilterOperatorWithExtraFields(server: GuiceJamesServer): Unit = {
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val requestDate = ZonedDateTime.now().minusDays(1)
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.plusHours(1)).asUTC.format(UTC_DATE_FORMAT)}",
| "operator": "AND",
| "conditions": [
| { "hasKeyword": "custom" }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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)
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "'/filter' property is not valid: Expecting filterOperator to contain only operator and conditions"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def emailQueryShouldRejectOperatorWithoutCondition(server: GuiceJamesServer): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND"
| }
| },
| "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)
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "invalidArguments",
| "description": "'/filter' property is not valid: Expecting filterOperator to contain only operator and conditions"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def inMailboxShouldResolveSimpleFilter(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "other"))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| { "inMailbox": "${mailboxId.serialize()}" }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId1.serialize}","${messageId2.serialize}"]""")
}
@Test
def inMailboxShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
val mailboxId2 = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "other"))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| { "inMailbox": "${mailboxId.serialize()}" },
| {
| "operator": "OR",
| "conditions": [
| { "inMailbox": "${mailboxId2.serialize()}" }, { "hasKeyword": "another_custom" }
| ]
| },
| { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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)
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "unsupportedFilter",
| "description": "Nested inMailbox filters are not supported"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def inMailboxOtherThanShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "other"))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageId = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| { "inMailboxOtherThan": ["${mailboxId.serialize()}"] }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId.serialize}"]""")
}
}
@Test
def inMailboxOtherThanShouldBeRejectedWhenOrOperator(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "other"))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.forUser(BOB, "other"), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "OR",
| "conditions": [
| { "inMailboxOtherThan": ["${mailboxId.serialize()}"] }, { "hasKeyword": "another_custom" }
| ]
| }
| },
| "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)
.isEqualTo(s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "unsupportedFilter",
| "description": "Nested inMailboxOtherThan filter are not supported"
| },
| "c1"
| ]
| ]
|}""".stripMargin)
}
@Test
def emailQueryFilterByTextShouldIgnoreAttachmentContent(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter": {
| "text": "RSA PRIVATE"
| }
| },
| "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"""[]""".stripMargin)
}
}
@Test
def nestedOperatorsShouldBeSupported(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
.getMessageId
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| {
| "operator": "AND",
| "conditions": [
| { "hasKeyword": "custom" }, { "hasKeyword": "another_custom" }
| ]
| }
| ]
| }
| },
| "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"""["${messageId3.serialize}"]""")
}
}
@Test
def shouldReturnInvalidArgumentsWhenInvalidFilterCondition(): Unit = {
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter":{
| "unsupported_option": "blahh_blahh",
| "role":"Inbox"
| }
| },
| "c1"]]
|}""".stripMargin
val response = `given`
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.body(request)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.extract
.body
.asString
assertThatJson(response)
.inPath("methodResponses[0][1]")
.isEqualTo(
"""{
| "type": "invalidArguments",
| "description": "'/filter' property is not valid: These '[unsupported_option, role]' was unsupported filter options"
|}
|""".stripMargin)
}
@Test
def priorityOfNestedOperatorsShouldBePreserved(server: GuiceJamesServer): Unit = {
val message: Message = buildTestMessage
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withFlags(new FlagsBuilder().add("custom_1", "custom_2").build())
.build(message))
.getMessageId
val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withFlags(new FlagsBuilder().add("custom_1", "custom_3").build())
.build(message))
.getMessageId
val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder()
.withFlags(new FlagsBuilder().add("custom_2", "custom_3").build())
.build(message))
.getMessageId
val request =
s"""{
| "using": [
| "urn:ietf:params:jmap:core",
| "urn:ietf:params:jmap:mail"],
| "methodCalls": [[
| "Email/query",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "filter" : {
| "operator": "AND",
| "conditions": [
| {
| "operator": "OR",
| "conditions": [
| { "hasKeyword": "custom_1" }, { "hasKeyword": "custom_2" }
| ]
| },
| { "hasKeyword": "custom_3" }
| ]
| }
| },
| "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))
.inPath("$.methodResponses[0][1].ids")
.isEqualTo(s"""["${messageId2.serialize}", "${messageId3.serialize}"]""")
}
}
private def sendMessageToBobInbox(server: GuiceJamesServer, message: Message, requestDate: Date): MessageId = {
server.getProbe(classOf[MailboxProbeImpl])
.appendMessage(BOB.asString, MailboxPath.inbox(BOB),
AppendCommand.builder().withInternalDate(requestDate).build(message))
.getMessageId
}
private def generateQueryState(messages: MessageId*): String =
Hashing.murmur3_32_fixed()
.hashUnencodedChars(messages.toList.map(_.serialize).mkString(" "))
.toString
}