blob: de65cf9a3c4a324884c2e82017b07c2fffcf55e0 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.jmap.draft.methods.integration;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.with;
import static io.restassured.config.EncoderConfig.encoderConfig;
import static io.restassured.config.RestAssuredConfig.newConfig;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.apache.james.jmap.JMAPTestingConstants.ALICE;
import static org.apache.james.jmap.JMAPTestingConstants.ALICE_PASSWORD;
import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS;
import static org.apache.james.jmap.JMAPTestingConstants.BOB;
import static org.apache.james.jmap.JMAPTestingConstants.BOB_PASSWORD;
import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN;
import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN_ALIAS;
import static org.apache.james.jmap.JMAPTestingConstants.FIRST_MAILBOX;
import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP;
import static org.apache.james.jmap.JMAPTestingConstants.NAME;
import static org.apache.james.jmap.JMAPTestingConstants.SECOND_ARGUMENTS;
import static org.apache.james.jmap.JMAPTestingConstants.SECOND_NAME;
import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait;
import static org.apache.james.jmap.JmapCommonRequests.getDraftId;
import static org.apache.james.jmap.JmapCommonRequests.getInboxId;
import static org.apache.james.jmap.JmapCommonRequests.getMailboxId;
import static org.apache.james.jmap.JmapCommonRequests.getOutboxId;
import static org.apache.james.jmap.JmapCommonRequests.getSentId;
import static org.apache.james.jmap.JmapCommonRequests.getSetMessagesUpdateOKResponseAssertions;
import static org.apache.james.jmap.JmapURIBuilder.baseUri;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
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.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.collection.IsMapWithSize.aMapWithSize;
import static org.hamcrest.collection.IsMapWithSize.anEmptyMap;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import jakarta.mail.Flags;
import jakarta.mail.Flags.Flag;
import jakarta.mail.internet.MimeMessage;
import org.apache.james.GuiceJamesServer;
import org.apache.james.core.Domain;
import org.apache.james.core.Username;
import org.apache.james.core.quota.QuotaSizeLimit;
import org.apache.james.events.Event;
import org.apache.james.jmap.AccessToken;
import org.apache.james.jmap.HttpJmapAuthentication;
import org.apache.james.jmap.JmapCommonRequests;
import org.apache.james.jmap.MessageAppender;
import org.apache.james.jmap.JmapGuiceProbe;
import org.apache.james.jmap.MessageIdProbe;
import org.apache.james.junit.categories.BasicFeature;
import org.apache.james.mailbox.DefaultMailboxes;
import org.apache.james.mailbox.FlagsBuilder;
import org.apache.james.mailbox.Role;
import org.apache.james.mailbox.events.MailboxEvents.Added;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.model.AttachmentMetadata;
import org.apache.james.mailbox.model.ComposedMessageId;
import org.apache.james.mailbox.model.MailboxACL;
import org.apache.james.mailbox.model.MailboxConstants;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MessageResult;
import org.apache.james.mailbox.model.QuotaRoot;
import org.apache.james.mailbox.model.StringBackedAttachmentId;
import org.apache.james.mailbox.probe.ACLProbe;
import org.apache.james.mailbox.probe.MailboxProbe;
import org.apache.james.mailbox.probe.QuotaProbe;
import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
import org.apache.james.mailbox.util.EventCollector;
import org.apache.james.modules.ACLProbeImpl;
import org.apache.james.modules.MailboxProbeImpl;
import org.apache.james.modules.QuotaProbesImpl;
import org.apache.james.modules.protocols.ImapGuiceProbe;
import org.apache.james.modules.protocols.SmtpGuiceProbe;
import org.apache.james.probe.DataProbe;
import org.apache.james.util.ClassLoaderUtils;
import org.apache.james.util.MimeMessageUtil;
import org.apache.james.util.io.ZeroedInputStream;
import org.apache.james.utils.DataProbeImpl;
import org.apache.james.utils.SMTPMessageSender;
import org.apache.james.utils.TestIMAPClient;
import org.apache.mailet.Mail;
import org.apache.mailet.base.test.FakeMail;
import org.assertj.core.api.SoftAssertions;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.filter.log.LogDetail;
import io.restassured.http.ContentType;
import io.restassured.parsing.Parser;
import io.restassured.path.json.JsonPath;
import net.javacrumbs.jsonunit.core.Option;
import net.javacrumbs.jsonunit.core.internal.Options;
public abstract class SetMessagesMethodTest {
private static final String FORWARDED = "$Forwarded";
private static final int _1MB = 1024 * 1024;
protected static final Username USERNAME = Username.of("username@" + DOMAIN);
private static final String ALIAS_OF_USERNAME_MAIL = "alias@" + DOMAIN;
private static final String GROUP_MAIL = "group@" + DOMAIN;
private static final Username ALIAS_OF_USERNAME = Username.of(ALIAS_OF_USERNAME_MAIL);
private static final String PASSWORD = "password";
protected static final MailboxPath USER_MAILBOX = MailboxPath.forUser(USERNAME, "mailbox");
private static final String NOT_UPDATED = ARGUMENTS + ".notUpdated";
private static final int BIG_MESSAGE_SIZE = 9 * 1024 * 1024;
public static final String OCTET_CONTENT_TYPE = "application/octet-stream";
public static final String OCTET_CONTENT_TYPE_UTF8 = "application/octet-stream; charset=UTF-8";
private AccessToken bobAccessToken;
protected abstract GuiceJamesServer createJmapServer() throws IOException;
protected abstract MessageId randomMessageId();
protected AccessToken accessToken;
protected GuiceJamesServer jmapServer;
protected MailboxProbe mailboxProbe;
private DataProbe dataProbe;
protected MessageIdProbe messageProbe;
private ACLProbe aclProbe;
@Before
public void setup() throws Throwable {
jmapServer = createJmapServer();
jmapServer.start();
mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class);
dataProbe = jmapServer.getProbe(DataProbeImpl.class);
messageProbe = jmapServer.getProbe(MessageIdProbe.class);
aclProbe = jmapServer.getProbe(ACLProbeImpl.class);
RestAssured.requestSpecification = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.setAccept(ContentType.JSON)
.setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8)))
.setPort(jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort().getValue())
.build();
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
RestAssured.defaultParser = Parser.JSON;
dataProbe.addDomain(DOMAIN);
dataProbe.addUser(USERNAME.asString(), PASSWORD);
dataProbe.addUser(BOB.asString(), BOB_PASSWORD);
mailboxProbe.createMailbox("#private", USERNAME.asString(), DefaultMailboxes.INBOX);
accessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), USERNAME, PASSWORD);
bobAccessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), BOB, BOB_PASSWORD);
}
@After
public void teardown() {
jmapServer.stop();
}
@Test
public void setMessagesShouldReturnAnErrorNotSupportedWhenRequestContainsNonNullAccountId() {
given()
.header("Authorization", accessToken.asString())
.body("[[\"setMessages\", {\"accountId\": \"1\"}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("error"))
.body(ARGUMENTS + ".type", equalTo("invalidArguments"))
.body(ARGUMENTS + ".description", equalTo("The field 'accountId' of 'SetMessagesRequest' is not supported"));
}
@Test
public void setMessagesShouldReturnAnErrorNotSupportedWhenRequestContainsNonNullIfInState() {
given()
.header("Authorization", accessToken.asString())
.body("[[\"setMessages\", {\"ifInState\": \"1\"}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("error"))
.body(ARGUMENTS + ".type", equalTo("invalidArguments"))
.body(ARGUMENTS + ".description", equalTo("The field 'ifInState' of 'SetMessagesRequest' is not supported"));
}
@Test
public void setMessagesShouldReturnNotDestroyedWhenUnknownMailbox() {
String unknownMailboxMessageId = randomMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.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", "notFound"),
hasEntry("description", "The message " + unknownMailboxMessageId + " can't be found"),
hasEntry(equalTo("properties"), is(nullValue()))))
);
}
@Test
public void setMessagesShouldReturnNotDestroyedWhenNoMatchingMessage() {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
String messageId = randomMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.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"), is(nullValue()))))
);
}
@Test
public void setMessagesShouldReturnDestroyedWhenMatchingMessage() throws Exception {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
given()
.header("Authorization", accessToken.asString())
.body("[[\"setMessages\", {\"destroy\": [\"" + message.getMessageId().serialize() + "\"]}, \"#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(message.getMessageId().serialize()));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldDeleteMessageWhenMatchingMessage() throws Exception {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
// When
given()
.header("Authorization", accessToken.asString())
.body("[[\"setMessages\", {\"destroy\": [\"" + message.getMessageId().serialize() + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200);
// Then
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + message.getMessageId().serialize() + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", empty());
}
@Test
public void setMessagesShouldReturnDestroyedNotDestroyWhenMixed() throws Exception {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message1 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message3 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String missingMessageId = randomMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"destroy\": [\"%s\", \"%s\", \"%s\"]}, \"#0\"]]",
message1.getMessageId().serialize(),
missingMessageId,
message3.getMessageId().serialize()))
.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", containsInAnyOrder(message1.getMessageId().serialize(), message3.getMessageId().serialize()))
.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
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message1 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message2 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message3 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
// When
with()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"destroy\": [\"%s\", \"%s\", \"%s\"]}, \"#0\"]]",
message1.getMessageId().serialize(),
randomMessageId().serialize(),
message3.getMessageId().serialize()))
.post("/jmap");
// Then
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"getMessages\", {\"ids\": [\"%s\", \"%s\", \"%s\"]}, \"#0\"]]",
message1.getMessageId().serialize(),
message2.getMessageId().serialize(),
message3.getMessageId().serialize()))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1));
}
@Test
public void setMessagesShouldReturnUpdatedIdAndNoErrorWhenIsUnreadPassedToFalse() throws MailboxException {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
// When
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
}
@Test
public void massiveFlagUpdateShouldBeApplied() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message1 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message2 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message3 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flag.SEEN));
ComposedMessageId message4 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message5 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flag.ANSWERED));
ComposedMessageId message6 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId1 = message1.getMessageId().serialize();
String serializedMessageId2 = message2.getMessageId().serialize();
String serializedMessageId3 = message3.getMessageId().serialize();
String serializedMessageId4 = message4.getMessageId().serialize();
String serializedMessageId5 = message5.getMessageId().serialize();
String serializedMessageId6 = message6.getMessageId().serialize();
// When
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {" +
" \"%s\" : { \"isUnread\" : false }, " +
" \"%s\" : { \"isUnread\" : false }, " +
" \"%s\" : { \"isUnread\" : false }, " +
" \"%s\" : { \"isUnread\" : false }, " +
" \"%s\" : { \"isUnread\" : false }, " +
" \"%s\" : { \"isUnread\" : false } " +
"} }, \"#0\"]]", serializedMessageId1, serializedMessageId2, serializedMessageId3,
serializedMessageId4, serializedMessageId5, serializedMessageId6))
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails().body(ARGUMENTS + ".updated", hasSize(6));
Flags flags1 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
Flags flags2 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
Flags flags3 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
Flags flags4 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
Flags flags5 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
Flags flags6 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(flags1).isEqualTo(new Flags(Flag.SEEN));
softly.assertThat(flags2).isEqualTo(new Flags(Flag.SEEN));
softly.assertThat(flags3).isEqualTo(new Flags(Flag.SEEN));
softly.assertThat(flags4).isEqualTo(new Flags(Flag.SEEN));
softly.assertThat(flags5).isEqualTo(new Flags(Flag.SEEN));
softly.assertThat(flags6).isEqualTo(new Flags(Flag.SEEN));
});
}
@Test
public void setMessagesWithUpdateShouldReturnAnErrorWhenBothIsFlagAndKeywordsArePassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false, \"keywords\": {\"$Seen\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NOT_UPDATED, hasKey(messageId))
.body(NOT_UPDATED + "[\"" + messageId + "\"].type", equalTo("invalidProperties"))
.body(NOT_UPDATED + "[\"" + messageId + "\"].description", containsString("Does not support keyword and is* at the same time"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldUpdateKeywordsWhenKeywordsArePassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Seen\": true, \"$Flagged\": true} } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].keywords.$Seen", equalTo(true))
.body(ARGUMENTS + ".list[0].keywords.$Flagged", equalTo(true));
}
@Test
public void setMessagesShouldAddForwardedFlagWhenKeywordsWithForwardedIsPassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Seen\": true, \"$Forwarded\": true} } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].keywords.$Seen", equalTo(true))
.body(ARGUMENTS + ".list[0].keywords.$Forwarded", equalTo(true));
}
@Test
public void setMessagesShouldRemoveForwardedFlagWhenKeywordsWithoutForwardedIsPassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
Flags flags = FlagsBuilder.builder()
.add(Flag.SEEN)
.add(FORWARDED)
.build();
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, flags);
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Seen\": true} } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].keywords.$Seen", equalTo(true));
}
@Test
public void setMessagesShouldReturnAnErrorWhenKeywordsWithDeletedArePassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.ANSWERED));
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Answered\": true, \"$Deleted\" : true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NOT_UPDATED, hasKey(messageId))
.body(NOT_UPDATED + "[\"" + messageId + "\"].type", equalTo("invalidProperties"))
.body(NOT_UPDATED + "[\"" + messageId + "\"].description", containsString("Does not allow to update 'Deleted' or 'Recent' flag"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Test
public void setMessagesShouldReturnAnErrorWhenKeywordsWithRecentArePassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.ANSWERED));
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Answered\": true, \"$Recent\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NOT_UPDATED, hasKey(messageId))
.body(NOT_UPDATED + "[\"" + messageId + "\"].type", equalTo("invalidProperties"))
.body(NOT_UPDATED + "[\"" + messageId + "\"].description", containsString("Does not allow to update 'Deleted' or 'Recent' flag"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Test
public void setMessagesShouldNotChangeOriginDeletedFlag() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.DELETED));
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Answered\": true, \"$Forwarded\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(messageId));
List<MessageResult> messages = messageProbe.getMessages(message.getMessageId(), USERNAME);
Flags expectedFlags = FlagsBuilder.builder()
.add(Flag.ANSWERED, Flag.DELETED)
.add(FORWARDED)
.build();
assertThat(messages)
.hasSize(1)
.extracting(MessageResult::getFlags)
.containsOnly(expectedFlags);
}
@Test
public void setMessagesShouldNotChangeOriginRecentFlag() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
Flags flags = FlagsBuilder.builder()
.add(Flag.DELETED, Flag.RECENT)
.build();
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, flags);
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Answered\": true, \"$Forwarded\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(messageId));
List<MessageResult> messages = messageProbe.getMessages(message.getMessageId(), USERNAME);
Flags expectedFlags = FlagsBuilder.builder()
.add(Flag.ANSWERED, Flag.DELETED, Flag.RECENT)
.add(FORWARDED)
.build();
assertThat(messages)
.hasSize(1)
.extracting(MessageResult::getFlags)
.containsOnly(expectedFlags);
}
@Test
public void setMessagesShouldReturnNewKeywordsWhenKeywordsArePassedToRemoveAndAddFlag() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
Flags currentFlags = FlagsBuilder.builder()
.add(Flag.DRAFT, Flag.ANSWERED)
.build();
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, currentFlags);
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Answered\": true, \"$Flagged\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(messageId));
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].keywords.$Answered", equalTo(true))
.body(ARGUMENTS + ".list[0].keywords.$Flagged", equalTo(true));
}
@Test
public void setMessagesShouldMarkAsReadWhenIsUnreadPassedToFalse() throws MailboxException {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap");
// Then
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#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
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
}
@Test
public void setMessagesShouldMarkAsUnreadWhenIsUnreadPassed() throws MailboxException {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap");
// Then
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#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
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
}
@Test
public void setMessagesShouldMarkAsFlaggedWhenIsFlaggedPassed() throws MailboxException {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap");
// Then
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#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 {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String messageId = randomMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.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", containsString("isUnread: Cannot deserialize value of type `java.lang.Boolean` from String \"123\": only \"true\" or \"false\" recognized"))
.body(NOT_UPDATED + "[\"" + messageId + "\"].description", containsString("(through reference chain: org.apache.james.jmap.draft.model.UpdateMessagePatch$Builder[\"isUnread\"])"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Test
@Ignore("Jackson json deserializer stops after first error found")
public void setMessagesShouldRejectUpdateWhenPropertiesHaveWrongTypes() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String messageId = USERNAME.asString() + "|mailbox|1";
given()
.header("Authorization", accessToken.asString())
.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
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
// When
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
}
@Test
public void setMessagesShouldMarkAsAnsweredWhenIsAnsweredPassed() throws MailboxException {
// Given
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", serializedMessageId))
// When
.when()
.post("/jmap");
// Then
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#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 setMessagesShouldMarkMessageAsForwardWhenIsForwardedPassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isForwarded\" : true } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(serializedMessageId));
}
@Test
public void setMessagesShouldMarkAsForwardedWhenIsForwardedPassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isForwarded\" : true } } }, \"#0\"]]", serializedMessageId))
.when()
.post("/jmap");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + serializedMessageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isForwarded", equalTo(true));
}
@Test
public void setMessagesShouldReturnNotFoundWhenUpdateUnknownMessage() {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
String nonExistingMessageId = randomMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.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));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldReturnCreatedMessageWhenSendingMessage() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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(is(nullValue()))),
hasEntry(equalTo("blobId"), not(is(nullValue()))),
hasEntry(equalTo("threadId"), not(is(nullValue()))),
hasEntry(equalTo("size"), not(is(nullValue())))
)))
// assert that message FLAGS are all unset
.body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
hasEntry(equalTo("isDraft"), equalTo(false)),
hasEntry(equalTo("isUnread"), equalTo(true)),
hasEntry(equalTo("isFlagged"), equalTo(false)),
hasEntry(equalTo("isAnswered"), equalTo(false))
)))
;
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldNotCreateOverSizedMessages() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String longText = "0123456789\\r\\n".repeat(1024 * 1024);
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\": \"" + longText + "\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap").prettyPeek()
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".type", equalTo("invalidArguments"))
// Message size is date-time and matchine (Message-Id) dependant
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description",
startsWith("Attempt to create a message of "))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description",
endsWith("bytes while the maximum allowed is 10485760"));
}
@Category(BasicFeature.class)
@Test
public void sendingAMailShouldLeadToAppropriateMailboxCountersOnOutbox() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.until(
() -> JmapCommonRequests.isAnyMessageFoundInRecipientsMailbox(accessToken, getSentId(accessToken)));
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMailboxes\", {" +
" \"ids\": [\"" + getOutboxId(accessToken) + "\"], " +
" \"properties\" : [\"unreadMessages\", \"totalMessages\"]}, " +
"\"#0\"]]")
.log().ifValidationFails()
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("mailboxes"))
.body(FIRST_MAILBOX + ".totalMessages", equalTo(0))
.body(FIRST_MAILBOX + ".unreadMessages", equalTo(0));
}
@Test
public void massiveMessageMoveShouldBeApplied() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.DRAFTS);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.OUTBOX);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.SENT);
MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.TRASH);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.SPAM);
ComposedMessageId message1 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message2 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message3 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
ComposedMessageId message4 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message5 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.ANSWERED));
ComposedMessageId message6 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message7 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message8 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message9 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
ComposedMessageId message10 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
ComposedMessageId message11 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.ANSWERED));
ComposedMessageId message12 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String serializedMessageId1 = message1.getMessageId().serialize();
String serializedMessageId2 = message2.getMessageId().serialize();
String serializedMessageId3 = message3.getMessageId().serialize();
String serializedMessageId4 = message4.getMessageId().serialize();
String serializedMessageId5 = message5.getMessageId().serialize();
String serializedMessageId6 = message6.getMessageId().serialize();
String serializedMessageId7 = message7.getMessageId().serialize();
String serializedMessageId8 = message8.getMessageId().serialize();
String serializedMessageId9 = message9.getMessageId().serialize();
String serializedMessageId10 = message10.getMessageId().serialize();
String serializedMessageId11 = message11.getMessageId().serialize();
String serializedMessageId12 = message12.getMessageId().serialize();
// When
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {" +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]}, " +
" \"%s\" : { \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]} " +
"} }, \"#0\"]]", serializedMessageId1, serializedMessageId2, serializedMessageId3,
serializedMessageId4, serializedMessageId5, serializedMessageId6,
serializedMessageId7, serializedMessageId8, serializedMessageId9,
serializedMessageId10, serializedMessageId11, serializedMessageId12))
.when()
.post("/jmap")
// Then
.then()
.log().ifValidationFails().body(ARGUMENTS + ".updated", hasSize(12));
}
@Category(BasicFeature.class)
@Test
public void sendingAMailShouldLeadToAppropriateMailboxCountersOnSent() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.until(
() -> JmapCommonRequests.isAnyMessageFoundInRecipientsMailbox(accessToken, getSentId(accessToken)));
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMailboxes\", {" +
" \"ids\": [\"" + getSentId(accessToken) + "\"], " +
" \"properties\" : [\"unreadMessages\", \"totalMessages\"]}, " +
"\"#0\"]]")
.log().ifValidationFails()
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("mailboxes"))
.body(FIRST_MAILBOX + ".totalMessages", equalTo(1))
.body(FIRST_MAILBOX + ".unreadMessages", equalTo(0));
}
@Test
public void setMessagesShouldReturnCreatedMessageWithEmptySubjectWhenSubjectIsNull() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 setMessagesShouldReturnCreatedMessageWithEmptySubjectWhenSubjectIsEmpty() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 setMessagesShouldReturnValidErrorWhenMailboxNotFound() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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", bobAccessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".type", equalTo("anErrorOccurred"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description", endsWith("can not be found"));
}
@Test
public void setMessagesShouldReturnCreatedMessageWithNonASCIICharactersInSubjectWhenPresent() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 setMessagesShouldReturnErrorWhenUserIsNotTheOwnerOfOneOfTheMailboxes() throws Exception {
dataProbe.addUser(ALICE.asString(), ALICE_PASSWORD);
MailboxId aliceOutbox = mailboxProbe.createMailbox("#private", ALICE.asString(), DefaultMailboxes.OUTBOX);
aclProbe.replaceRights(MailboxPath.forUser(ALICE, DefaultMailboxes.OUTBOX), USERNAME.asString(), MailboxACL.FULL_RIGHTS);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"\"," +
" \"mailboxIds\": [\"" + aliceOutbox.serialize() + "\", \"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("anErrorOccurred"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("mailboxIds"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("MailboxId invalid"));
}
@Test
public void setMessageWithCreatedMessageShouldReturnAnErrorWhenBothIsFlagAndKeywordsPresent() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isDraft\": true," +
" \"keywords\": {\"$Answered\": true}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("error"))
.body(ARGUMENTS + ".type", equalTo("invalidArguments"))
.body(ARGUMENTS + ".description", containsString("Does not support keyword and is* at the same time"));
}
@Test
public void setMessageWithCreatedMessageShouldSupportKeywordsForFlags() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Answered\": true, \"$Flagged\": true}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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 + "\"].keywords.$Answered", equalTo(true))
.body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].keywords.$Flagged", equalTo(true));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldAllowDraftCreation() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldNotAllowDraftCreationWhenOverQuota() throws MailboxException {
QuotaProbe quotaProbe = jmapServer.getProbe(QuotaProbesImpl.class);
QuotaRoot inboxQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(USERNAME));
quotaProbe.setMaxStorage(inboxQuotaRoot, QuotaSizeLimit.size(100));
MessageAppender.fillMailbox(mailboxProbe, USERNAME.asString(), MailboxConstants.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("maxQuotaReached"));
}
@Category(BasicFeature.class)
@Test
public void setMessagesWithABigBodyShouldReturnCreatedMessageWhenSendingMessage() {
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.HEADERS);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String body = Strings.repeat("d", BIG_MESSAGE_SIZE);
{
String requestBody = new StringBuilder(BIG_MESSAGE_SIZE + 10 * 1024)
.append("[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"")
.append(body)
.append("\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]")
.toString();
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", aMapWithSize(0))
.body(ARGUMENTS + ".created", aMapWithSize(1))
.body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), hasEntry(equalTo("textBody"), equalTo(body))));
}
calmlyAwait
.pollDelay(Duration.ofMillis(500))
.atMost(30, TimeUnit.SECONDS).until(() -> hasANewMailWithBody(accessToken, body));
}
private boolean hasANewMailWithBody(AccessToken recipientToken, String body) {
try {
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].textBody", equalTo(body));
return true;
} catch (AssertionError e) {
return false;
}
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldNotAllowCopyWhenOverQuota() throws MailboxException {
QuotaProbe quotaProbe = jmapServer.getProbe(QuotaProbesImpl.class);
QuotaRoot inboxQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(USERNAME));
quotaProbe.setMaxStorage(inboxQuotaRoot, QuotaSizeLimit.size(100));
List<ComposedMessageId> composedMessageIds = MessageAppender.fillMailbox(mailboxProbe, USERNAME.asString(), MailboxConstants.INBOX);
String messageId = composedMessageIds.get(0).getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"mailboxIds\": [\"" + getInboxId(accessToken) + "\",\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".updated", hasSize(0))
.body(ARGUMENTS + ".notUpdated", aMapWithSize(1))
.body(ARGUMENTS + ".notUpdated." + messageId + ".type", equalTo("maxQuotaReached"));
}
@Test
public void setMessagesShouldCreateDraftInSeveralMailboxes() {
MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String draftId = getDraftId(accessToken);
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + mailboxId.serialize() + "\", \"" + draftId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].mailboxIds", containsInAnyOrder(mailboxId.serialize(), draftId));
}
@Test
public void setMessagesShouldAllowDraftCreationOutsideOfDraftMailbox() {
MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldRejectMessageCreationWithNoMailbox() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": []" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description", equalTo("Message needs to be in at least one mailbox"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".properties", contains("mailboxIds"));
}
@Test
public void setMessagesShouldNotFailWhenSavingADraftInSeveralMailboxes() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\", \"" + mailboxId.serialize() + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldAllowDraftCreationWhenUsingIsDraftProperty() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isDraft\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldMarkAsDraftWhenIsDraftPassed() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isDraft\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isDraft", equalTo(true));
}
@Test
public void setMessagesShouldRejectCreateInDraftAndOutboxForASingleMessage() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\", \"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description", equalTo("Message creation is only supported in mailboxes with role Draft and Outbox"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".properties", contains("mailboxIds"));
}
@Test
public void setMessagesShouldStoreDraft() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + messageCreationId + "\"].id");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".id", equalTo(receivedMessageId));
}
@Test
public void setMessagesShouldNotCheckFromWhenDraft() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"invalid@domain.com\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldNotCheckFromWhenInvalidEmailWhenDraft() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"invalid\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldAllowDraftCreationWithoutFrom() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldAllowDraftCreationWithoutRecipients() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"invalid@domain.com\"}," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldRequireDraftFlagWhenSavingDraft() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Flagged\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("keywords"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", equalTo("A draft message should be flagged as Draft"));
}
@Test
public void setMessagesShouldCheckAttachmentsWhenDraft() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"attachments\": [" +
" {\"blobId\" : \"wrong\", \"type\" : \"image/jpeg\", \"size\" : 1337}" +
" ]," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("attachments"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", equalTo("Attachment not found"));
}
@Test
public void setMessagesShouldAcceptAttachmentsWhenDraft() throws Exception {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String bytes = "attachment";
AttachmentMetadata uploadedAttachment = uploadAttachment(OCTET_CONTENT_TYPE, bytes.getBytes(StandardCharsets.UTF_8));
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"attachments\": [" +
" {\"blobId\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\"," +
" \"size\" : " + uploadedAttachment.getSize() + "}" +
" ]," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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));
}
@Test
public void setMessagesShouldNotAllowDraftCreationInSomeoneElseMailbox() throws Exception {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", bobAccessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("anErrorOccurred"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("can not be found"));
}
@Test
public void setMessagesShouldNotAllowDraftCreationInADelegatedMailbox() throws Exception {
String messageCreationId = "creationId1337";
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.DRAFTS);
aclProbe.addRights(
MailboxPath.forUser(USERNAME, DefaultMailboxes.DRAFTS),
BOB.asString(),
MailboxACL.FULL_RIGHTS);
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", bobAccessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("anErrorOccurred"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].properties", contains("mailboxIds"))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].description", endsWith("MailboxId invalid"));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldSendMessageByMovingDraftToOutbox() {
String draftCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String createDraft = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + draftCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String draftId =
with()
.header("Authorization", accessToken.asString())
.body(createDraft)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id");
String moveDraftToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + draftId + "\" : {" +
" \"keywords\": {}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(moveDraftToOutBox)
.post("/jmap");
calmlyAwait
.pollDelay(Duration.ofMillis(500))
.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken));
}
@Test
public void setMessagesShouldSendMessageByMovingDraftToOutboxForAMailSentFromAnAlias() throws Exception {
dataProbe.addUserAliasMapping(Username.of(ALIAS_OF_USERNAME_MAIL).getLocalPart(), ALIAS_OF_USERNAME.getDomainPart().get().asString(), USERNAME.asString());
String draftCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String createDraft = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + draftCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + ALIAS_OF_USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String draftId =
with()
.header("Authorization", accessToken.asString())
.body(createDraft)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id");
String moveDraftToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + draftId + "\" : {" +
" \"keywords\": {}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(moveDraftToOutBox)
.post("/jmap");
calmlyAwait
.pollDelay(Duration.ofMillis(500))
.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken));
}
@Test
public void setMessagesShouldRejectDraftCopyToOutbox() {
String draftCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String createDraft = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + draftCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String draftId =
with()
.header("Authorization", accessToken.asString())
.body(createDraft)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id");
String copyDraftToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + draftId + "\" : {" +
" \"keywords\": {\"$Draft\":true}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\",\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(copyDraftToOutBox)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notUpdated", hasKey(draftId))
.body(ARGUMENTS + ".notUpdated[\"" + draftId + "\"].type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notUpdated[\"" + draftId + "\"].description", endsWith("When moving a message to Outbox, only Outboxes mailboxes should be targeted."))
.body(ARGUMENTS + ".notUpdated[\"" + draftId + "\"].properties", hasSize(1))
.body(ARGUMENTS + ".notUpdated[\"" + draftId + "\"].properties", contains("mailboxIds"))
.body(ARGUMENTS + ".created", aMapWithSize(0));
}
@Test
public void setMessagesShouldRejectMovingMessageToOutboxWhenNotInDraft() throws MailboxException {
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String messageId = message.getMessageId().serialize();
String moveMessageToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(moveMessageToOutBox)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notUpdated", hasKey(messageId))
.body(ARGUMENTS + ".notUpdated[\"" + messageId + "\"].type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notUpdated[\"" + messageId + "\"].description", endsWith("Only message with `$Draft` keyword can be moved to Outbox"))
.body(ARGUMENTS + ".notUpdated[\"" + messageId + "\"].properties", hasSize(1))
.body(ARGUMENTS + ".notUpdated[\"" + messageId + "\"].properties", contains("mailboxIds"))
.body(ARGUMENTS + ".created", aMapWithSize(0));
}
@Test
public void setMessagesShouldSupportArbitraryMessageId() {
String messageCreationId = "1717fcd1-603e-44a5-b2a6-1234dbcd5723";
String fromAddress = USERNAME.asString();
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.asString())
.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 = "creationId1337";
String fromAddress = USERNAME.asString();
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\"" +
" ]" +
"]";
EventCollector eventCollector = new EventCollector();
jmapServer.getProbe(JmapGuiceProbe.class).addEventListener(eventCollector);
String messageId = with()
.header("Authorization", accessToken.asString())
.body(requestBody)
// When
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(5, TimeUnit.SECONDS).until(() -> eventCollector.getEvents().stream()
.anyMatch(event -> isAddedToOutboxEvent(messageId, event, outboxId)));
}
private boolean isAddedToOutboxEvent(String messageId, Event event, String outboxId) {
if (!(event instanceof Added)) {
return false;
}
Added added = (Added) event;
return added.getMailboxId().serialize().equals(outboxId)
&& added.getUids().size() == 1
&& added.getMetaData(added.getUids().iterator().next()).getMessageId().serialize().equals(messageId);
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldMoveMessageInSentWhenMessageIsSent() {
// Given
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
String fromAddress = USERNAME.asString();
String messageCreationId = "creationId1337";
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.asString())
.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.asString())
.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 = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 = "creationId1337";
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.asString())
.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 setMessagesShouldReturnNotCreatedWhenSendingMessageWithAnotherFromAddressThanTheConnectedUser() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"wrongaddress@otherdomain.org\"}," +
" \"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.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".type", equalTo("invalidProperties"))
.body(ARGUMENTS + ".notCreated." + messageCreationId + ".description", equalTo("Invalid 'from' field. One accepted value is " + USERNAME.asString()));
}
@Test
public void setMessagesShouldSucceedWhenSendingMessageFromAnAliasOfTheConnectedUser() throws Exception {
dataProbe.addUserAliasMapping(Username.of(ALIAS_OF_USERNAME_MAIL).getLocalPart(), ALIAS_OF_USERNAME.getDomainPart().get().asString(), USERNAME.asString());
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + ALIAS_OF_USERNAME_MAIL + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"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.asString())
.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(ALIAS_OF_USERNAME_MAIL))
.body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.name", equalTo(ALIAS_OF_USERNAME_MAIL))
.body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.email", equalTo(ALIAS_OF_USERNAME_MAIL));
calmlyAwait
.pollDelay(Duration.ofMillis(500))
.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken));
}
@Test
public void setMessagesShouldSucceedWhenSendingMessageFromADomainAliasOfTheConnectedUser() throws Exception {
dataProbe.addDomain(DOMAIN_ALIAS);
dataProbe.addDomainAliasMapping(DOMAIN_ALIAS, DOMAIN);
String messageCreationId = "creationId1337";
String alias = USERNAME.withOtherDomain(Domain.of(DOMAIN_ALIAS)).asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + alias + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"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.asString())
.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(alias))
.body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.name", equalTo(alias))
.body(ARGUMENTS + ".created[\"" + messageCreationId + "\"].from.email", equalTo(alias));
calmlyAwait
.pollDelay(Duration.ofMillis(500))
.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken));
}
@Test
public void setMessagesShouldFailWhenSendingMessageFromAGroupAliasOfTheConnectedUser() throws Exception {
dataProbe.addGroupAliasMapping(GROUP_MAIL, USERNAME.asString());
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + GROUP_MAIL + "\"}," +
" \"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.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(ARGUMENTS + ".created", anEmptyMap())
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId));
String outboxId = getMailboxId(accessToken, Role.OUTBOX);
assertThat(hasNoMessageIn(bobAccessToken, outboxId)).isTrue();
}
@Test
public void setMessagesShouldNotCreateMessageInOutboxWhenSendingMessageWithAnotherFromAddressThanTheConnectedUser() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"wrongaddress@otherdomain.org\"}," +
" \"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.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200);
String outboxId = getMailboxId(accessToken, Role.OUTBOX);
assertThat(hasNoMessageIn(bobAccessToken, outboxId)).isTrue();
}
private boolean hasNoMessageIn(AccessToken accessToken, String mailboxId) {
try {
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + mailboxId + "\"]}}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messageList"))
.body(ARGUMENTS + ".messageIds", empty());
return true;
} catch (AssertionError e) {
return false;
}
}
@Test
public void setMessagesShouldSucceedWhenSendingMessageWithOnlyFromAddress() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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() {
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId));
}
@Test
public void setMessagesShouldNotRejectWhenSendingMessageHasMissingSubject() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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 = "creationId1337";
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.asString())
.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. One accepted value is username@domain.tld"))
.body(ARGUMENTS + ".created", aMapWithSize(0));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldDeliverMessageToRecipient() throws Exception {
// Sender
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
AccessToken recipientToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), password);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(recipientToken));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldTriggerMaxQuotaReachedWhenTryingToSendMessageAndQuotaReached() throws Exception {
QuotaProbe quotaProbe = jmapServer.getProbe(QuotaProbesImpl.class);
QuotaRoot inboxQuotaRoot = quotaProbe.getQuotaRoot(MailboxPath.inbox(USERNAME));
quotaProbe.setMaxStorage(inboxQuotaRoot, QuotaSizeLimit.size(100));
MessageAppender.fillMailbox(mailboxProbe, USERNAME.asString(), MailboxConstants.INBOX);
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), password);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
// When
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", aMapWithSize(1))
.body(ARGUMENTS + ".created", aMapWithSize(0))
.body(ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"].type", equalTo("maxQuotaReached"));
}
@Test
public void setMessagesShouldStripBccFromDeliveredEmail() throws Exception {
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String bccRecipient = BOB.asString();
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
AccessToken recipientToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), password);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," +
" \"bcc\": [{ \"name\": \"BOB\", \"email\": \"" + bccRecipient + "\"}]," +
" \"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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(recipientToken));
with()
.header("Authorization", recipientToken.asString())
.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
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," +
" \"bcc\": [{ \"name\": \"BOB\", \"email\": \"bob@" + 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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId));
with()
.header("Authorization", this.accessToken.asString())
.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
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
String bccAddress = BOB.asString();
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, bccAddress, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInRecipientsMailboxes(bobAccessToken));
with()
.header("Authorization", bobAccessToken.asString())
.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.asString())
.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 {
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
AccessToken recipientToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), password);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isHtmlMessageReceived(recipientToken));
}
@Test
public void setMessagesWhenSavingToDraftsShouldNotSendMessage() throws Exception {
String recipientAddress = "recipient" + "@" + DOMAIN;
String recipientPassword = "password";
dataProbe.addUser(recipientAddress, recipientPassword);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
AccessToken recipientToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), recipientPassword);
String senderDraftsMailboxId = getMailboxId(accessToken, Role.DRAFTS);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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.asString())
.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.asString();
MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, sender, "regular");
String recipientAddress = "recipient" + "@" + DOMAIN;
String recipientPassword = "password";
dataProbe.addUser(recipientAddress, recipientPassword);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\": [\"" + mailboxId.serialize() + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String notCreatedMessage = ARGUMENTS + ".notCreated[\"" + messageCreationId + "\"]";
given()
.header("Authorization", this.accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(notCreatedMessage + ".type", equalTo("invalidProperties"))
.body(notCreatedMessage + ".description", equalTo("Message creation is only supported in mailboxes with role Draft and Outbox"))
.body(ARGUMENTS + ".created", aMapWithSize(0));
}
private boolean isHtmlMessageReceived(AccessToken recipientToken) {
try {
with()
.header("Authorization", recipientToken.asString())
.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 {
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
AccessToken recipientToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(recipientAddress), password);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
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.asString())
.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.asString())
.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 mailboxIdsShouldReturnUpdatedWhenNoChange() throws Exception {
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String mailboxId = message.getMailboxId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + mailboxId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(messageToMoveId));
}
@Category(BasicFeature.class)
@Test
public void mailboxIdsShouldBeInDestinationWhenUsingForMove() throws Exception {
String newMailboxName = "heartFolder";
String heartFolderId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + heartFolderId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", contains(heartFolderId));
}
@Category(BasicFeature.class)
@Test
public void mailboxIdsShouldNotBeAnymoreInSourceWhenUsingForMove() throws Exception {
String newMailboxName = "heartFolder";
String heartFolderId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String inboxId = message.getMailboxId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + heartFolderId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", not(contains(inboxId)));
}
@Category(BasicFeature.class)
@Test
public void mailboxIdsShouldBeInBothMailboxWhenUsingForCopy() throws Exception {
String newMailboxName = "heartFolder";
String heartFolderId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String inboxId = message.getMailboxId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + heartFolderId + "\",\"" + inboxId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", containsInAnyOrder(heartFolderId, inboxId));
}
@Test
public void mailboxIdsShouldBeInOriginalMailboxWhenNoChange() throws Exception {
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String mailboxId = message.getMailboxId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + mailboxId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", contains(mailboxId));
}
@Test
public void mailboxIdsShouldReturnErrorWhenMovingToADeletedMailbox() throws Exception {
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "any");
String mailboxId = mailboxProbe.getMailboxId(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "any")
.serialize();
mailboxProbe.deleteMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "any");
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + mailboxId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(NOT_UPDATED, hasKey(messageToMoveId))
.body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].type", equalTo("anErrorOccurred"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Test
public void mailboxIdsShouldReturnErrorWhenSetToEmpty() throws Exception {
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": []" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(NOT_UPDATED, hasKey(messageToMoveId))
.body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].type", equalTo("invalidProperties"))
.body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].properties", hasSize(1))
.body(NOT_UPDATED + "[\"" + messageToMoveId + "\"].properties[0]", equalTo("mailboxIds"))
.body(ARGUMENTS + ".updated", hasSize(0));
}
@Test
public void updateShouldNotReturnErrorWithFlagsAndMailboxUpdate() throws Exception {
String newMailboxName = "heartFolder";
String heartFolderId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + heartFolderId + "\"]," +
" \"isUnread\": true" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.spec(getSetMessagesUpdateOKResponseAssertions(messageToMoveId));
}
@Test
public void updateShouldWorkWithFlagsAndMailboxUpdate() throws Exception {
String newMailboxName = "heartFolder";
String heartFolderId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + heartFolderId + "\"]," +
" \"isUnread\": true" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", contains(heartFolderId))
.body(firstMessage + ".isUnread", equalTo(true));
}
@Test
public void setMessagesShouldWorkForMoveToTrash() throws Exception {
String trashId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.TRASH).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + trashId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".updated[0]", equalTo(messageToMoveId))
.body(ARGUMENTS + ".updated", hasSize(1));
}
@Test
public void copyToTrashShouldWork() throws Exception {
String newMailboxName = "heartFolder";
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), newMailboxName);
String trashId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), DefaultMailboxes.TRASH).serialize();
ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), MailboxPath.inbox(USERNAME),
new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
String messageToMoveId = message.getMessageId().serialize();
String mailboxId = message.getMailboxId().serialize();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageToMoveId + "\" : {" +
" \"mailboxIds\": [\"" + trashId + "\",\"" + mailboxId + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
String firstMessage = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageToMoveId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".mailboxIds", containsInAnyOrder(trashId, mailboxId));
}
@Test
public void setMessagesShouldReturnAttachmentsNotFoundWhenBlobIdDoesntExist() {
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
.body(notCreatedPath + ".type", equalTo("invalidProperties"))
.body(notCreatedPath + ".properties", contains("attachments"))
.body(notCreatedPath + ".attachmentsNotFound", containsInAnyOrder("brokenId1", "brokenId2"))
.body(ARGUMENTS + ".created", aMapWithSize(0));
}
@Test
public void setMessagesShouldReturnAttachmentsWhenMessageHasAttachment() throws Exception {
String bytes1 = "attachment";
String bytes2 = "attachment2";
AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE, bytes1.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE, bytes2.getBytes(StandardCharsets.UTF_8));
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment1.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment1.getSize() + "}," +
" {\"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment2.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment2.getSize() + ", " +
" \"cid\" : \"123456789\", " +
" \"isInline\" : true }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]";
String json = given()
.header("Authorization", accessToken.asString())
.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))
.extract().asString();
assertThatJson(json)
.withOptions(new Options(Option.TREATING_NULL_AS_ABSENT, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_FIELDS))
.whenIgnoringPaths(createdPath + ".attachments[0].blobId", createdPath + ".attachments[1].blobId",
createdPath + ".attachments[0].inlinedWithCid", createdPath + ".attachments[1].inlinedWithCid")
.inPath(createdPath + ".attachments")
.isEqualTo("[{" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes1.length() + "," +
" \"cid\":null," +
" \"isInline\":false" +
"}, {" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes2.length() + "," +
" \"cid\":\"123456789\"," +
" \"isInline\":true" +
"}]");
}
@Test
public void setMessagesShouldPreserveCharsetOfAttachment() throws Exception {
String bytes1 = "attachment";
String bytes2 = "attachment2";
AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes1.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes2.getBytes(StandardCharsets.UTF_8));
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment1.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment1.getSize() + "}," +
" {\"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment2.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment2.getSize() + ", " +
" \"cid\" : \"123456789\", " +
" \"isInline\" : true }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]";
String json = given()
.header("Authorization", accessToken.asString())
.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))
.extract().asString();
assertThatJson(json)
.withOptions(new Options(Option.TREATING_NULL_AS_ABSENT, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_FIELDS))
.whenIgnoringPaths(createdPath + ".attachments[0].blobId", createdPath + ".attachments[1].blobId",
createdPath + ".attachments[0].inlinedWithCid", createdPath + ".attachments[1].inlinedWithCid")
.inPath(createdPath + ".attachments")
.isEqualTo("[{" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes1.length() + "," +
" \"cid\":null," +
" \"isInline\":false" +
"}, {" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes2.length() + "," +
" \"cid\":\"123456789\"," +
" \"isInline\":true" +
"}]");
}
@Test
public void setMessagesShouldReturnAttachmentsWithNonASCIINames() throws Exception {
String bytes1 = "attachment";
String bytes2 = "attachment2";
String bytes3 = "attachment3";
AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE, bytes1.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE, bytes2.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment3 = uploadAttachment(OCTET_CONTENT_TYPE, bytes3.getBytes(StandardCharsets.UTF_8));
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment1.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment1.getSize() + "," +
" \"name\" : \"ديناصور.png\", " +
" \"isInline\" : false" +
" }," +
" {" +
" \"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment2.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment2.getSize() + "," +
" \"name\" : \"эволюционировать.png\", " +
" \"isInline\" : false" +
" }," +
" {" +
" \"blobId\" : \"" + uploadedAttachment3.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment3.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment3.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.asString())
.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 {
String bytes1 = "attachment";
String bytes2 = "attachment2";
String bytes3 = "attachment3";
AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE, bytes1.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE, bytes2.getBytes(StandardCharsets.UTF_8));
AttachmentMetadata uploadedAttachment3 = uploadAttachment(OCTET_CONTENT_TYPE, bytes3.getBytes(StandardCharsets.UTF_8));
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment1.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment1.getSize() + "," +
" \"name\" : \"ديناصور.png\", " +
" \"isInline\" : false" +
" }," +
" {" +
" \"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment2.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment2.getSize() + "," +
" \"name\" : \"эволюционировать.png\", " +
" \"isInline\" : false" +
" }," +
" {" +
" \"blobId\" : \"" + uploadedAttachment3.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment3.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment3.getSize() + "," +
" \"name\" : \"进化还是不.png\"," +
" \"isInline\" : false" +
" }" +
" ]" +
" }" +
" }" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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 inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#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 AttachmentMetadata uploadAttachment(String contentType, byte[] content) {
JsonPath json = with()
.header("Authorization", accessToken.asString())
.contentType(contentType)
.body(new ByteArrayInputStream(content))
.post("/upload")
.then()
.extract()
.body()
.jsonPath();
return AttachmentMetadata.builder()
.messageId(new DefaultMessageId())
.attachmentId(StringBackedAttachmentId.from(json.getString("blobId")))
.size(json.getLong("size"))
.type(json.getString("type"))
.build();
}
private AttachmentMetadata uploadTextAttachment(String contentType, String content) {
JsonPath json = with()
.header("Authorization", accessToken.asString())
.contentType(contentType)
.body(content)
.post("/upload")
.then()
.extract()
.body()
.jsonPath();
return AttachmentMetadata.builder()
.messageId(new DefaultMessageId())
.attachmentId(StringBackedAttachmentId.from(json.getString("blobId")))
.size(json.getLong("size"))
.type(json.getString("type"))
.build();
}
@Test
public void attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesBinaryAttachment() throws Exception {
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};
AttachmentMetadata uploadedAttachment = uploadAttachment(OCTET_CONTENT_TYPE, rawBytes);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment.getSize() + ", " +
" \"cid\" : \"123456789\", " +
" \"isInline\" : true }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String blobId = given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".attachments", hasSize(1))
.body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE_UTF8))
.body(firstAttachment + ".size", equalTo(rawBytes.length))
.body(firstAttachment + ".cid", equalTo("123456789"))
.body(firstAttachment + ".isInline", equalTo(true))
.extract()
.jsonPath()
.getString(firstAttachment + ".blobId");
checkBlobContent(blobId, rawBytes);
}
@Category(BasicFeature.class)
@Test
public void attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesTextAttachment() throws Exception {
byte[] rawBytes = ByteStreams.toByteArray(new ZeroedInputStream(_1MB));
AttachmentMetadata uploadedAttachment = uploadAttachment(OCTET_CONTENT_TYPE, rawBytes);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment.getSize() + ", " +
" \"cid\" : \"123456789\", " +
" \"isInline\" : true }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String blobId = given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".attachments", hasSize(1))
.body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE_UTF8))
.body(firstAttachment + ".size", equalTo(rawBytes.length))
.body(firstAttachment + ".cid", equalTo("123456789"))
.body(firstAttachment + ".isInline", equalTo(true))
.extract()
.jsonPath()
.getString(firstAttachment + ".blobId");
checkBlobContent(blobId, rawBytes);
}
private boolean isAnyMessageFoundInInbox(AccessToken recipientToken) {
try {
String inboxId = getMailboxId(recipientToken, Role.INBOX);
with()
.header("Authorization", recipientToken.asString())
.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 {
String text = "<html>\n" +
" <body>attachment</body>\n" + // needed indentation, else restassured is adding some
"</html>";
String contentType = "text/html; charset=UTF-8";
AttachmentMetadata uploadedAttachment = uploadTextAttachment(contentType, text);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment.getSize() + ", " +
" \"isInline\" : false }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String blobId = given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#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 + ".type", equalTo("text/html; charset=UTF-8"))
.body(firstAttachment + ".size", equalTo(text.length()))
.extract()
.jsonPath()
.getString(firstAttachment + ".blobId");
checkBlobContent(blobId, text.getBytes(StandardCharsets.UTF_8));
}
@Test
public void attachmentsAndBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextBodyAndHtmlAttachment() throws Exception {
String text = "<html>\n" +
" <body>attachment</body>\n" + // needed indentation, else restassured is adding some
"</html>";
String contentType = "text/html; charset=UTF-8";
AttachmentMetadata uploadedAttachment = uploadTextAttachment(contentType, text);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment.getSize() + ", " +
" \"isInline\" : false }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String blobId = given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#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", is(nullValue()))
.body(firstMessage + ".attachments", hasSize(1))
.body(firstAttachment + ".type", equalTo("text/html; charset=UTF-8"))
.body(firstAttachment + ".size", equalTo((int) uploadedAttachment.getSize()))
.extract()
.jsonPath()
.getString(firstAttachment + ".blobId");
checkBlobContent(blobId, text.getBytes(StandardCharsets.UTF_8));
}
private void checkBlobContent(String blobId, byte[] rawBytes) {
byte[] attachmentBytes = with()
.header("Authorization", accessToken.asString())
.get("/download/" + blobId)
.then()
.extract()
.body()
.asByteArray();
assertThat(new ByteArrayInputStream(attachmentBytes))
.hasSameContentAs(new ByteArrayInputStream(rawBytes));
}
@Test
public void attachmentAndEmptyBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextAttachmentWithoutMailBody() throws Exception {
String text = "some text";
String contentType = "text/plain; charset=UTF-8";
AttachmentMetadata uploadedAttachment = uploadTextAttachment(contentType, text);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment.getSize() + ", " +
" \"isInline\" : false }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String blobId = given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".textBody", is(nullValue()))
.body(firstMessage + ".htmlBody", is(nullValue()))
.body(firstMessage + ".attachments", hasSize(1))
.body(firstAttachment + ".type", equalTo("text/plain; charset=UTF-8"))
.body(firstAttachment + ".size", equalTo((int) uploadedAttachment.getSize()))
.extract()
.jsonPath()
.getString(firstAttachment + ".blobId");
checkBlobContent(blobId, text.getBytes(StandardCharsets.UTF_8));
}
@Test
public void setMessagesShouldVerifyHeaderOfMessageInInbox() throws Exception {
String toUsername = "username1@" + DOMAIN;
String password = "password";
dataProbe.addUser(toUsername, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, toUsername, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + toUsername + "\"}]," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
accessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(jmapServer), Username.of(toUsername), password);
String inboxMailboxId = getMailboxId(accessToken, Role.INBOX);
calmlyAwait.atMost(60, TimeUnit.SECONDS).until(() -> messageInMailboxHasHeaders(inboxMailboxId, buildExpectedHeaders()));
}
@Test
public void setMessagesShouldVerifyHeaderOfMessageInSent() throws Exception {
String toUsername = "username1@" + DOMAIN;
String password = "password";
dataProbe.addUser(toUsername, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, toUsername, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + toUsername + "\"}]," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
calmlyAwait.atMost(60, TimeUnit.SECONDS).until(() -> messageInMailboxHasHeaders(sentMailboxId, buildExpectedHeaders()));
}
private ImmutableList<String> buildExpectedHeaders() {
return ImmutableList.<String>builder()
.add("Sender")
.add("Content-Transfer-Encoding")
.add("From")
.add("To")
.add("MIME-Version")
.add("Subject")
.add("Content-Type")
.add("Message-ID")
.add("Date")
.build();
}
private boolean messageInMailboxHasHeaders(String mailboxId, ImmutableList<String> expectedHeaders) {
try {
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", "
+ "{"
+ "\"fetchMessages\": true, "
+ "\"fetchMessageProperties\": [\"headers\"], "
+ "\"filter\":{\"inMailboxes\":[\"" + mailboxId + "\"]} "
+ "}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(ARGUMENTS + ".messageIds", hasSize(1))
.body(SECOND_NAME, equalTo("messages"))
.body(SECOND_ARGUMENTS + ".list[0]", hasEntry(equalTo("headers"), allHeadersMatcher(expectedHeaders)));
return true;
} catch (AssertionError e) {
e.printStackTrace();
return false;
}
}
private Matcher<Map<? extends String, ? extends String>> allHeadersMatcher(ImmutableList<String> expectedHeaders) {
return Matchers.allOf(expectedHeaders.stream()
.map((String header) -> hasEntry(equalTo(header), not(is(nullValue()))))
.collect(Collectors.toList()));
}
@Test
public void setMessagesShouldSetUserAddedHeaders() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"headers\": { \"X-MY-SPECIAL-HEADER\": \"first header value\", \"OTHER-HEADER\": \"other value\"}," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
// When
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".headers", Matchers.allOf(
hasEntry("X-MY-SPECIAL-HEADER", "first header value"),
hasEntry("OTHER-HEADER", "other value")));
}
@Test
public void setMessagesShouldSetUserAddedHeadersForReplyAndForwardWhenAskedTo() throws Exception {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"headers\": { \"In-Reply-To\": \"inreplyto value\", \"X-Forwarded-Message-Id\": \"forward value\"}," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.<String>path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".headers", Matchers.allOf(
hasEntry("In-Reply-To", "inreplyto value"),
hasEntry("X-Forwarded-Message-Id", "forward value")));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldUpdateIsAnsweredWhenInReplyToHeaderSentViaOutbox() throws Exception {
OriginalMessage firstMessage = receiveFirstMessage();
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"headers\": { \"In-Reply-To\": \"" + firstMessage.mimeMessageId + "\"}," +
" \"subject\": \"RE: Hi!\"," +
" \"textBody\": \"Fine, thank you!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(bobAccessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + firstMessage.jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".keywords.$Answered", equalTo(true))
.body(message + ".isAnswered", equalTo(true));
}
@Category(BasicFeature.class)
@Test
public void setMessagesShouldUpdateIsForwardedWhenXForwardedHeaderSentViaOutbox() throws Exception {
OriginalMessage firstMessage = receiveFirstMessage();
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"headers\": { \"X-Forwarded-Message-Id\": \"" + firstMessage.mimeMessageId + "\"}," +
" \"subject\": \"Fwd: Hi!\"," +
" \"textBody\": \"You talking to me?\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(bobAccessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + firstMessage.jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".keywords.$Forwarded", equalTo(true))
.body(message + ".isForwarded", equalTo(true));
}
@Test
public void setMessagesShouldUpdateIsAnsweredWhenInReplyToHeaderSentViaDraft() throws Exception {
OriginalMessage firstMessage = receiveFirstMessage();
String draftCreationId = "creationId1337";
String createDraft = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + draftCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"headers\": { \"In-Reply-To\": \"" + firstMessage.mimeMessageId + "\"}," +
" \"subject\": \"RE: Hi!\"," +
" \"textBody\": \"Fine, thank you!\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String draftId =
with()
.header("Authorization", accessToken.asString())
.body(createDraft)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id");
String moveDraftToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + draftId + "\" : {" +
" \"keywords\": {}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(moveDraftToOutBox)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(bobAccessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + firstMessage.jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".keywords.$Answered", equalTo(true))
.body(message + ".isAnswered", equalTo(true));
}
@Test
public void setMessagesShouldUpdateIsForwardedWhenXForwardedHeaderSentViaDraft() throws Exception {
OriginalMessage firstMessage = receiveFirstMessage();
String draftCreationId = "creationId1337";
String createDraft = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + draftCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}]," +
" \"headers\": { \"X-Forwarded-Message-Id\": \"" + firstMessage.mimeMessageId + "\"}," +
" \"subject\": \"Fwd: Hi!\"," +
" \"textBody\": \"You talking to me?\"," +
" \"keywords\": {\"$Draft\": true}," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String draftId =
with()
.header("Authorization", accessToken.asString())
.body(createDraft)
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".created[\"" + draftCreationId + "\"].id");
String moveDraftToOutBox = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + draftId + "\" : {" +
" \"keywords\": {}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(moveDraftToOutBox)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(bobAccessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + firstMessage.jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".keywords.$Forwarded", equalTo(true))
.body(message + ".isForwarded", equalTo(true));
}
private OriginalMessage receiveFirstMessage() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"subject\": \"Hi!\"," +
" \"textBody\": \"How are you?\"," +
" \"mailboxIds\": [\"" + getOutboxId(bobAccessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", bobAccessToken.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String jmapMessageId = with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.extract()
.<String>path(ARGUMENTS + ".messageIds[0]");
String mimeMessageId = with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.extract()
.<String>path(ARGUMENTS + ".list[0].headers['Message-ID']");
return new OriginalMessage(jmapMessageId, mimeMessageId);
}
private static class OriginalMessage {
final String jmapMessageId;
final String mimeMessageId;
OriginalMessage(String jmapMessageId, String mimeMessageId) {
this.jmapMessageId = jmapMessageId;
this.mimeMessageId = mimeMessageId;
}
}
@Test
public void setMessagesShouldSetUserAddedHeadersInSent() throws Exception {
String toUsername = "username1@" + DOMAIN;
String password = "password";
dataProbe.addUser(toUsername, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, toUsername, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"" + toUsername + "\"}]," +
" \"headers\": { \"X-MY-SPECIAL-HEADER\": \"first header value\", \"OTHER-HEADER\": \"other value\"}," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId));
String message = SECOND_ARGUMENTS + ".list[0]";
with()
.header("Authorization", this.accessToken.asString())
.body("[[\"getMessageList\", {\"fetchMessages\":true, \"fetchMessageProperties\": [\"headers\"], \"filter\":{\"inMailboxes\":[\"" + sentMailboxId + "\"]}}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(SECOND_NAME, equalTo("messages"))
.body(SECOND_ARGUMENTS + ".list", hasSize(1))
.body(message + ".headers", Matchers.allOf(
hasEntry("X-MY-SPECIAL-HEADER", "first header value"),
hasEntry("OTHER-HEADER", "other value")));
}
@Test
public void setMessagesShouldSetMultivaluedUserAddedHeaders() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"headers\": { \"X-MY-MULTIVALUATED-HEADER\": \"first value\nsecond value\"}," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
// When
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".headers", hasEntry("X-MY-MULTIVALUATED-HEADER", "first value\nsecond value"));
}
@Test
public void setMessagesShouldRenderCorrectlyInIMAPMultivaluedUserAddedHeaders() throws Exception {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"headers\": { \"X-MY-MULTIVALUATED-HEADER\": \"first value\nsecond value\"}," +
" \"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.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
try (TestIMAPClient testIMAPClient = new TestIMAPClient()) {
testIMAPClient.connect(LOCALHOST_IP, jmapServer.getProbe(ImapGuiceProbe.class).getImapPort())
.login(USERNAME, PASSWORD)
.select(MailboxConstants.INBOX);
assertThat(testIMAPClient.readFirstMessage())
.contains("X-MY-MULTIVALUATED-HEADER: first value")
.contains("X-MY-MULTIVALUATED-HEADER: second value");
}
}
@Test
public void setMessagesShouldFilterComputedHeadersFromUserAddedHeaders() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"headers\": { \"From\": \"hacker@example.com\", \"X-MY-SPECIAL-HEADER\": \"first header value\", \"OTHER-HEADER\": \"other value\"}," +
" \"subject\": \"Thank you for joining example.com!\"," +
" \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = with()
.header("Authorization", accessToken.asString())
.body(requestBody)
// When
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".headers", Matchers.allOf(
hasEntry("X-MY-SPECIAL-HEADER", "first header value"),
hasEntry("OTHER-HEADER", "other value"),
not(hasEntry("From", "hacker@example.com")),
hasEntry("From", "Me <" + USERNAME.asString() + ">")));
}
@Test
public void setMessagesShouldCreateMessageWhenSendingMessageWithNonIndexableAttachment() throws Exception {
byte[] bytes = ClassLoaderUtils.getSystemResourceAsByteArray("attachment/nonIndexableAttachment.html");
String contentType = "text/html";
AttachmentMetadata uploadedAttachment = uploadAttachment(contentType, bytes);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
String outboxId = getOutboxId(accessToken);
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
" \"subject\": \"Message with non indexable attachment\"," +
" \"textBody\": \"Test body\"," +
" \"mailboxIds\": [\"" + outboxId + "\"], " +
" \"attachments\": [" +
" {\"blobId\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"name\" : \"nonIndexableAttachment.html\", " +
" \"size\" : " + uploadedAttachment.getSize() + "}" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]";
String singleAttachment = createdPath + ".attachments[0]";
given()
.header("Authorization", accessToken.asString())
.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(1))
.body(singleAttachment + ".type", equalTo("text/html; charset=UTF-8; name=\"=?US-ASCII?Q?nonIndexableAttachment.html?=\""))
.body(singleAttachment + ".size", equalTo((int) uploadedAttachment.getSize()));
}
@Test
public void messageWithNonIndexableAttachmentShouldBeRetrievedWhenChainingSetMessagesAndGetMessages() throws Exception {
byte[] bytes = ClassLoaderUtils.getSystemResourceAsByteArray("attachment/nonIndexableAttachment.html");
String contentType = "text/html";
AttachmentMetadata uploadedAttachment = uploadAttachment(contentType, bytes);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
String outboxId = getOutboxId(accessToken);
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
" \"subject\": \"Message with non indexable attachment\"," +
" \"textBody\": \"Test body\"," +
" \"mailboxIds\": [\"" + outboxId + "\"], " +
" \"attachments\": [" +
" {\"blobId\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"name\" : \"nonIndexableAttachment.html\", " +
" \"size\" : " + uploadedAttachment.getSize() + "}" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = with()
.header("Authorization", accessToken.asString())
.body(requestBody)
// When
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".attachments", hasSize(1));
}
@Test
public void messageWithNonIndexableAttachmentShouldHaveItsEmailBodyIndexed() throws Exception {
byte[] bytes = ClassLoaderUtils.getSystemResourceAsByteArray("attachment/nonIndexableAttachment.html");
String contentType = "text/html";
AttachmentMetadata uploadedAttachment = uploadAttachment(contentType, bytes);
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
String outboxId = getOutboxId(accessToken);
String inboxId = getMailboxId(accessToken, Role.INBOX);
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
" \"subject\": \"Message with non indexable attachment\"," +
" \"textBody\": \"Test body\"," +
" \"mailboxIds\": [\"" + outboxId + "\"], " +
" \"attachments\": [" +
" {\"blobId\" : \"" + uploadedAttachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment.getType().asString() + "\", " +
" \"name\" : \"nonIndexableAttachment.html\", " +
" \"size\" : " + uploadedAttachment.getSize() + "}" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
with()
.header("Authorization", accessToken.asString())
.body(requestBody)
.post("/jmap");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{" +
" \"body\": \"Test body\", " +
" \"inMailboxes\":[\"" + inboxId + "\"]}}, " +
"\"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messageList"))
.body(ARGUMENTS + ".messageIds", hasSize(1));
}
@Test
public void setMessagesShouldReturnAttachmentsWhenMessageHasInlinedAttachmentButNoCid() throws Exception {
String bytes = "attachment";
AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes.getBytes(StandardCharsets.UTF_8));
String bytes2 = "attachment2";
AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes2.getBytes(StandardCharsets.UTF_8));
String messageCreationId = "creationId";
String fromAddress = USERNAME.asString();
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\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment1.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment1.getSize() + "}," +
" {\"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + uploadedAttachment2.getType().asString() + "\", " +
" \"size\" : " + uploadedAttachment2.getSize() + ", " +
" \"isInline\" : true }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]";
String json = given()
.header("Authorization", accessToken.asString())
.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))
.extract()
.asString();
assertThatJson(json)
.withOptions(new Options(Option.TREATING_NULL_AS_ABSENT, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_FIELDS))
.inPath(createdPath + ".attachments")
.isEqualTo("[{" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes2.length() + "," +
" \"isInline\":false" +
"}, {" +
" \"type\":\"application/octet-stream; charset=UTF-8\"," +
" \"size\":" + bytes.length() + "," +
" \"isInline\":false" + // See JAMES-2258 inline should be false in case of no Content-ID for inlined attachment
// Stored attachment will not be considered as having an inlined attachment.
"}]");
}
@Test
public void setMessageWithUpdateShouldBeOKWhenKeywordsWithCustomFlagArePassed() throws MailboxException {
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
ComposedMessageId message = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
String messageId = message.getMessageId().serialize();
given()
.header("Authorization", accessToken.asString())
.body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"keywords\": {\"$Seen\": true, \"$Unknown\": true} } } }, \"#0\"]]", messageId))
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.spec(getSetMessagesUpdateOKResponseAssertions(messageId));
}
@Test
public void setMessageWithCreationShouldBeOKWhenKeywordsWithCustomFlagArePassed() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Answered\": true, \"$Unknown\": true}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.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 setMessageWithCreationShouldThrowWhenKeywordsWithUnsupportedArePassed() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"keywords\": {\"$Answered\": true, \"$Deleted\": true}," +
" \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.log().ifValidationFails()
.statusCode(200)
.body(NAME, equalTo("error"))
.body(ARGUMENTS + ".type", equalTo("invalidArguments"))
.body(ARGUMENTS + ".description", containsString("Does not allow to update 'Deleted' or 'Recent' flag"));
}
@Test
public void textBodyOfMessageWithTextCalendarShouldBeConvertedToAttachment() throws Exception {
MimeMessage calendarMessage = MimeMessageUtil.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream("eml/calendar.eml"));
String fromAddress = USERNAME.asString();
Mail mail = FakeMail.builder()
.name("name")
.mimeMessage(calendarMessage)
.sender(fromAddress)
.recipient(fromAddress)
.build();
try (SMTPMessageSender messageSender = SMTPMessageSender.noAuthentication(LOCALHOST_IP, jmapServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue(), DOMAIN)) {
messageSender.authenticate(USERNAME.asString(), PASSWORD).sendMessage(mail);
}
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String message = ARGUMENTS + ".list[0]";
String firstAttachment = message + ".attachments[0]";
String inboxId = getMailboxId(accessToken, Role.INBOX);
String receivedMessageId =
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
given()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + receivedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(message + ".attachments", hasSize(1))
.body(firstAttachment + ".type", equalTo("text/calendar; method=REPLY; charset=UTF-8"))
.body(firstAttachment + ".blobId", not(is(nullValue())));
}
@Test
public void setMessagesShouldSetTheSeenKeywordOnMessageInSentMailbox() throws Exception {
// Sender
String sentMailboxId = getMailboxId(accessToken, Role.SENT);
// Recipient
String recipientAddress = "recipient" + "@" + DOMAIN;
String password = "password";
dataProbe.addUser(recipientAddress, password);
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, DefaultMailboxes.INBOX);
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"recipient\", \"email\": \"" + recipientAddress + "\"}]," +
" \"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.asString())
.body(requestBody)
// When
.when()
.post("/jmap");
// Then
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> messageHasBeenMovedToSentBox(sentMailboxId));
with()
.header("Authorization", this.accessToken.asString())
.body("[[\"getMessageList\", {\"fetchMessages\":true, \"fetchMessageProperties\": [\"keywords\"], \"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].keywords.$Seen", equalTo(true));
}
@Test
public void setMessagesShouldCreateMessageWithFlagsWhenFlagsAttributesAreGiven() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isUnread\": true," +
" \"isFlagged\": true," +
" \"isAnswered\": true," +
" \"isDraft\": true," +
" \"isForwarded\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isUnread", equalTo(true))
.body(ARGUMENTS + ".list[0].isFlagged", equalTo(true))
.body(ARGUMENTS + ".list[0].isAnswered", equalTo(true))
.body(ARGUMENTS + ".list[0].isDraft", equalTo(true))
.body(ARGUMENTS + ".list[0].isForwarded", equalTo(true));
}
@Test
public void setMessagesShouldUpdateFlagsWhenSomeAreAlreadySet() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isDraft\": true," +
" \"isForwarded\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
String updateRequestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"isUnread\": true," +
" \"isFlagged\": true," +
" \"isAnswered\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(updateRequestBody)
.when()
.post("/jmap");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isUnread", equalTo(true))
.body(ARGUMENTS + ".list[0].isFlagged", equalTo(true))
.body(ARGUMENTS + ".list[0].isAnswered", equalTo(true))
.body(ARGUMENTS + ".list[0].isDraft", equalTo(true))
.body(ARGUMENTS + ".list[0].isForwarded", equalTo(true));
}
@Test
public void setMessagesShouldRemoveFlagsWhenAskedFor() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isUnread\": true," +
" \"isFlagged\": true," +
" \"isAnswered\": true," +
" \"isDraft\": true," +
" \"isForwarded\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
String updateRequestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"isUnread\": false," +
" \"isFlagged\": false," +
" \"isAnswered\": false," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(updateRequestBody)
.when()
.post("/jmap");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isUnread", equalTo(false))
.body(ARGUMENTS + ".list[0].isFlagged", equalTo(false))
.body(ARGUMENTS + ".list[0].isAnswered", equalTo(false))
.body(ARGUMENTS + ".list[0].isDraft", equalTo(true))
.body(ARGUMENTS + ".list[0].isForwarded", equalTo(true));
}
@Test
public void setMessagesShouldNotReturnAnErrorWhenTryingToChangeDraftFlagAmongOthers() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isUnread\": true," +
" \"isFlagged\": true," +
" \"isAnswered\": true," +
" \"isDraft\": true," +
" \"isForwarded\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
String updateRequestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"isUnread\": false," +
" \"isFlagged\": false," +
" \"isAnswered\": false," +
" \"isDraft\": false," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(updateRequestBody)
.when()
.post("/jmap")
.then()
.statusCode(200)
.body(ARGUMENTS + ".updated", hasSize(1))
.body(ARGUMENTS + ".updated", contains(messageId));
}
@Test
public void setMessagesShouldModifyTheMessageWhenTryingToChangeDraftFlagAmongOthers() {
String messageCreationId = "creationId1337";
String fromAddress = USERNAME.asString();
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
" \"subject\": \"subject\"," +
" \"isUnread\": true," +
" \"isFlagged\": true," +
" \"isAnswered\": true," +
" \"isDraft\": true," +
" \"isForwarded\": true," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String messageId = given()
.header("Authorization", accessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".id");
String updateRequestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"update\": { \"" + messageId + "\" : {" +
" \"isUnread\": false," +
" \"isFlagged\": false," +
" \"isAnswered\": false," +
" \"isDraft\": false," +
" \"mailboxIds\": [\"" + getDraftId(accessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
given()
.header("Authorization", accessToken.asString())
.body(updateRequestBody)
.when()
.post("/jmap");
with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
.post("/jmap")
.then()
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(ARGUMENTS + ".list[0].isUnread", equalTo(false))
.body(ARGUMENTS + ".list[0].isFlagged", equalTo(false))
.body(ARGUMENTS + ".list[0].isAnswered", equalTo(false))
.body(ARGUMENTS + ".list[0].isDraft", equalTo(false))
.body(ARGUMENTS + ".list[0].isForwarded", equalTo(true));
}
@Test
public void mimeMessageIdShouldBePreservedWhenSending() {
String messageCreationId = "creationId1337";
String requestBody = "[" +
" [" +
" \"setMessages\"," +
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Bob\", \"email\": \"" + BOB.asString() + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME.asString() + "\"}]," +
" \"subject\": \"Hi!\"," +
" \"textBody\": \"How are you?\"," +
" \"mailboxIds\": [\"" + getOutboxId(bobAccessToken) + "\"]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";
String creationMimeMessageId = given()
.header("Authorization", bobAccessToken.asString())
.body(requestBody)
.when()
.post("/jmap")
.then()
.extract()
.body()
.path(ARGUMENTS + ".created." + messageCreationId + ".headers['Message-ID']");
calmlyAwait.atMost(30, TimeUnit.SECONDS).until(() -> isAnyMessageFoundInInbox(accessToken));
String jmapMessageId = with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessageList\", {}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".messageIds[0]");
String receivedMimeMessageId = with()
.header("Authorization", accessToken.asString())
.body("[[\"getMessages\", {\"ids\": [\"" + jmapMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.extract()
.path(ARGUMENTS + ".list[0].headers['Message-ID']");
assertThat(receivedMimeMessageId).isEqualTo(creationMimeMessageId);
}
}