Java tutorial
/**************************************************************** * 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.methods.integration; import static com.jayway.restassured.RestAssured.given; import static com.jayway.restassured.RestAssured.with; import static com.jayway.restassured.config.EncoderConfig.encoderConfig; import static com.jayway.restassured.config.RestAssuredConfig.newConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; import static org.hamcrest.collection.IsMapWithSize.anEmptyMap; import java.io.ByteArrayInputStream; import java.io.IOException; import java.time.ZonedDateTime; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.mail.Flags; import org.apache.commons.io.IOUtils; import org.apache.james.GuiceJamesServer; import org.apache.james.jmap.JmapAuthentication; import org.apache.james.jmap.api.access.AccessToken; import org.apache.james.jmap.model.mailbox.Role; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.Attachment; import org.apache.james.mailbox.model.MailboxConstants; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.util.ZeroedInputStream; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import com.google.common.base.Charsets; import com.google.common.io.ByteStreams; import com.jayway.awaitility.Awaitility; import com.jayway.awaitility.Duration; import com.jayway.awaitility.core.ConditionFactory; import com.jayway.restassured.RestAssured; import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.builder.ResponseSpecBuilder; import com.jayway.restassured.http.ContentType; import com.jayway.restassured.specification.ResponseSpecification; public abstract class SetMessagesMethodTest { private static final int _1MB = 1024 * 1024; private static final String NAME = "[0][0]"; private static final String ARGUMENTS = "[0][1]"; private static final String SECOND_NAME = "[1][0]"; private static final String SECOND_ARGUMENTS = "[1][1]"; private static final String USERS_DOMAIN = "domain.tld"; private static final String NOT_UPDATED = ARGUMENTS + ".notUpdated"; private ConditionFactory calmlyAwait; protected abstract GuiceJamesServer createJmapServer(); protected abstract void await(); private AccessToken accessToken; private String username; private GuiceJamesServer jmapServer; @Before public void setup() throws Throwable { jmapServer = createJmapServer(); jmapServer.start(); RestAssured.requestSpecification = new RequestSpecBuilder().setContentType(ContentType.JSON) .setAccept(ContentType.JSON) .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8))) .setPort(jmapServer.getJmapPort()).build(); username = "username@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addDomain(USERS_DOMAIN); jmapServer.serverProbe().addUser(username, password); jmapServer.serverProbe().createMailbox("#private", username, "inbox"); accessToken = JmapAuthentication.authenticateJamesUser(username, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "outbox"); await(); Duration slowPacedPollInterval = Duration.FIVE_HUNDRED_MILLISECONDS; calmlyAwait = Awaitility.with().pollInterval(slowPacedPollInterval).and().with() .pollDelay(slowPacedPollInterval).await(); } @After public void teardown() { jmapServer.stop(); } private String getOutboxId(AccessToken accessToken) { return getMailboxId(accessToken, Role.OUTBOX); } private String getMailboxId(AccessToken accessToken, Role role) { return getAllMailboxesIds(accessToken).stream().filter(x -> x.get("role").equals(role.serialize())) .map(x -> x.get("id")).findFirst().get(); } private List<Map<String, String>> getAllMailboxesIds(AccessToken accessToken) { return with().header("Authorization", accessToken.serialize()) .body("[[\"getMailboxes\", {\"properties\": [\"role\", \"id\"]}, \"#0\"]]").post("/jmap") .andReturn().body().jsonPath().getList(ARGUMENTS + ".list"); } @Test public void setMessagesShouldReturnErrorNotSupportedWhenRequestContainsNonNullAccountId() throws Exception { given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"accountId\": \"1\"}, \"#0\"]]").when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("error")) .body(ARGUMENTS + ".type", equalTo("Not yet implemented")); } @Test public void setMessagesShouldReturnErrorNotSupportedWhenRequestContainsNonNullIfInState() throws Exception { given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"ifInState\": \"1\"}, \"#0\"]]").when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("error")) .body(ARGUMENTS + ".type", equalTo("Not yet implemented")); } @Test public void setMessagesShouldReturnNotDestroyedWhenUnknownMailbox() throws Exception { String unknownMailboxMessageId = username + "|unknown|12345"; given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + unknownMailboxMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().log().ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".destroyed", empty()) .body(ARGUMENTS + ".notDestroyed", hasEntry(equalTo(unknownMailboxMessageId), Matchers.allOf(hasEntry("type", "anErrorOccurred"), hasEntry("description", "An error occurred while deleting message " + unknownMailboxMessageId), hasEntry(equalTo("properties"), isEmptyOrNullString())))); } @Test public void setMessagesShouldReturnNotDestroyedWhenNoMatchingMessage() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); String messageId = username + "|mailbox|12345"; given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + messageId + "\"]}, \"#0\"]]").when().post("/jmap") .then().log().ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".destroyed", empty()).body(ARGUMENTS + ".notDestroyed", hasEntry(equalTo(messageId), Matchers.allOf(hasEntry("type", "notFound"), hasEntry("description", "The message " + messageId + " can't be found"), hasEntry(equalTo("properties"), isEmptyOrNullString())))); } @Test public void setMessagesShouldReturnDestroyedWhenMatchingMessage() throws Exception { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + username + "|mailbox|1\"]}, \"#0\"]]").when() .post("/jmap").then().log().ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notDestroyed", anEmptyMap()).body(ARGUMENTS + ".destroyed", hasSize(1)) .body(ARGUMENTS + ".destroyed", contains(username + "|mailbox|1")); } @Test public void setMessagesShouldDeleteMessageWhenMatchingMessage() throws Exception { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); // When given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + username + "|mailbox|1\"]}, \"#0\"]]").when() .post("/jmap").then().log().ifValidationFails().statusCode(200); // Then given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + username + "|mailbox|1\"]}, \"#0\"]]").when() .post("/jmap").then().log().ifValidationFails().statusCode(200).body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", empty()); } @Test public void setMessagesShouldReturnDestroyedNotDestroyWhenMixed() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String missingMessageId = username + "|mailbox|4"; given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + username + "|mailbox|1\", \"" + missingMessageId + "\", \"" + username + "|mailbox|3\"]}, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(NAME, equalTo("messagesSet")).body(ARGUMENTS + ".destroyed", hasSize(2)) .body(ARGUMENTS + ".notDestroyed", aMapWithSize(1)) .body(ARGUMENTS + ".destroyed", contains(username + "|mailbox|1", username + "|mailbox|3")) .body(ARGUMENTS + ".notDestroyed", hasEntry(equalTo(missingMessageId), Matchers.allOf(hasEntry("type", "notFound"), hasEntry("description", "The message " + missingMessageId + " can't be found")))); } @Test public void setMessagesShouldDeleteMatchingMessagesWhenMixed() throws Exception { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); // When given().header("Authorization", accessToken.serialize()) .body("[[\"setMessages\", {\"destroy\": [\"" + username + "|mailbox|1\", \"" + username + "|mailbox|4\", \"" + username + "|mailbox|3\"]}, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200); // Then given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + username + "|mailbox|1\", \"" + username + "|mailbox|2\", \"" + username + "|mailbox|3\"]}, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(NAME, equalTo("messages")).body(ARGUMENTS + ".list", hasSize(1)); } @Test public void setMessagesShouldReturnUpdatedIdAndNoErrorWhenIsUnreadPassedToFalse() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; // When given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", presumedMessageId)) .when().post("/jmap") // Then .then().log().ifValidationFails().spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId)); } private ResponseSpecification getSetMessagesUpdateOKResponseAssertions(String messageId) { ResponseSpecBuilder builder = new ResponseSpecBuilder().expectStatusCode(200) .expectBody(NAME, equalTo("messagesSet")).expectBody(ARGUMENTS + ".updated", hasSize(1)) .expectBody(ARGUMENTS + ".updated", contains(messageId)) .expectBody(ARGUMENTS + ".error", isEmptyOrNullString()) .expectBody(NOT_UPDATED, not(hasKey(messageId))); return builder.build(); } @Test public void setMessagesShouldMarkAsReadWhenIsUnreadPassedToFalse() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap"); // Then with().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").post("/jmap") .then().log().ifValidationFails().statusCode(200).body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(ARGUMENTS + ".list[0].isUnread", equalTo(false)); } @Test public void setMessagesShouldReturnUpdatedIdAndNoErrorWhenIsUnreadPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN)); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap") // Then .then().log().ifValidationFails().spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId)); } @Test public void setMessagesShouldMarkAsUnreadWhenIsUnreadPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN)); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap"); // Then with().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").post("/jmap") .then().log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(ARGUMENTS + ".list[0].isUnread", equalTo(true)); } @Test public void setMessagesShouldReturnUpdatedIdAndNoErrorWhenIsFlaggedPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap") // Then .then().log().ifValidationFails().spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId)); } @Test public void setMessagesShouldMarkAsFlaggedWhenIsFlaggedPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap"); // Then with().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").post("/jmap") .then().log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(ARGUMENTS + ".list[0].isFlagged", equalTo(true)); } @Test public void setMessagesShouldRejectUpdateWhenPropertyHasWrongType() throws MailboxException { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String messageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\" } } }, \"#0\"]]", messageId)) .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(NAME, equalTo("messagesSet")).body(NOT_UPDATED, hasKey(messageId)) .body(NOT_UPDATED + "[\"" + messageId + "\"].type", equalTo("invalidProperties")) .body(NOT_UPDATED + "[\"" + messageId + "\"].properties[0]", equalTo("isUnread")) .body(NOT_UPDATED + "[\"" + messageId + "\"].description", equalTo( "isUnread: Can not construct instance of java.lang.Boolean from String value '123': only \"true\" or \"false\" recognized\n" + " at [Source: {\"isUnread\":\"123\"}; line: 1, column: 2] (through reference chain: org.apache.james.jmap.model.Builder[\"isUnread\"])")) .body(ARGUMENTS + ".updated", hasSize(0)); } @Test @Ignore("Jackson json deserializer stops after first error found") public void setMessagesShouldRejectUpdateWhenPropertiesHaveWrongTypes() throws MailboxException { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String messageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()).body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\", \"isFlagged\" : 456 } } }, \"#0\"]]", messageId)).when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(NAME, equalTo("messagesSet")).body(NOT_UPDATED, hasKey(messageId)) .body(NOT_UPDATED + "[\"" + messageId + "\"].type", equalTo("invalidProperties")) .body(NOT_UPDATED + "[\"" + messageId + "\"].properties", hasSize(2)) .body(NOT_UPDATED + "[\"" + messageId + "\"].properties[0]", equalTo("isUnread")) .body(NOT_UPDATED + "[\"" + messageId + "\"].properties[1]", equalTo("isFlagged")) .body(ARGUMENTS + ".updated", hasSize(0)); } @Test public void setMessagesShouldMarkMessageAsAnsweredWhenIsAnsweredPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; // When given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId)) .when().post("/jmap") // Then .then().log().ifValidationFails().spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId)); } @Test public void setMessagesShouldMarkAsAnsweredWhenIsAnsweredPassed() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"), new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags()); await(); String presumedMessageId = username + "|mailbox|1"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId)) // When .when().post("/jmap"); // Then with().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").post("/jmap") .then().log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(ARGUMENTS + ".list[0].isAnswered", equalTo(true)); } @Test public void setMessageShouldReturnNotFoundWhenUpdateUnknownMessage() { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox"); String nonExistingMessageId = username + "|mailbox|12345"; given().header("Authorization", accessToken.serialize()) .body(String.format( "[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", nonExistingMessageId)) .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(NAME, equalTo("messagesSet")).body(NOT_UPDATED, hasKey(nonExistingMessageId)) .body(NOT_UPDATED + "[\"" + nonExistingMessageId + "\"].type", equalTo("notFound")) .body(NOT_UPDATED + "[\"" + nonExistingMessageId + "\"].description", equalTo("message not found")) .body(ARGUMENTS + ".updated", hasSize(0)); } @Test public void setMessageShouldReturnCreatedMessageWhenSendingMessage() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)) // note that assertions on result message had to be split between // string-typed values and boolean-typed value assertions on the same .created entry // make sure only one creation has been processed .body(ARGUMENTS + ".created", aMapWithSize(1)) // assert server-set attributes are returned .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(hasEntry(equalTo("id"), not(isEmptyOrNullString())), hasEntry(equalTo("blobId"), not(isEmptyOrNullString())), hasEntry(equalTo("threadId"), not(isEmptyOrNullString())), hasEntry(equalTo("size"), not(isEmptyOrNullString()))))) // assert that message flags are all unset .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(hasEntry(equalTo("isDraft"), equalTo(false)), hasEntry(equalTo("isUnread"), equalTo(false)), hasEntry(equalTo("isFlagged"), equalTo(false)), hasEntry(equalTo("isAnswered"), equalTo(false))))); } @Test public void setMessageShouldReturnCreatedMessageWithEmptySubjectWhenSubjectIsNull() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": null," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)).body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].subject", equalTo("")); } @Test public void setMessageShouldReturnCreatedMessageWithEmptySubjectWhenSubjectIsEmpty() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)).body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].subject", equalTo("")); } @Test public void setMessageShouldReturnCreatedMessageWithNonASCIICharactersInSubjectWhenPresent() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \" \"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)).body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].subject", equalTo( " ")); } @Test public void setMessageShouldSupportArbitraryMessageId() { String messageCreationId = "1717fcd1-603e-44a5-b2a6-1234dbcd5723"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)).body(ARGUMENTS + ".created", aMapWithSize(1)); } @Test public void setMessagesShouldCreateMessageInOutboxWhenSendingMessage() throws MailboxException { // Given String messageCreationId = "user|inbox|1"; String presumedMessageId = "username@domain.tld|outbox|1"; String fromAddress = username; String messageSubject = "Thank you for joining example.com!"; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"" + messageSubject + "\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + outboxId + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then with().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").post("/jmap") .then().log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(ARGUMENTS + ".list[0].subject", equalTo(messageSubject)) .body(ARGUMENTS + ".list[0].mailboxIds", contains(outboxId)); } @Test public void setMessagesShouldMoveMessageInSentWhenMessageIsSent() throws MailboxException { // Given jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); String sentMailboxId = getMailboxId(accessToken, Role.SENT); String fromAddress = username; String messageCreationId = "user|inbox|1"; String messageSubject = "Thank you for joining example.com!"; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"" + messageSubject + "\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId)); } private boolean messageHasBeenMovedToSentBox(String sentMailboxId) { try { with().header("Authorization", accessToken.serialize()) .body("[[\"getMessageList\", {\"fetchMessages\":true, \"filter\":{\"inMailboxes\":[\"" + sentMailboxId + "\"]}}, \"#0\"]]") .when().post("/jmap").then().statusCode(200).body(SECOND_NAME, equalTo("messages")) .body(SECOND_ARGUMENTS + ".list", hasSize(1)); return true; } catch (AssertionError e) { return false; } } @Test public void setMessagesShouldRejectWhenSendingMessageHasNoValidAddress() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("invalidProperties")) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("no recipient address set")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } @Test public void setMessagesShouldRejectWhenSendingMessageHasMissingFrom() { String messageCreationId = "user|inbox|1"; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("invalidProperties")) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("'from' address is mandatory")) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", hasSize(1)) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("from")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } @Test public void setMessagesShouldSucceedWhenSendingMessageWithOnlyFromAddress() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].headers.From", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.name", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.email", equalTo(fromAddress)); } @Test public void setMessagesShouldSucceedWithHtmlBody() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"htmlBody\": \"Hello <i>someone</i>, and thank <b>you</b> for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].headers.From", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.name", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.email", equalTo(fromAddress)); } @Test public void setMessagesShouldMoveToSentWhenSendingMessageWithOnlyFromAddress() { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); String sentMailboxId = getMailboxId(accessToken, Role.SENT); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId)); } @Test public void setMessagesShouldNotRejectWhenSendingMessageHasMissingSubject() { String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", aMapWithSize(0)).body(ARGUMENTS + ".created", aMapWithSize(1)); } @Test public void setMessagesShouldRejectWhenSendingMessageUseSomeoneElseFromAddress() { String messageCreationId = "user|inbox|1"; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"other@domain.tld\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then().log() .ifValidationFails().statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("invalidProperties")) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", hasSize(1)) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("from")) .body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("Invalid 'from' field. Must be one of username@domain.tld")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } @Test public void setMessagesShouldDeliverMessageToRecipient() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, password); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS) .until(() -> isAnyMessageFoundInRecipientsMailboxes(recipientToken)); } @Test public void setMessagesShouldStripBccFromDeliveredEmail() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, password); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," + " \"bcc\": [{ \"name\": \"BOB\", \"email\": \"bob@" + USERS_DOMAIN + "\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS) .until(() -> isAnyMessageFoundInRecipientsMailboxes(recipientToken)); with().header("Authorization", recipientToken.serialize()).body( "[[\"getMessageList\", {\"fetchMessages\": true, \"fetchMessageProperties\": [\"bcc\"] }, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(SECOND_NAME, equalTo("messages")).body(SECOND_ARGUMENTS + ".list", hasSize(1)) .body(SECOND_ARGUMENTS + ".list[0].bcc", empty()); } @Test public void setMessagesShouldKeepBccInSentMailbox() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); String sentMailboxId = getMailboxId(accessToken, Role.SENT); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," + " \"bcc\": [{ \"name\": \"BOB\", \"email\": \"bob@" + USERS_DOMAIN + "\" }]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId)); with().header("Authorization", this.accessToken.serialize()).body( "[[\"getMessageList\", {\"fetchMessages\":true, \"fetchMessageProperties\": [\"bcc\"], \"filter\":{\"inMailboxes\":[\"" + sentMailboxId + "\"]}}, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(SECOND_NAME, equalTo("messages")).body(SECOND_ARGUMENTS + ".list", hasSize(1)) .body(SECOND_ARGUMENTS + ".list[0].bcc", hasSize(1)); } @Test public void setMessagesShouldSendMessageToBcc() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); String bccAddress = "bob" + "@" + USERS_DOMAIN; jmapServer.serverProbe().addUser(bccAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, bccAddress, "inbox"); await(); AccessToken bccToken = JmapAuthentication.authenticateJamesUser(bccAddress, password); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," + " \"bcc\": [{ \"name\": \"BOB\", \"email\": \"" + bccAddress + "\" }]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bccToken)); with().header("Authorization", bccToken.serialize()).body( "[[\"getMessageList\", {\"fetchMessages\": true, \"fetchMessageProperties\": [\"bcc\"] }, \"#0\"]]") .when().post("/jmap").then().log().ifValidationFails().statusCode(200) .body(SECOND_NAME, equalTo("messages")).body(SECOND_ARGUMENTS + ".list", hasSize(1)) .body(SECOND_ARGUMENTS + ".list[0].bcc", empty()); } private boolean isAnyMessageFoundInRecipientsMailboxes(AccessToken recipientToken) { try { with().header("Authorization", recipientToken.serialize()).body("[[\"getMessageList\", {}, \"#0\"]]") .when().post("/jmap").then().statusCode(200).body(NAME, equalTo("messageList")) .body(ARGUMENTS + ".messageIds", hasSize(1)); return true; } catch (AssertionError e) { return false; } } @Test public void setMessagesShouldSendAReadableHtmlMessage() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, password); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"htmlBody\": \"Hello <b>someone</b>, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isHtmlMessageReceived(recipientToken)); } @Test public void setMessagesWhenSavingToDraftsShouldNotSendMessage() throws Exception { String sender = username; jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "sent"); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "drafts"); String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String recipientPassword = "password"; jmapServer.serverProbe().addUser(recipientAddress, recipientPassword); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, recipientPassword); String senderDraftsMailboxId = getMailboxId(accessToken, Role.DRAFTS); String messageCreationId = "creationId"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + senderDraftsMailboxId + "\"], " + " \"isDraft\": false" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", this.accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200); //We need to wait for an async event to not happen, we couldn't found any //robust way to check that. Thread.sleep(TimeUnit.SECONDS.toMillis(5)); assertThat(isAnyMessageFoundInRecipientsMailboxes(recipientToken)).isFalse(); } @Test public void setMessagesWhenSavingToRegularMailboxShouldNotSendMessage() throws Exception { String sender = username; jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "sent"); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "drafts"); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "regular"); Mailbox regularMailbox = jmapServer.serverProbe().getMailbox(MailboxConstants.USER_NAMESPACE, sender, "regular"); String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String recipientPassword = "password"; jmapServer.serverProbe().addUser(recipientAddress, recipientPassword); await(); String messageCreationId = "creationId"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + " \"cc\": [{ \"name\": \"ALICE\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + " \"mailboxIds\": [\"" + regularMailbox.getMailboxId().serialize() + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; String notCreatedMessage = ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"]"; given().header("Authorization", this.accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200).body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) .body(notCreatedMessage + ".type", equalTo("invalidProperties")) .body(notCreatedMessage + ".description", equalTo("Not yet implemented")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } private boolean isHtmlMessageReceived(AccessToken recipientToken) { try { with().header("Authorization", recipientToken.serialize()).body( "[[\"getMessageList\", {\"fetchMessages\": true, \"fetchMessageProperties\": [\"htmlBody\"]}, \"#0\"]]") .post("/jmap").then().statusCode(200).body(SECOND_NAME, equalTo("messages")) .body(SECOND_ARGUMENTS + ".list", hasSize(1)).body(SECOND_ARGUMENTS + ".list[0].htmlBody", equalTo("Hello <b>someone</b>, and thank you for joining example.com!")); return true; } catch (AssertionError e) { return false; } } @Test public void setMessagesShouldSendAReadableTextPlusHtmlMessage() throws Exception { // Sender jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); // Recipient String recipientAddress = "recipient" + "@" + USERS_DOMAIN; String password = "password"; jmapServer.serverProbe().addUser(recipientAddress, password); jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); await(); AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, password); String messageCreationId = "user|inbox|1"; String fromAddress = username; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + " \"subject\": \"Thank you for joining example.com!\"," + " \"htmlBody\": \"Hello <b>someone</b>, and thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com, text version!\"," + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; // Given given().header("Authorization", this.accessToken.serialize()).body(requestBody) // When .when().post("/jmap"); // Then calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isTextPlusHtmlMessageReceived(recipientToken)); } private boolean isTextPlusHtmlMessageReceived(AccessToken recipientToken) { try { with().header("Authorization", recipientToken.serialize()).body( "[[\"getMessageList\", {\"fetchMessages\": true, \"fetchMessageProperties\": [\"htmlBody\", \"textBody\"]}, \"#0\"]]") .post("/jmap").then().statusCode(200).body(SECOND_NAME, equalTo("messages")) .body(SECOND_ARGUMENTS + ".list", hasSize(1)) .body(SECOND_ARGUMENTS + ".list[0].htmlBody", equalTo("Hello <b>someone</b>, and thank you for joining example.com!")) .body(SECOND_ARGUMENTS + ".list[0].textBody", equalTo("Hello someone, and thank you for joining example.com, text version!")); return true; } catch (AssertionError e) { return false; } } @Test public void movingAMessageIsNotSupported() throws Exception { String newMailboxName = "heartFolder"; jmapServer.serverProbe().createMailbox("#private", username, newMailboxName); Mailbox heartFolder = jmapServer.serverProbe().getMailbox("#private", username, newMailboxName); String heartFolderId = heartFolder.getMailboxId().serialize(); ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z"); jmapServer.serverProbe().appendMessage(username, new MailboxPath("#private", username, "inbox"), new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags()); String messageToMoveId = "user|inbox|1"; String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"update\": { \"" + messageToMoveId + "\" : {" + " \"mailboxIds\": [\"" + heartFolderId + "\"]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", this.accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200).body(NAME, equalTo("messagesSet")).body(NOT_UPDATED, hasKey(messageToMoveId)) .body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].type", equalTo("invalidProperties")) .body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].properties[0]", equalTo("mailboxIds")) .body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].description", equalTo("mailboxIds: moving a message is not supported " + "(through reference chain: org.apache.james.jmap.model.Builder[\"mailboxIds\"])")) .body(ARGUMENTS + ".updated", hasSize(0)); } @Test public void setMessagesShouldReturnAttachmentsNotFoundWhenBlobIdDoesntExist() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); await(); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Message with a broken blobId\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"brokenId1\", \"type\" : \"image/gif\", \"size\" : 1337}," + " {\"blobId\" : \"brokenId2\", \"type\" : \"image/jpeg\", \"size\" : 1337}" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; String notCreatedPath = ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200).body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) .body(notCreatedPath + ".type", equalTo("invalidProperties")) .body(notCreatedPath + ".attachmentsNotFound", contains("brokenId1", "brokenId2")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } @Test public void setMessagesShouldReturnAttachmentsWhenMessageHasAttachment() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes("attachment".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment); Attachment attachment2 = Attachment.builder().bytes("attachment2".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment2); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Message with two attachments\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + "}," + " {\"blobId\" : \"" + attachment2.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment2.getType() + "\", " + " \"size\" : " + attachment2.getSize() + ", " + " \"cid\" : \"123456789\", " + " \"isInline\" : true }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]"; String firstAttachment = createdPath + ".attachments[0]"; String secondAttachment = createdPath + ".attachments[1]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200).body(NAME, equalTo("messagesSet")).body(ARGUMENTS + ".notCreated", aMapWithSize(0)) .body(ARGUMENTS + ".created", aMapWithSize(1)).body(createdPath + ".attachments", hasSize(2)) .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId())) .body(firstAttachment + ".type", equalTo("application/octet-stream; charset=UTF-8")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())) .body(firstAttachment + ".cid", nullValue()).body(firstAttachment + ".isInline", equalTo(false)) .body(secondAttachment + ".blobId", equalTo(attachment2.getAttachmentId().getId())) .body(secondAttachment + ".type", equalTo("application/octet-stream; charset=UTF-8")) .body(secondAttachment + ".size", equalTo((int) attachment2.getSize())) .body(secondAttachment + ".cid", equalTo("123456789")) .body(secondAttachment + ".isInline", equalTo(true)); } @Test public void setMessagesShouldReturnAttachmentsWithNonASCIINames() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes("attachment".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment); Attachment attachment2 = Attachment.builder().bytes("attachment2".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment2); Attachment attachment3 = Attachment.builder().bytes("attachment3".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment3); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\":" + " {" + " \"" + messageCreationId + "\" : " + " {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," + " \"subject\": \"Message with three attachments with non ASCII name\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\":" + " [" + " {" + " \"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + "," + " \"name\" : \".png\", " + " \"isInline\" : false" + " }," + " {" + " \"blobId\" : \"" + attachment2.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment2.getType() + "\", " + " \"size\" : " + attachment2.getSize() + "," + " \"name\" : \"?.png\", " + " \"isInline\" : false" + " }," + " {" + " \"blobId\" : \"" + attachment3.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment3.getType() + "\", " + " \"size\" : " + attachment3.getSize() + "," + " \"name\" : \"?.png\"," + " \"isInline\" : false" + " }" + " ]" + " }" + " }" + " }," + " \"#0\"" + " ]" + "]"; String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]"; String firstAttachment = createdPath + ".attachments[0]"; String secondAttachment = createdPath + ".attachments[1]"; String thirdAttachment = createdPath + ".attachments[2]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then() .statusCode(200).body(NAME, equalTo("messagesSet")).body(ARGUMENTS + ".notCreated", aMapWithSize(0)) .body(ARGUMENTS + ".created", aMapWithSize(1)).body(createdPath + ".attachments", hasSize(3)) .body(firstAttachment + ".name", equalTo(".png")) .body(secondAttachment + ".name", equalTo("?.png")) .body(thirdAttachment + ".name", equalTo("?.png")); } @Test public void filenamesAttachmentsWithNonASCIICharactersShouldBeRetrievedWhenChainingSetMessagesAndGetMessages() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes("attachment".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment); Attachment attachment2 = Attachment.builder().bytes("attachment2".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment2); Attachment attachment3 = Attachment.builder().bytes("attachment3".getBytes(Charsets.UTF_8)) .type("application/octet-stream").build(); uploadAttachment(attachment3); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\":" + " {" + " \"" + messageCreationId + "\" : " + " {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with three attachments with non ASCII name\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\":" + " [" + " {" + " \"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + "," + " \"name\" : \".png\", " + " \"isInline\" : false" + " }," + " {" + " \"blobId\" : \"" + attachment2.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment2.getType() + "\", " + " \"size\" : " + attachment2.getSize() + "," + " \"name\" : \"?.png\", " + " \"isInline\" : false" + " }," + " {" + " \"blobId\" : \"" + attachment3.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment3.getType() + "\", " + " \"size\" : " + attachment3.getSize() + "," + " \"name\" : \"?.png\"," + " \"isInline\" : false" + " }" + " ]" + " }" + " }" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap").then(); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String message = ARGUMENTS + ".list[0]"; String firstAttachment = message + ".attachments[0]"; String secondAttachment = message + ".attachments[1]"; String thirdAttachment = message + ".attachments[2]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(message + ".attachments", hasSize(3)) .body(firstAttachment + ".name", equalTo(".png")) .body(secondAttachment + ".name", equalTo("?.png")) .body(thirdAttachment + ".name", equalTo("?.png")); } private void uploadAttachment(Attachment attachment) throws IOException { with().header("Authorization", accessToken.serialize()).contentType(attachment.getType()) .content(attachment.getStream()).post("/upload"); } private void uploadTextAttachment(Attachment attachment) throws IOException { with().header("Authorization", accessToken.serialize()).contentType(attachment.getType()) .content(new String(IOUtils.toByteArray(attachment.getStream()), Charsets.UTF_8)).post("/upload"); } @Test public void attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesBinaryAttachment() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); byte[] rawBytes = new byte[] { -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127 }; Attachment attachment = Attachment.builder().bytes(rawBytes).type("application/octet-stream").build(); uploadAttachment(attachment); String expectedBlobId = attachment.getAttachmentId().getId(); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with an attachment\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + ", " + " \"cid\" : \"123456789\", " + " \"isInline\" : true }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap"); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String firstMessage = ARGUMENTS + ".list[0]"; String firstAttachment = firstMessage + ".attachments[0]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(firstMessage + ".attachments", hasSize(1)) .body(firstAttachment + ".blobId", equalTo(expectedBlobId)) .body(firstAttachment + ".type", equalTo("application/octet-stream")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())) .body(firstAttachment + ".cid", equalTo("123456789")) .body(firstAttachment + ".isInline", equalTo(true)); } @Test public void attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesTextAttachment() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes(ByteStreams.toByteArray(new ZeroedInputStream(_1MB))) .type("application/octet-stream").build(); uploadAttachment(attachment); String expectedBlobId = attachment.getAttachmentId().getId(); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with an attachment\"," + " \"textBody\": \"Test body\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + ", " + " \"cid\" : \"123456789\", " + " \"isInline\" : true }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap"); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String firstMessage = ARGUMENTS + ".list[0]"; String firstAttachment = firstMessage + ".attachments[0]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(firstMessage + ".attachments", hasSize(1)) .body(firstAttachment + ".blobId", equalTo(expectedBlobId)) .body(firstAttachment + ".type", equalTo("application/octet-stream")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())) .body(firstAttachment + ".cid", equalTo("123456789")) .body(firstAttachment + ".isInline", equalTo(true)); } private boolean isAnyMessageFoundInInbox(AccessToken recipientToken) { try { String inboxId = getMailboxId(accessToken, Role.INBOX); with().header("Authorization", recipientToken.serialize()) .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]") .when().post("/jmap").then().statusCode(200).body(NAME, equalTo("messageList")) .body(ARGUMENTS + ".messageIds", hasSize(1)); return true; } catch (AssertionError e) { return false; } } @Test public void attachmentsAndBodysShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithMixedTextAndHtmlBodyAndHtmlAttachment() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes(("<html>\n" + " <body>attachment</body>\n" + // needed indentation, else restassured is adding some "</html>").getBytes(Charsets.UTF_8)).type("text/html; charset=UTF-8").build(); uploadTextAttachment(attachment); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with an attachment\"," + " \"textBody\": \"Test body, plain text version\"," + " \"htmlBody\": \"Test <b>body</b>, HTML version\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + ", " + " \"isInline\" : false }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap"); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String firstMessage = ARGUMENTS + ".list[0]"; String firstAttachment = firstMessage + ".attachments[0]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)) .body(firstMessage + ".textBody", equalTo("Test body, plain text version")) .body(firstMessage + ".htmlBody", equalTo("Test <b>body</b>, HTML version")) .body(firstMessage + ".attachments", hasSize(1)) .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId())) .body(firstAttachment + ".type", equalTo("text/html")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())); } @Test public void attachmentsAndBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextBodyAndHtmlAttachment() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes(("<html>\n" + " <body>attachment</body>\n" + // needed indentation, else restassured is adding some "</html>").getBytes(Charsets.UTF_8)).type("text/html; charset=UTF-8").build(); uploadTextAttachment(attachment); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with an attachment\"," + " \"textBody\": \"Test body, plain text version\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + ", " + " \"isInline\" : false }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap"); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String firstMessage = ARGUMENTS + ".list[0]"; String firstAttachment = firstMessage + ".attachments[0]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)) .body(firstMessage + ".textBody", equalTo("Test body, plain text version")) .body(firstMessage + ".htmlBody", isEmptyOrNullString()) .body(firstMessage + ".attachments", hasSize(1)) .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId())) .body(firstAttachment + ".type", equalTo("text/html")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())); } @Test public void attachmentAndEmptyBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextAttachmentWithoutMailBody() throws Exception { jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent"); Attachment attachment = Attachment.builder().bytes(("some text").getBytes(Charsets.UTF_8)) .type("text/plain; charset=UTF-8").build(); uploadTextAttachment(attachment); String messageCreationId = "creationId"; String fromAddress = username; String outboxId = getOutboxId(accessToken); String requestBody = "[" + " [" + " \"setMessages\"," + " {" + " \"create\": { \"" + messageCreationId + "\" : {" + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + " \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," + " \"subject\": \"Message with an attachment\"," + " \"mailboxIds\": [\"" + outboxId + "\"], " + " \"attachments\": [" + " {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " + " \"type\" : \"" + attachment.getType() + "\", " + " \"size\" : " + attachment.getSize() + ", " + " \"isInline\" : false }" + " ]" + " }}" + " }," + " \"#0\"" + " ]" + "]"; given().header("Authorization", accessToken.serialize()).body(requestBody).when().post("/jmap"); calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken)); String firstMessage = ARGUMENTS + ".list[0]"; String firstAttachment = firstMessage + ".attachments[0]"; String presumedMessageId = "username@domain.tld|INBOX|1"; given().header("Authorization", accessToken.serialize()) .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]").when() .post("/jmap").then().statusCode(200).log().ifValidationFails().body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)).body(firstMessage + ".textBody", isEmptyOrNullString()) .body(firstMessage + ".htmlBody", isEmptyOrNullString()) .body(firstMessage + ".attachments", hasSize(1)) .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId())) .body(firstAttachment + ".type", equalTo("text/plain")) .body(firstAttachment + ".size", equalTo((int) attachment.getSize())); } }