This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c49550500e6d06e5ba556ac3669c00a9bd1b34ad Author: Rene Cordier <rcord...@linagora.com> AuthorDate: Wed Apr 3 12:07:41 2024 +0700 JAMES-4025 VacationIntegrationTest for JMAP RFC-8621 --- .../DistributedVacationIntegrationTest.java | 26 ++ .../rfc8621/contract/VacationIntegrationTest.scala | 447 +++++++++++++++++++++ .../memory/MemoryVacationIntegrationTest.java | 26 ++ 3 files changed, 499 insertions(+) diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedVacationIntegrationTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedVacationIntegrationTest.java new file mode 100644 index 0000000000..8aaf04cf1b --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedVacationIntegrationTest.java @@ -0,0 +1,26 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.rfc8621.distributed; + +import org.apache.james.jmap.rfc8621.contract.VacationIntegrationTest; + +public class DistributedVacationIntegrationTest extends DistributedBase implements VacationIntegrationTest { + +} diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationIntegrationTest.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationIntegrationTest.scala new file mode 100644 index 0000000000..e77cc0b3ac --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/VacationIntegrationTest.scala @@ -0,0 +1,447 @@ +/**************************************************************** + * 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.util.concurrent.TimeUnit + +import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT +import io.restassured.RestAssured +import io.restassured.RestAssured.`given` +import io.restassured.builder.ResponseSpecBuilder +import io.restassured.http.ContentType.JSON +import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson +import org.apache.http.HttpStatus.SC_OK +import org.apache.james.GuiceJamesServer +import org.apache.james.core.Username +import org.apache.james.jmap.JMAPTestingConstants.DOMAIN +import org.apache.james.jmap.JmapGuiceProbe +import org.apache.james.jmap.api.model.AccountId +import org.apache.james.jmap.http.UserCredential +import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, authScheme, baseRequestSpecBuilder} +import org.apache.james.jmap.rfc8621.contract.VacationIntegrationTest.{andreDraftsPath, html_reason, original_message_text_body, reason} +import org.apache.james.junit.categories.BasicFeature +import org.apache.james.mailbox.DefaultMailboxes +import org.apache.james.mailbox.MessageManager.AppendCommand +import org.apache.james.mailbox.model.{MailboxConstants, MailboxId, MailboxPath, MessageId} +import org.apache.james.mime4j.dom.Message +import org.apache.james.modules.MailboxProbeImpl +import org.apache.james.utils.DataProbeImpl +import org.apache.james.vacation.api.VacationPatch +import org.awaitility.Awaitility +import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS +import org.junit.experimental.categories.Category +import org.junit.jupiter.api.{BeforeEach, Test} + +object VacationIntegrationTest { + private val reason = "Message explaining my wonderful vacations" + private val html_reason = "<b>" + reason + "</b>" + private val original_message_text_body = "Hello someone, and thank you for joining example.com!" + private val andreDraftsPath = MailboxPath.forUser(ANDRE, DefaultMailboxes.DRAFTS) +} + +trait VacationIntegrationTest { + 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) + + @BeforeEach + def setUp(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[DataProbeImpl]) + .fluent + .addDomain(DOMAIN) + .addUser(BOB.asString, BOB_PASSWORD) + .addUser(ANDRE.asString, ANDRE_PASSWORD) + + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.SENT) + mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.SENT) + + mailboxProbe.createMailbox(andreDraftsPath) + + RestAssured.requestSpecification = baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD))) + .build + } + + @Category(Array(classOf[BasicFeature])) + @Test + def jmapVacationShouldGenerateAReplyWhenActive(server: GuiceJamesServer): Unit = { + /* Test scenario : + - bob sets a Vacation on its account + - andre matth...@mydomain.tld sends bob a mail + - bob should well receive this mail + - andre should well receive a notification about bob vacation + */ + + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + // Bob sets a Vacation on its account + setVacationResponse() + + // When + // andre sends Bob a mail + andreSendMailToBob(server) + + // Then + // Bob should well receive this mail + isMessageReceived(server, ACCOUNT_ID, bobInboxId, BOB, BOB_PASSWORD, original_message_text_body) + // andre should well receive a notification about user 1 vacation + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, reason) + } + + @Test + def jmapVacationShouldGenerateAReplyEvenWhenNoText(server: GuiceJamesServer): Unit = { + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + server.getProbe(classOf[JmapGuiceProbe]) + .modifyVacation(AccountId.fromUsername(BOB), VacationPatch.builder.isEnabled(true).build) + // When + andreSendMailToBob(server) + // Then + // Bob should well receive this mail + isMessageReceived(server, ACCOUNT_ID, bobInboxId, BOB, BOB_PASSWORD, original_message_text_body) + + // Andre should well receive a notification about user 1 vacation + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, "") + } + + @Test + def jmapVacationShouldHaveSupportForHtmlMail(server: GuiceJamesServer): Unit = { + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + setHtmlVacationResponse() + // When + andreSendMailToBob(server) + // Then + isMessageReceived(server, ACCOUNT_ID, bobInboxId, BOB, BOB_PASSWORD, original_message_text_body) + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, reason) + } + + @Test + def jmapVacationShouldNotGenerateAReplyWhenInactive(server: GuiceJamesServer): Unit = { + /* Test scenario : + - Andre sends User 1 a mail + - Bob should well receive this mail + - Andre should not receive a notification + */ + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + // When + // Andre sends User 1 a mail + andreSendMailToBob(server) + // Then + // Bob should well receive this mail + isMessageReceived(server, ACCOUNT_ID, bobInboxId, BOB, BOB_PASSWORD, original_message_text_body) + // Andre should not receive a notification + Thread.sleep(1000L) + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "$ANDRE_ACCOUNT_ID", + | "filter": {"inMailbox": "${andreInboxId.serialize}"} + | }, + | "c1"]] + |}""".stripMargin + + val response = `given`( + baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD))) + .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .setBody(request) + .build, new ResponseSpecBuilder().build) + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0][1].ids") + .isArray + .hasSize(0) + } + + @Test + def jmapVacationShouldNotSendNotificationTwice(server: GuiceJamesServer): Unit = { + /* Test scenario : + - Bob sets a Vacation on its account + - Andre sends Bob a mail + - Andre sends Bob a second mail + - Bob should well receive this mail + - Andre should well receive only one notification about user 1 vacation + */ + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + // Bob sets a Vacation on its account + setVacationResponse() + + // When + // Andre sends Bob a mail + andreSendMailToBob(server) + andreSendMailToBob(server) + // Then + // Andre should well receive a notification about user 1 vacation + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, reason) + // Andre should not receive another notification + Thread.sleep(1000L) + + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, reason) + } + + @Test + def jmapVacationShouldSendNotificationTwiceWhenVacationReset(server: GuiceJamesServer): Unit = { + /* Test scenario : + - Bob sets a Vacation on its account + - Andre sends Bob a mail + - Andre sends Bob a second mail + - Bob should well receive this mail + - Andre should well receive only one notification about user 1 vacation + */ + val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl]) + val bobInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, BOB.asString, DefaultMailboxes.INBOX) + val andreInboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ANDRE.asString, DefaultMailboxes.INBOX) + + // Bob sets a Vacation on its account + setVacationResponse() + // Andre sends Bob a mail + andreSendMailToBob(server) + // Wait Bob to receive the eMail before reset of vacation + isMessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD, reason) + // When + // Bob resets a Vacation on its account + setVacationResponse() + //Andre sends Bob a mail + andreSendMailToBob(server) + // Then + // Andre should well receive two notification about user 1 vacation + are2MessageReceived(server, ANDRE_ACCOUNT_ID, andreInboxId, ANDRE, ANDRE_PASSWORD) + } + + private def setVacationResponse(): Unit = { + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail", + | "urn:ietf:params:jmap:vacationresponse" ], + | "methodCalls": [ + | ["VacationResponse/set", { + | "accountId": "$ACCOUNT_ID", + | "update": { + | "singleton": { + | "isEnabled": true, + | "subject": "I am in vacation", + | "textBody": "$reason" + | } + | } + | }, "c1"] + | ] + |} + |""".stripMargin + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + } + + private def setHtmlVacationResponse(): Unit = { + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail", + | "urn:ietf:params:jmap:vacationresponse" ], + | "methodCalls": [ + | ["VacationResponse/set", { + | "accountId": "$ACCOUNT_ID", + | "update": { + | "singleton": { + | "isEnabled": true, + | "subject": "I am in vacation", + | "htmlBody": "$html_reason" + | } + | } + | }, "c1"] + | ] + |} + |""".stripMargin + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + } + + private def andreSendMailToBob(server: GuiceJamesServer): Unit = { + val message: Message = Message.Builder + .of + .setSubject("test") + .setSender(ANDRE.asString) + .setFrom("ANDRE <" + ANDRE.asString + ">") + .setTo(BOB.asString) + .setBody(original_message_text_body, StandardCharsets.UTF_8) + .build + + val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(ANDRE.asString(), andreDraftsPath, AppendCommand.builder().build(message)) + .getMessageId + + val requestAndre = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"], + | "methodCalls": [ + | ["EmailSubmission/set", { + | "accountId": "$ANDRE_ACCOUNT_ID", + | "create": { + | "k1490": { + | "emailId": "${messageId.serialize}", + | "envelope": { + | "mailFrom": {"email": "${ANDRE.asString}"}, + | "rcptTo": [{"email": "${BOB.asString}"}] + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + `given`( + baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD))) + .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .setBody(requestAndre) + .build, new ResponseSpecBuilder().build) + .post + .`then` + .statusCode(SC_OK) + } + + private def isMessageReceived(server: GuiceJamesServer, accountId: String, mailboxId: MailboxId, username: Username, password: String, expectedBody: String): Unit = { + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "$accountId", + | "filter": {"inMailbox": "${mailboxId.serialize}"} + | }, + | "c1"], [ + | "Email/get", + | { + | "accountId": "$accountId", + | "#ids": { + | "resultOf":"c1", + | "name":"Email/query", + | "path":"ids/*" + | } + | }, + | "c2"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given`( + baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(username, password))) + .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .setBody(request) + .build, new ResponseSpecBuilder().build) + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0][1].ids") + .isArray + .hasSize(1) + + assertThatJson(response) + .inPath("methodResponses[1][1].list[0].preview") + .isEqualTo(expectedBody) + } + } + + private def are2MessageReceived(server: GuiceJamesServer, accountId: String, mailboxId: MailboxId, username: Username, password: String): Unit = { + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "$accountId", + | "filter": {"inMailbox": "${mailboxId.serialize}"} + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given`( + baseRequestSpecBuilder(server) + .setAuth(authScheme(UserCredential(username, password))) + .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .setBody(request) + .build, new ResponseSpecBuilder().build) + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("methodResponses[0][1].ids") + .isArray + .hasSize(2) + } + } +} diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryVacationIntegrationTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryVacationIntegrationTest.java new file mode 100644 index 0000000000..d4abf8a2eb --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryVacationIntegrationTest.java @@ -0,0 +1,26 @@ +/**************************************************************** + * 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.memory; + +import org.apache.james.jmap.rfc8621.contract.VacationIntegrationTest; + +public class MemoryVacationIntegrationTest extends MemoryBase implements VacationIntegrationTest { + +} --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org