| /**************************************************************** |
| * 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.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 javax.mail.Flags |
| import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson |
| import org.apache.http.HttpStatus.SC_OK |
| import org.apache.james.GuiceJamesServer |
| import org.apache.james.jmap.http.UserCredential |
| import org.apache.james.jmap.model.UTCDate |
| 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.MessageManager.AppendCommand |
| import org.apache.james.mailbox.model.{MailboxPath, MessageId} |
| import org.apache.james.mime4j.dom.Message |
| import org.apache.james.modules.MailboxProbeImpl |
| import org.apache.james.utils.DataProbeImpl |
| import org.awaitility.Awaitility |
| import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS |
| import org.junit.jupiter.api.{BeforeEach, Test} |
| import org.junit.jupiter.params.ParameterizedTest |
| import org.junit.jupiter.params.provider.{Arguments, MethodSource} |
| 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 shouldListMailsInAllUserMailboxes(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") |
| 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).isEqualTo( |
| s"""{ |
| | "sessionState": "75128aab4b1b", |
| | "methodResponses": [[ |
| | "Email/query", |
| | { |
| | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", |
| | "queryState": "${generateQueryState(messageId2, messageId1)}", |
| | "canCalculateChanges": false, |
| | "position": 0, |
| | "limit": 256, |
| | "ids": ["${messageId2.serialize()}", "${messageId1.serialize()}"] |
| | }, |
| | "c1" |
| | ]] |
| |}""".stripMargin) |
| } |
| } |
| |
| @Test |
| def shouldNotListMailsFromOtherUserMailboxes(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(ANDRE, "other") |
| 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(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": "75128aab4b1b", |
| | "methodResponses": [[ |
| | "Email/query", |
| | { |
| | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", |
| | "queryState": "${generateQueryState(messageId1)}", |
| | "canCalculateChanges": false, |
| | "position": 0, |
| | "limit": 256, |
| | "ids": ["${messageId1.serialize()}"] |
| | }, |
| | "c1" |
| | ]] |
| |}""".stripMargin) |
| } |
| } |
| |
| @Test |
| def listMailsShouldBeSortedByDescendingOrderOfArrivalByDefault(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") |
| server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) |
| val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) |
| val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDateMessage1) |
| val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(1).toInstant) |
| val messageId2 = sendMessageToBobInbox(server, message, requestDateMessage2) |
| val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(2).toInstant) |
| val messageId3 = sendMessageToBobInbox(server, message, requestDateMessage3) |
| |
| 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) |
| .inPath("$.methodResponses[0][1].ids") |
| .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""") |
| } |
| } |
| |
| @Test |
| def listMailsShouldBeIdempotent(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") |
| 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" |
| | }, |
| | "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 = 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") |
| 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", |
| | "comparator": [{ |
| | "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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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", |
| | "comparator": [{ |
| | "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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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", |
| | "comparator": [{ |
| | "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()}" |
| | }, |
| | "comparator": [{ |
| | "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": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"error.path.missing\"]}]}" |
| } |
| """) |
| } |
| } |
| |
| @Test |
| def shouldListMailsInASpecificUserMailboxes(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()}" |
| | } |
| | }, |
| | "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()}" |
| | }, |
| | "comparator": [{ |
| | "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": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"'unsupported' is not a supported sort property\"]}]}" |
| } |
| """) |
| } |
| } |
| |
| @Test |
| def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(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 |
| 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": "75128aab4b1b", |
| | "methodResponses": [ |
| | [ |
| | "error", |
| | { |
| | "type": "invalidArguments", |
| | "description": "${otherMailboxId.serialize()} can not be found" |
| | }, |
| | "c1" |
| | ] |
| | ] |
| |}""".stripMargin) |
| } |
| } |
| |
| @Test |
| def shouldListMailsNotInASpecificUserMailboxes(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) |
| 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": { |
| | "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 = 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") |
| 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) |
| .inPath("$.methodResponses[0][1].ids") |
| .isEqualTo(s"""["${messageId2.serialize()}", "${messageId1.serialize()}"]""") |
| } |
| } |
| |
| @Test |
| def listMailsInAFirstMailboxAndNotSomeOtherMailboxShouldReturnMailsInFirstMailbox(server: GuiceJamesServer): Unit = { |
| val message: Message = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.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 shouldListMailsReceivedAfterADate(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 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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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 = 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) |
| 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 |
| | }, |
| | "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": "75128aab4b1b", |
| | "methodResponses": [[ |
| | "Email/query", |
| | { |
| | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", |
| | "queryState": "${generateQueryState(messageId2)}", |
| | "canCalculateChanges": false, |
| | "position": 0, |
| | "ids": ["${messageId2.serialize()}"] |
| | }, |
| | "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": "75128aab4b1b", |
| | "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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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" |
| | }, |
| | "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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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 |
| | }, |
| | "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") |
| } |
| } |
| |
| private def sendMessageToBobInbox(server: GuiceJamesServer, message: Message, requestDate: Date) = { |
| server.getProbe(classOf[MailboxProbeImpl]) |
| .appendMessage(BOB.asString, MailboxPath.inbox(BOB), |
| AppendCommand.builder().withInternalDate(requestDate).build(message)) |
| .getMessageId |
| } |
| |
| @ParameterizedTest |
| @MethodSource(value = Array("jmapSystemKeywords")) |
| def listMailsBySystemKeywordShouldReturnOnlyMailsWithThisSystemKeyword(keywordFlag: Flags, keywordName: String, 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 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("""{ |
| | "sessionState": "75128aab4b1b", |
| | "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("""{ |
| | "sessionState": "75128aab4b1b", |
| | "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": "75128aab4b1b", |
| | "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": "75128aab4b1b", |
| | "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("""{ |
| | "sessionState": "75128aab4b1b", |
| | "methodResponses": [ |
| | [ |
| | "error", |
| | { |
| | "type": "invalidArguments" |
| | }, |
| | "c1" |
| | ] |
| | ] |
| |}""".stripMargin) |
| } |
| |
| @Test |
| def listMailsByCustomKeywordShouldReturnOnlyMailsWithThisCustomKeyword(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 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()}"]""") |
| } |
| } |
| |
| @ParameterizedTest |
| @MethodSource(value = Array("jmapSystemKeywords")) |
| def listMailsNotBySystemKeywordShouldReturnOnlyMailsWithoutThisSystemKeyword(keywordFlag: Flags, keywordName: String, 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)) |
| 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 = Message.Builder |
| .of |
| .setSubject("test") |
| .setBody("testmail", StandardCharsets.UTF_8) |
| .build |
| 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()}"]""") |
| } |
| } |
| |
| private def generateQueryState(messages: MessageId*): String = { |
| Hashing.murmur3_32() |
| .hashUnencodedChars(messages.toList.map(_.serialize()).mkString(" ")) |
| .toString |
| } |
| } |