Merge pull request #1553 from k9mail/message-builder-tests

MessageBuilder refactorings for tests
This commit is contained in:
cketti 2016-08-16 00:02:49 +02:00 committed by GitHub
commit 1cc7c499f9
29 changed files with 447 additions and 123 deletions

View file

@ -221,7 +221,7 @@ public class PgpMimeMessageTest {
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
MimeMessage message;
try {
message = new MimeMessage(messageInputStream, true);
message = MimeMessage.parseMimeMessage(messageInputStream, true);
} finally {
messageInputStream.close();
}

View file

@ -8,7 +8,6 @@ import java.io.InputStream;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MimeMessage;
@ -58,7 +57,7 @@ public class ReconstructMessageTest {
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
MimeMessage message;
try {
message = new MimeMessage(messageInputStream, true);
message = MimeMessage.parseMimeMessage(messageInputStream, true);
} finally {
messageInputStream.close();
}

View file

@ -0,0 +1,34 @@
package com.fsck.k9.mail;
import java.util.Locale;
import java.util.Random;
import android.support.annotation.VisibleForTesting;
public class BoundaryGenerator {
private static final BoundaryGenerator INSTANCE = new BoundaryGenerator(new Random());
private final Random random;
public static BoundaryGenerator getInstance() {
return INSTANCE;
}
@VisibleForTesting
BoundaryGenerator(Random random) {
this.random = random;
}
public String generateBoundary() {
StringBuilder sb = new StringBuilder();
sb.append("----");
for (int i = 0; i < 30; i++) {
sb.append(Integer.toString(random.nextInt(36), 36));
}
return sb.toString().toUpperCase(Locale.US);
}
}

View file

@ -39,7 +39,7 @@ public class BinaryTempFileMessageBody extends BinaryTempFileBody implements Com
* could be sent along without processing. But since we
* don't know, we recursively parse it.
*/
MimeMessage message = new MimeMessage(in, true);
MimeMessage message = MimeMessage.parseMimeMessage(in, true);
message.setUsing7bitTransport();
message.writeTo(out);
} else {

View file

@ -0,0 +1,50 @@
package com.fsck.k9.mail.internet;
import java.util.Locale;
import java.util.UUID;
import android.support.annotation.VisibleForTesting;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
public class MessageIdGenerator {
public static MessageIdGenerator getInstance() {
return new MessageIdGenerator();
}
@VisibleForTesting
MessageIdGenerator() {
}
public String generateMessageId(Message message) {
String hostname = null;
Address[] from = message.getFrom();
if (from != null && from.length >= 1) {
hostname = from[0].getHostname();
}
if (hostname == null) {
Address[] replyTo = message.getReplyTo();
if (replyTo != null && replyTo.length >= 1) {
hostname = replyTo[0].getHostname();
}
}
if (hostname == null) {
hostname = "email.android.com";
}
String uuid = generateUuid();
return "<" + uuid + "@" + hostname + ">";
}
@VisibleForTesting
protected String generateUuid() {
// We use upper case here to match Apple Mail Message-ID format (for privacy)
return UUID.randomUUID().toString().toUpperCase(Locale.US);
}
}

View file

@ -14,7 +14,6 @@ import java.util.LinkedList;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import android.support.annotation.NonNull;
@ -61,20 +60,14 @@ public class MimeMessage extends Message {
protected int mSize;
private String serverExtra;
public MimeMessage() {
public static MimeMessage parseMimeMessage(InputStream in, boolean recurse) throws IOException, MessagingException {
MimeMessage mimeMessage = new MimeMessage();
mimeMessage.parse(in, recurse);
return mimeMessage;
}
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
*
* @param in
* @param recurse A boolean indicating to recurse through all nested MimeMessage subparts.
* @throws IOException
* @throws MessagingException
*/
public MimeMessage(InputStream in, boolean recurse) throws IOException, MessagingException {
parse(in, recurse);
public MimeMessage() {
}
/**
@ -319,27 +312,6 @@ public class MimeMessage extends Message {
return mMessageId;
}
public void generateMessageId() throws MessagingException {
String hostname = null;
if (mFrom != null && mFrom.length >= 1) {
hostname = mFrom[0].getHostname();
}
if (hostname == null && mReplyTo != null && mReplyTo.length >= 1) {
hostname = mReplyTo[0].getHostname();
}
if (hostname == null) {
hostname = "email.android.com";
}
/* We use upper case here to match Apple Mail Message-ID format (for privacy) */
String messageId = "<" + UUID.randomUUID().toString().toUpperCase(Locale.US) + "@" + hostname + ">";
setMessageId(messageId);
}
public void setMessageId(String messageId) {
setHeader("Message-ID", messageId);
mMessageId = messageId;
@ -529,15 +501,11 @@ public class MimeMessage extends Message {
expect(Part.class);
Part e = (Part)stack.peek();
try {
String mimeType = bd.getMimeType();
String boundary = bd.getBoundary();
MimeMultipart multiPart = new MimeMultipart(mimeType, boundary);
e.setBody(multiPart);
stack.addFirst(multiPart);
} catch (MessagingException me) {
throw new MimeException(me.getMessage(), me);
}
String mimeType = bd.getMimeType();
String boundary = bd.getBoundary();
MimeMultipart multiPart = new MimeMultipart(mimeType, boundary);
e.setBody(multiPart);
stack.addFirst(multiPart);
}
@Override

View file

@ -1,26 +1,35 @@
package com.fsck.k9.mail.internet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import java.io.*;
import java.util.Locale;
import java.util.Random;
public class MimeMultipart extends Multipart {
private String mimeType;
private byte[] preamble;
private byte[] epilogue;
private final String boundary;
public MimeMultipart() throws MessagingException {
boundary = generateBoundary();
setSubType("mixed");
public static MimeMultipart newInstance() {
String boundary = BoundaryGenerator.getInstance().generateBoundary();
return new MimeMultipart(boundary);
}
public MimeMultipart(String mimeType, String boundary) throws MessagingException {
public MimeMultipart(String boundary) {
this("multipart/mixed", boundary);
}
public MimeMultipart(String mimeType, String boundary) {
if (mimeType == null) {
throw new IllegalArgumentException("mimeType can't be null");
}
@ -32,16 +41,6 @@ public class MimeMultipart extends Multipart {
this.boundary = boundary;
}
public static String generateBoundary() {
Random random = new Random();
StringBuilder sb = new StringBuilder();
sb.append("----");
for (int i = 0; i < 30; i++) {
sb.append(Integer.toString(random.nextInt(36), 36));
}
return sb.toString().toUpperCase(Locale.US);
}
@Override
public String getBoundary() {
return boundary;

View file

@ -986,7 +986,7 @@ class ImapFolder extends Folder<ImapMessage> {
/*
* This is a multipart/*
*/
MimeMultipart mp = new MimeMultipart();
MimeMultipart mp = MimeMultipart.newInstance();
for (int i = 0, count = bs.size(); i < count; i++) {
if (bs.get(i) instanceof ImapList) {
/*

View file

@ -23,7 +23,6 @@ class ImapMessage extends MimeMessage {
super.setFlag(flag, set);
}
@Override
public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);

View file

@ -1184,7 +1184,7 @@ public class Pop3Store extends RemoteStore {
}//Pop3Folder
static class Pop3Message extends MimeMessage {
public Pop3Message(String uid, Pop3Folder folder) {
Pop3Message(String uid, Pop3Folder folder) {
mUid = uid;
mFolder = folder;
mSize = -1;

View file

@ -21,6 +21,7 @@ import static com.fsck.k9.mail.helper.UrlEncodingHelper.encodeUtf8;
class WebDavMessage extends MimeMessage {
private String mUrl = "";
WebDavMessage(String uid, Folder folder) {
this.mUid = uid;
this.mFolder = folder;

View file

@ -0,0 +1,46 @@
package com.fsck.k9.mail;
import java.util.Random;
import org.junit.Test;
import org.mockito.stubbing.OngoingStubbing;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BoundaryGeneratorTest {
@Test
public void generateBoundary_allZeros() throws Exception {
Random random = createRandom(0);
BoundaryGenerator boundaryGenerator = new BoundaryGenerator(random);
String result = boundaryGenerator.generateBoundary();
assertEquals("----000000000000000000000000000000", result);
}
@Test
public void generateBoundary() throws Exception {
Random random = createRandom(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, 35);
BoundaryGenerator boundaryGenerator = new BoundaryGenerator(random);
String result = boundaryGenerator.generateBoundary();
assertEquals("----0123456789ABCDEFGHIJKLMNOPQRSZ", result);
}
private Random createRandom(int... values) {
Random random = mock(Random.class);
OngoingStubbing<Integer> ongoingStubbing = when(random.nextInt(36));
for (int value : values) {
ongoingStubbing = ongoingStubbing.thenReturn(value);
}
return random;
}
}

View file

@ -0,0 +1,60 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21)
public class MessageIdGeneratorTest {
private MessageIdGenerator messageIdGenerator;
@Before
public void setUp() throws Exception {
messageIdGenerator = new MessageIdGenerator() {
@Override
protected String generateUuid() {
return "00000000-0000-4000-0000-000000000000";
}
};
}
@Test
public void generateMessageId_withFromAndReplyToAddress() throws Exception {
Message message = new MimeMessage();
message.setFrom(new Address("alice@example.org"));
message.setReplyTo(Address.parse("bob@example.com"));
String result = messageIdGenerator.generateMessageId(message);
assertEquals("<00000000-0000-4000-0000-000000000000@example.org>", result);
}
@Test
public void generateMessageId_withReplyToAddress() throws Exception {
Message message = new MimeMessage();
message.setReplyTo(Address.parse("bob@example.com"));
String result = messageIdGenerator.generateMessageId(message);
assertEquals("<00000000-0000-4000-0000-000000000000@example.com>", result);
}
@Test
public void generateMessageId_withoutRelevantHeaders() throws Exception {
Message message = new MimeMessage();
String result = messageIdGenerator.generateMessageId(message);
assertEquals("<00000000-0000-4000-0000-000000000000@email.android.com>", result);
}
}

View file

@ -210,11 +210,11 @@ public class MimeMessageParseTest {
}
private static MimeMessage parseWithoutRecurse(InputStream data) throws Exception {
return new MimeMessage(data, false);
return MimeMessage.parseMimeMessage(data, false);
}
private static MimeMessage parseWithRecurse(InputStream data) throws Exception {
return new MimeMessage(data, true);
return MimeMessage.parseMimeMessage(data, true);
}
private static void checkAddresses(Address[] actual, String... expected) {

View file

@ -128,7 +128,7 @@ public class ReconstructMessageFromDatabaseTest extends ApplicationTestCase<K9>
protected MimeMessage parseMessage() throws IOException, MessagingException {
InputStream messageInputStream = new ByteArrayInputStream(MESSAGE_SOURCE.getBytes());
try {
return new MimeMessage(messageInputStream, true);
return MimeMessage.parseMimeMessage(messageInputStream, true);
} finally {
messageInputStream.close();
}

View file

@ -1,6 +1,7 @@
package com.fsck.k9.activity;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -716,14 +717,16 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return null;
}
PgpMessageBuilder pgpBuilder = new PgpMessageBuilder(getApplicationContext());
PgpMessageBuilder pgpBuilder = PgpMessageBuilder.newInstance();
recipientPresenter.builderSetProperties(pgpBuilder);
builder = pgpBuilder;
} else {
builder = new SimpleMessageBuilder(getApplicationContext());
builder = SimpleMessageBuilder.newInstance();
}
builder.setSubject(mSubjectView.getText().toString())
.setSentDate(new Date())
.setHideTimeZone(K9.hideTimeZone())
.setTo(recipientPresenter.getToAddresses())
.setCc(recipientPresenter.getCcAddresses())
.setBcc(recipientPresenter.getBccAddresses())

View file

@ -22,11 +22,9 @@ import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.ComposeCryptoStatusBuilder;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MailTo;
import com.fsck.k9.helper.ReplyToParser;
@ -35,7 +33,6 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.message.ComposePgpInlineDecider;
import com.fsck.k9.message.PgpMessageBuilder;
import com.fsck.k9.view.RecipientSelectView.Recipient;

View file

@ -21,7 +21,7 @@ class AttachmentMessageBodyUtil {
* could be sent along without processing. But since we
* don't know, we recursively parse it.
*/
MimeMessage message = new MimeMessage(in, true);
MimeMessage message = MimeMessage.parseMimeMessage(in, true);
message.setUsing7bitTransport();
message.writeTo(out);
} else {

View file

@ -36,6 +36,7 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@ -1416,7 +1417,7 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
cv.put("decoded_body_size", attachment.size);
if (MimeUtility.isMultipart(part.getMimeType())) {
cv.put("boundary", MimeMultipart.generateBoundary());
cv.put("boundary", BoundaryGenerator.getInstance().generateBoundary());
}
}

View file

@ -40,6 +40,7 @@ public class LocalMessage extends MimeMessage {
private String mimeType;
private PreviewType previewType;
private LocalMessage(LocalStore localStore) {
this.localStore = localStore;
}
@ -50,6 +51,7 @@ public class LocalMessage extends MimeMessage {
this.mFolder = folder;
}
void populateFromGetMessageCursor(Cursor cursor) throws MessagingException {
final String subject = cursor.getString(0);
this.setSubject(subject == null ? "" : subject);
@ -496,7 +498,7 @@ public class LocalMessage extends MimeMessage {
@Override
public LocalMessage clone() {
LocalMessage message = new LocalMessage(this.localStore);
LocalMessage message = new LocalMessage(localStore);
super.copy(message);
message.mId = mId;

View file

@ -144,17 +144,14 @@ public class MimePartStreamParser {
@Override
public void startMultipart(BodyDescriptor bd) throws MimeException {
Part part = (Part) stack.peek();
try {
String mimeType = bd.getMimeType();
String boundary = bd.getBoundary();
MimeMultipart multipart = new MimeMultipart(mimeType, boundary);
part.setBody(multipart);
String mimeType = bd.getMimeType();
String boundary = bd.getBoundary();
stack.push(multipart);
} catch (MessagingException e) {
throw new MimeException(e);
}
MimeMultipart multipart = new MimeMultipart(mimeType, boundary);
part.setBody(multipart);
stack.push(multipart);
}
@Override

View file

@ -20,9 +20,11 @@ import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
@ -38,8 +40,13 @@ import org.apache.james.mime4j.util.MimeUtil;
public abstract class MessageBuilder {
private final Context context;
private final MessageIdGenerator messageIdGenerator;
private final BoundaryGenerator boundaryGenerator;
private String subject;
private Date sentDate;
private boolean hideTimeZone;
private Address[] to;
private Address[] cc;
private Address[] bcc;
@ -64,8 +71,10 @@ public abstract class MessageBuilder {
private boolean isDraft;
private boolean isPgpInlineEnabled;
public MessageBuilder(Context context) {
protected MessageBuilder(Context context, MessageIdGenerator messageIdGenerator, BoundaryGenerator boundaryGenerator) {
this.context = context;
this.messageIdGenerator = messageIdGenerator;
this.boundaryGenerator = boundaryGenerator;
}
/**
@ -84,7 +93,7 @@ public abstract class MessageBuilder {
}
private void buildHeader(MimeMessage message) throws MessagingException {
message.addSentDate(new Date(), K9.hideTimeZone());
message.addSentDate(sentDate, hideTimeZone);
Address from = new Address(identity.getEmail(), identity.getName());
message.setFrom(from);
message.setRecipients(RecipientType.TO, to);
@ -115,12 +124,18 @@ public abstract class MessageBuilder {
message.setReferences(references);
}
message.generateMessageId();
String messageId = messageIdGenerator.generateMessageId(message);
message.setMessageId(messageId);
if (isDraft && isPgpInlineEnabled) {
message.setFlag(Flag.X_DRAFT_OPENPGP_INLINE, true);
}
}
protected MimeMultipart createMimeMultipart() {
String boundary = boundaryGenerator.generateBoundary();
return new MimeMultipart(boundary);
}
private void buildBody(MimeMessage message) throws MessagingException {
// Build the body.
@ -137,7 +152,7 @@ public abstract class MessageBuilder {
// HTML message (with alternative text part)
// This is the compiled MIME part for an HTML message.
MimeMultipart composedMimeMessage = new MimeMultipart();
MimeMultipart composedMimeMessage = createMimeMultipart();
composedMimeMessage.setSubType("alternative"); // Let the receiver select either the text or the HTML part.
composedMimeMessage.addBodyPart(new MimeBodyPart(body, "text/html"));
bodyPlain = buildText(isDraft, SimpleMessageFormat.TEXT);
@ -148,7 +163,7 @@ public abstract class MessageBuilder {
// whole message (mp here), of which one part is a MimeMultipart container
// (composedMimeMessage) with the user's composed messages, and subsequent parts for
// the attachments.
MimeMultipart mp = new MimeMultipart();
MimeMultipart mp = createMimeMultipart();
mp.addBodyPart(new MimeBodyPart(composedMimeMessage));
addAttachmentsToMessage(mp);
MimeMessageHelper.setBody(message, mp);
@ -159,7 +174,7 @@ public abstract class MessageBuilder {
} else if (messageFormat == SimpleMessageFormat.TEXT) {
// Text-only message.
if (hasAttachments) {
MimeMultipart mp = new MimeMultipart();
MimeMultipart mp = createMimeMultipart();
mp.addBodyPart(new MimeBodyPart(body, "text/plain"));
addAttachmentsToMessage(mp);
MimeMessageHelper.setBody(message, mp);
@ -331,6 +346,16 @@ public abstract class MessageBuilder {
return this;
}
public MessageBuilder setSentDate(Date sentDate) {
this.sentDate = sentDate;
return this;
}
public MessageBuilder setHideTimeZone(boolean hideTimeZone) {
this.hideTimeZone = hideTimeZone;
return this;
}
public MessageBuilder setTo(List<Address> to) {
this.to = to.toArray(new Address[to.size()]);
return this;

View file

@ -10,14 +10,18 @@ import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.fsck.k9.Globals;
import com.fsck.k9.K9;
import com.fsck.k9.activity.compose.ComposeCryptoStatus;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
@ -44,10 +48,20 @@ public class PgpMessageBuilder extends MessageBuilder {
private boolean opportunisticSkipEncryption;
private boolean opportunisticSecondPass;
public PgpMessageBuilder(Context context) {
super(context);
public static PgpMessageBuilder newInstance() {
Context context = Globals.getContext();
MessageIdGenerator messageIdGenerator = MessageIdGenerator.getInstance();
BoundaryGenerator boundaryGenerator = BoundaryGenerator.getInstance();
return new PgpMessageBuilder(context, messageIdGenerator, boundaryGenerator);
}
@VisibleForTesting
PgpMessageBuilder(Context context, MessageIdGenerator messageIdGenerator, BoundaryGenerator boundaryGenerator) {
super(context, messageIdGenerator, boundaryGenerator);
}
public void setOpenPgpApi(OpenPgpApi openPgpApi) {
this.openPgpApi = openPgpApi;
}
@ -269,7 +283,7 @@ public class PgpMessageBuilder extends MessageBuilder {
throw new MessagingException("didn't find expected RESULT_DETACHED_SIGNATURE in api call result");
}
MimeMultipart multipartSigned = new MimeMultipart();
MimeMultipart multipartSigned = createMimeMultipart();
multipartSigned.setSubType("signed");
multipartSigned.addBodyPart(signedBodyPart);
multipartSigned.addBodyPart(
@ -293,7 +307,7 @@ public class PgpMessageBuilder extends MessageBuilder {
throw new IllegalStateException("call to mimeBuildEncryptedMessage while encryption isn't enabled!");
}
MimeMultipart multipartEncrypted = new MimeMultipart();
MimeMultipart multipartEncrypted = createMimeMultipart();
multipartEncrypted.setSubType("encrypted");
multipartEncrypted.addBodyPart(new MimeBodyPart(new TextBody("Version: 1"), "application/pgp-encrypted"));
multipartEncrypted.addBodyPart(new MimeBodyPart(encryptedBodyPart, "application/octet-stream"));
@ -327,5 +341,4 @@ public class PgpMessageBuilder extends MessageBuilder {
public void setCryptoStatus(ComposeCryptoStatus cryptoStatus) {
this.cryptoStatus = cryptoStatus;
}
}

View file

@ -3,16 +3,27 @@ package com.fsck.k9.message;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.VisibleForTesting;
import com.fsck.k9.Globals;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeMessage;
public class SimpleMessageBuilder extends MessageBuilder {
public SimpleMessageBuilder(Context context) {
super(context);
public static SimpleMessageBuilder newInstance() {
Context context = Globals.getContext();
MessageIdGenerator messageIdGenerator = MessageIdGenerator.getInstance();
BoundaryGenerator boundaryGenerator = BoundaryGenerator.getInstance();
return new SimpleMessageBuilder(context, messageIdGenerator, boundaryGenerator);
}
@VisibleForTesting
SimpleMessageBuilder(Context context, MessageIdGenerator messageIdGenerator, BoundaryGenerator boundaryGenerator) {
super(context, messageIdGenerator, boundaryGenerator);
}
@Override

View file

@ -55,7 +55,7 @@ public class MessageDecryptVerifierTest {
@Test
public void findEncryptedPartsShouldReturnEmptyEncryptedPart() throws Exception {
MimeMessage message = new MimeMessage();
MimeMultipart multipartEncrypted = new MimeMultipart();
MimeMultipart multipartEncrypted = MimeMultipart.newInstance();
multipartEncrypted.setSubType("encrypted");
MimeMessageHelper.setBody(message, multipartEncrypted);
setContentTypeWithProtocol(message, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
@ -69,11 +69,11 @@ public class MessageDecryptVerifierTest {
@Test
public void findEncryptedPartsShouldReturnMultipleEncryptedParts() throws Exception {
MimeMessage message = new MimeMessage();
MimeMultipart multipartMixed = new MimeMultipart();
MimeMultipart multipartMixed = MimeMultipart.newInstance();
multipartMixed.setSubType("mixed");
MimeMessageHelper.setBody(message, multipartMixed);
MimeMultipart multipartEncryptedOne = new MimeMultipart();
MimeMultipart multipartEncryptedOne = MimeMultipart.newInstance();
multipartEncryptedOne.setSubType("encrypted");
MimeBodyPart bodyPartOne = new MimeBodyPart(multipartEncryptedOne);
setContentTypeWithProtocol(bodyPartOne, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
@ -82,7 +82,7 @@ public class MessageDecryptVerifierTest {
MimeBodyPart bodyPartTwo = new MimeBodyPart(null, "text/plain");
multipartMixed.addBodyPart(bodyPartTwo);
MimeMultipart multipartEncryptedThree = new MimeMultipart();
MimeMultipart multipartEncryptedThree = MimeMultipart.newInstance();
multipartEncryptedThree.setSubType("encrypted");
MimeBodyPart bodyPartThree = new MimeBodyPart(multipartEncryptedThree);
setContentTypeWithProtocol(bodyPartThree, MIME_TYPE_MULTIPART_ENCRYPTED, PROTCOL_PGP_ENCRYPTED);
@ -279,7 +279,7 @@ public class MessageDecryptVerifierTest {
}
MimeBodyPart multipart(String type, BodyPart... subParts) throws MessagingException {
MimeMultipart multiPart = new MimeMultipart();
MimeMultipart multiPart = MimeMultipart.newInstance();
multiPart.setSubType(type);
for (BodyPart subPart : subParts) {
multiPart.addBodyPart(subPart);

View file

@ -52,7 +52,7 @@ public class AttachmentResolverTest {
@Test
public void buildCidMap__onMultipartWithNoParts__shouldReturnEmptyMap() throws Exception {
Multipart multipartBody = new MimeMultipart();
Multipart multipartBody = MimeMultipart.newInstance();
Part multipartPart = new MimeBodyPart(multipartBody);
Map<String,Uri> result = AttachmentResolver.buildCidToAttachmentUriMap(attachmentInfoExtractor, multipartPart);
@ -62,7 +62,7 @@ public class AttachmentResolverTest {
@Test
public void buildCidMap__onMultipartWithEmptyBodyPart__shouldReturnEmptyMap() throws Exception {
Multipart multipartBody = new MimeMultipart();
Multipart multipartBody = MimeMultipart.newInstance();
BodyPart bodyPart = spy(new MimeBodyPart());
Part multipartPart = new MimeBodyPart(multipartBody);
multipartBody.addBodyPart(bodyPart);
@ -75,7 +75,7 @@ public class AttachmentResolverTest {
@Test
public void buildCidMap__onTwoPart__shouldReturnBothUris() throws Exception {
Multipart multipartBody = new MimeMultipart();
Multipart multipartBody = MimeMultipart.newInstance();
Part multipartPart = new MimeBodyPart(multipartBody);
BodyPart subPart1 = new MimeBodyPart();

View file

@ -151,7 +151,7 @@ public class MessageViewInfoExtractorTest {
TextBody body2 = new TextBody(bodyText2);
// Create multipart/mixed part
MimeMultipart multipart = new MimeMultipart();
MimeMultipart multipart = MimeMultipart.newInstance();
MimeBodyPart bodyPart1 = new MimeBodyPart(body1, "text/plain");
MimeBodyPart bodyPart2 = new MimeBodyPart(body2, "text/plain");
multipart.addBodyPart(bodyPart1);
@ -209,7 +209,7 @@ public class MessageViewInfoExtractorTest {
MimeMessageHelper.setBody(innerMessage, innerBody);
// Create multipart/mixed part
MimeMultipart multipart = new MimeMultipart();
MimeMultipart multipart = MimeMultipart.newInstance();
MimeBodyPart bodyPart1 = new MimeBodyPart(textBody, "text/plain");
MimeBodyPart bodyPart2 = new MimeBodyPart(innerMessage, "message/rfc822");
bodyPart2.setHeader("Content-Disposition", "inline; filename=\"message.eml\"");

View file

@ -2,20 +2,30 @@ package com.fsck.k9.message;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import android.app.Application;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.Identity;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.message.MessageBuilder.Callback;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@ -32,12 +42,14 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = "src/main/AndroidManifest.xml", sdk = 21)
public class MessageBuilderTest {
public static final String TEST_MESSAGE_TEXT = "message text";
public static final String TEST_MESSAGE_TEXT = "soviet message\r\ntext ☭";
public static final String TEST_ATTACHMENT_TEXT = "text data in attachment";
public static final String TEST_SUBJECT = "test_subject";
public static final Address TEST_IDENTITY_ADDRESS = new Address("test@example.org", "tester");
public static final Address[] TEST_TO = new Address[] {
@ -46,9 +58,72 @@ public class MessageBuilderTest {
public static final Address[] TEST_CC = new Address[] { new Address("cc@example.org", "cc recip") };
public static final Address[] TEST_BCC = new Address[] { new Address("bcc@example.org", "bcc recip") };
public static final String TEST_MESSAGE_ID = "<00000000-0000-007B-0000-0000000000EA@example.org>";
public static final String BOUNDARY_1 = "----boundary1";
public static final String BOUNDARY_2 = "----boundary2";
public static final String BOUNDARY_3 = "----boundary3";
public static final Date SENT_DATE = new Date(10000000000L);
public static final String MESSAGE_HEADERS =
"Date: Sun, 26 Apr 1970 18:46:40 +0100\r\n" +
"From: tester <test@example.org>\r\n" +
"To: recip 1 <to1@example.org>,recip 2 <to2@example.org>\r\n" +
"CC: cc recip <cc@example.org>\r\n" +
"BCC: bcc recip <bcc@example.org>\r\n" +
"Subject: test_subject\r\n" +
"User-Agent: K-9 Mail for Android\r\n" +
"In-Reply-To: inreplyto\r\n" +
"References: references\r\n" +
"Message-ID: " + TEST_MESSAGE_ID + "\r\n" +
"MIME-Version: 1.0\r\n";
public static final String MESSAGE_CONTENT =
"Content-Type: text/plain;\r\n" +
" charset=utf-8\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"soviet message\r\n" +
"text ☭";
public static final String MESSAGE_CONTENT_WITH_ATTACH =
"Content-Type: multipart/mixed; boundary=\"" + BOUNDARY_1 + "\"\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" charset=utf-8\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"soviet message\r\n" +
"text ☭\r\n" +
"--" + BOUNDARY_1 + "\r\n" +
"Content-Type: text/plain;\r\n" +
" name=\"attach.txt\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"Content-Disposition: attachment;\r\n" +
" filename=\"attach.txt\";\r\n" +
" size=23\r\n" +
"\r\n" +
"dGV4dCBkYXRhIGluIGF0dGFjaG1lbnQ=\r\n" +
"\r\n" +
"--" + BOUNDARY_1 + "--\r\n";
private Application context;
private MessageIdGenerator messageIdGenerator;
private BoundaryGenerator boundaryGenerator;
@Before
public void setUp() throws Exception {
messageIdGenerator = mock(MessageIdGenerator.class);
when(messageIdGenerator.generateMessageId(any(Message.class))).thenReturn(TEST_MESSAGE_ID);
boundaryGenerator = mock(BoundaryGenerator.class);
when(boundaryGenerator.generateBoundary()).thenReturn(BOUNDARY_1, BOUNDARY_2, BOUNDARY_3);
context = RuntimeEnvironment.application;
}
@Test
public void build__shouldSucceed() throws MessagingException {
public void build__shouldSucceed() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
@ -68,6 +143,43 @@ public class MessageBuilderTest {
assertArrayEquals(TEST_TO, mimeMessage.getRecipients(RecipientType.TO));
assertArrayEquals(TEST_CC, mimeMessage.getRecipients(RecipientType.CC));
assertArrayEquals(TEST_BCC, mimeMessage.getRecipients(RecipientType.BCC));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
mimeMessage.writeTo(bos);
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT, bos.toString());
}
@Test
public void build__withAttachment__shouldSucceed() throws Exception {
MessageBuilder messageBuilder = createSimpleMessageBuilder();
Attachment attachment = createAttachmentWithContent("text/plain", "attach.txt", TEST_ATTACHMENT_TEXT.getBytes());
messageBuilder.setAttachments(Collections.singletonList(attachment));
Callback mockCallback = mock(Callback.class);
messageBuilder.buildAsync(mockCallback);
ArgumentCaptor<MimeMessage> mimeMessageCaptor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(mimeMessageCaptor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage mimeMessage = mimeMessageCaptor.getValue();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
mimeMessage.writeTo(bos);
assertEquals(MESSAGE_HEADERS + MESSAGE_CONTENT_WITH_ATTACH, bos.toString());
}
private Attachment createAttachmentWithContent(String mimeType, String filename, byte[] content) throws Exception {
File tempFile = File.createTempFile("pre", ".tmp");
tempFile.deleteOnExit();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
fileOutputStream.write(content);
fileOutputStream.close();
return Attachment.createAttachment(null, 0, mimeType)
.deriveWithMetadataLoaded(mimeType, filename, content.length)
.deriveWithLoadComplete(tempFile.getAbsolutePath());
}
@Test
@ -93,7 +205,7 @@ public class MessageBuilderTest {
@Test
public void buildWithException__shouldThrow() throws MessagingException {
MessageBuilder messageBuilder = new SimpleMessageBuilder(RuntimeEnvironment.application) {
MessageBuilder messageBuilder = new SimpleMessageBuilder(context, messageIdGenerator, boundaryGenerator) {
@Override
protected void buildMessageInternal() {
queueMessageBuildException(new MessagingException("expected error"));
@ -109,7 +221,7 @@ public class MessageBuilderTest {
@Test
public void buildWithException__detachAndReattach__shouldThrow() throws MessagingException {
MessageBuilder messageBuilder = new SimpleMessageBuilder(RuntimeEnvironment.application) {
MessageBuilder messageBuilder = new SimpleMessageBuilder(context, messageIdGenerator, boundaryGenerator) {
@Override
protected void buildMessageInternal() {
queueMessageBuildException(new MessagingException("expected error"));
@ -133,8 +245,8 @@ public class MessageBuilderTest {
verifyNoMoreInteractions(mockCallback);
}
private static MessageBuilder createSimpleMessageBuilder() {
MessageBuilder b = new SimpleMessageBuilder(RuntimeEnvironment.application);
private MessageBuilder createSimpleMessageBuilder() {
MessageBuilder b = new SimpleMessageBuilder(context, messageIdGenerator, boundaryGenerator);
Identity identity = new Identity();
identity.setName(TEST_IDENTITY_ADDRESS.getPersonal());
@ -143,6 +255,8 @@ public class MessageBuilderTest {
identity.setSignatureUse(false);
b.setSubject(TEST_SUBJECT)
.setSentDate(SENT_DATE)
.setHideTimeZone(false)
.setTo(Arrays.asList(TEST_TO))
.setCc(Arrays.asList(TEST_CC))
.setBcc(Arrays.asList(TEST_BCC))

View file

@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import android.app.Activity;
import android.app.PendingIntent;
@ -23,8 +24,10 @@ import com.fsck.k9.activity.compose.RecipientPresenter.CryptoProviderState;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
@ -413,8 +416,9 @@ public class PgpMessageBuilderTest {
}
private static PgpMessageBuilder createDefaultPgpMessageBuilder(OpenPgpApi openPgpApi) {
PgpMessageBuilder b = new PgpMessageBuilder(RuntimeEnvironment.application);
b.setOpenPgpApi(openPgpApi);
PgpMessageBuilder builder = new PgpMessageBuilder(
RuntimeEnvironment.application, MessageIdGenerator.getInstance(), BoundaryGenerator.getInstance());
builder.setOpenPgpApi(openPgpApi);
Identity identity = new Identity();
identity.setName("tester");
@ -422,7 +426,9 @@ public class PgpMessageBuilderTest {
identity.setDescription("test identity");
identity.setSignatureUse(false);
b.setSubject("subject")
builder.setSubject("subject")
.setSentDate(new Date())
.setHideTimeZone(false)
.setTo(new ArrayList<Address>())
.setCc(new ArrayList<Address>())
.setBcc(new ArrayList<Address>())
@ -446,7 +452,7 @@ public class PgpMessageBuilderTest {
.setMessageReference(null)
.setDraft(false);
return b;
return builder;
}
private static void assertContentOfBodyPartEquals(String reason, BodyPart signatureBodyPart, byte[] expected) {
@ -500,5 +506,4 @@ public class PgpMessageBuilderTest {
}
}
}
}