Tests for IMAP FETCH

Adding regression test for the new IMAP parser.

Change-Id: Iac7f5c022e44ca5f06f735e145af15cc459eb61f
This commit is contained in:
Makoto Onuki 2010-05-17 15:44:36 -07:00
parent 1c4abf20d3
commit 7d3519151a
4 changed files with 259 additions and 27 deletions

View File

@ -23,9 +23,9 @@
-keep class * extends org.apache.james.mime4j.util.TempStorage
# Keep names that are used only by unit tests
# Any methods whose name is '*ForTest' are preserved.
-keep class ** {
*** *ForTest(...);
}
@ -172,3 +172,7 @@
-keep class org.apache.james.mime4j.message.Message {
*;
}
-keepclasseswithmembers class org.apache.commons.io.IOUtils {
*** toByteArray(...);
}

View File

@ -104,4 +104,8 @@ public class MimeMultipart extends Multipart {
public InputStream getInputStream() throws MessagingException {
return null;
}
public String getSubTypeForTest() {
return mSubType;
}
}

View File

@ -17,7 +17,9 @@
package com.android.email.mail.store;
import com.android.email.Email;
import com.android.email.Utility;
import com.android.email.mail.Address;
import com.android.email.mail.Body;
import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
@ -28,11 +30,15 @@ import com.android.email.mail.Transport;
import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeMultipart;
import com.android.email.mail.internet.MimeUtility;
import com.android.email.mail.internet.TextBody;
import com.android.email.mail.store.ImapStore.ImapMessage;
import com.android.email.mail.transport.MockTransport;
import org.apache.commons.io.IOUtils;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
@ -438,6 +444,224 @@ public class ImapStoreUnitTests extends AndroidTestCase {
assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
}
public void testFetchFlagEnvelope() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.ENVELOPE);
mock.expect(getNextTag(false) +
" UID FETCH 1 \\(UID FLAGS INTERNALDATE RFC822\\.SIZE BODY\\.PEEK\\[HEADER.FIELDS" +
" \\(date subject from content-type to cc message-id\\)\\]\\)",
new String[] {
"* 9 FETCH (UID 1 RFC822.SIZE 120626 INTERNALDATE \"17-May-2010 22:00:15 +0000\"" +
"FLAGS (\\Seen) BODY[HEADER.FIELDS (date subject from content-type to cc" +
" message-id)]" +
" {279}",
"From: Xxxxxx Yyyyy <userxx@android.com>",
"Date: Mon, 17 May 2010 14:59:52 -0700",
"Message-ID: <x0000000000000000000000000000000000000000000000y@android.com>",
"Subject: ssubject",
"To: android.test01@android.com",
"Content-Type: multipart/mixed; boundary=a00000000000000000000000000b",
"",
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("android.test01@android.com", message.getHeader("to")[0]);
assertEquals("Xxxxxx Yyyyy <userxx@android.com>", message.getHeader("from")[0]);
assertEquals("multipart/mixed; boundary=a00000000000000000000000000b",
message.getHeader("Content-Type")[0]);
assertTrue(message.isSet(Flag.SEEN));
}
public void testFetchBodyStructure() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.STRUCTURE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
new String[] {
"* 9 FETCH (UID 1 BODYSTRUCTURE ((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\")" +
" CID NIL \"7BIT\" 18 3 NIL NIL NIL)" +
"(\"IMAGE\" \"PNG\"" +
" (\"NAME\" \"device.png\") NIL NIL \"BASE64\" 117840 NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"device.png\")) NIL)" +
"(\"TEXT\" \"HTML\"" +
" () NIL NIL \"7BIT\" 100 NIL NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"attachment.html\" \"SIZE\" 555)) NIL)" +
"\"MIXED\" (\"BOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
// Check mime structure...
Body body = message.getBody();
assertTrue(body instanceof MimeMultipart);
MimeMultipart mimeMultipart = (MimeMultipart) body;
assertEquals(3, mimeMultipart.getCount());
assertEquals("mixed", mimeMultipart.getSubTypeForTest());
Part part0 = mimeMultipart.getBodyPart(0);
Part part1 = mimeMultipart.getBodyPart(1);
Part part2 = mimeMultipart.getBodyPart(2);
assertTrue(part0 instanceof MimeBodyPart);
assertTrue(part1 instanceof MimeBodyPart);
assertTrue(part2 instanceof MimeBodyPart);
MimeBodyPart mimePart0 = (MimeBodyPart) part0; // text/plain
MimeBodyPart mimePart1 = (MimeBodyPart) part1; // image/png
MimeBodyPart mimePart2 = (MimeBodyPart) part2; // text/html
MoreAsserts.assertEquals(
new String[] {"text/plain;\n CHARSET=\"ISO-8859-1\""},
part0.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"image/png;\n NAME=\"device.png\""},
part1.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"text/html"},
part2.getHeader("Content-Type")
);
MoreAsserts.assertEquals(
new String[] {"CID"},
part0.getHeader("Content-ID")
);
assertNull(
part1.getHeader("Content-ID")
);
assertNull(
part2.getHeader("Content-ID")
);
MoreAsserts.assertEquals(
new String[] {"7BIT"},
part0.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {"BASE64"},
part1.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {"7BIT"},
part2.getHeader("Content-Transfer-Encoding")
);
MoreAsserts.assertEquals(
new String[] {";\n size=18"}, // TODO Is that right?
part0.getHeader("Content-Disposition")
);
MoreAsserts.assertEquals(
new String[] {"attachment;\n filename=\"device.png\";\n size=117840"},
part1.getHeader("Content-Disposition")
);
MoreAsserts.assertEquals(
new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""},
part2.getHeader("Content-Disposition")
);
// TODO Test for quote: If a filename contains ", it should become %22,
// which isn't implemented.
}
public void testFetchBodySane() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY_SANE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]<0.51200>\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[] {23}",
"from: a@b.com", // 15 bytes
"", // 2
"test", // 6
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("a@b.com", message.getHeader("from")[0]);
}
public void testFetchBody() throws MessagingException {
final MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[\\]\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[] {23}",
"from: a@b.com", // 15 bytes
"", // 2
"test", // 6
")",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("a@b.com", message.getHeader("from")[0]);
}
public void testFetchAttachment() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
final Message message = mFolder.createMessage("1");
final FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.STRUCTURE);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)",
new String[] {
"* 9 FETCH (UID 1 BODYSTRUCTURE ((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\")" +
" CID NIL \"7BIT\" 18 3 NIL NIL NIL)" +
"(\"IMAGE\" \"PNG\"" +
" (\"NAME\" \"device.png\") NIL NIL \"BASE64\" 117840 NIL (\"ATTACHMENT\"" +
"(\"FILENAME\" \"device.png\")) NIL)" +
"\"MIXED\"))",
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
// Check mime structure, and get the second part.
Body body = message.getBody();
assertTrue(body instanceof MimeMultipart);
MimeMultipart mimeMultipart = (MimeMultipart) body;
assertEquals(2, mimeMultipart.getCount());
Part part1 = mimeMultipart.getBodyPart(1);
assertTrue(part1 instanceof MimeBodyPart);
MimeBodyPart mimePart1 = (MimeBodyPart) part1;
// Fetch the second part
fp.clear();
fp.add(mimePart1);
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[2\\]\\)",
new String[] {
"* 9 FETCH (UID 1 BODY[2] {4}",
"YWJj)", // abc in base64
getNextTag(true) + " OK SUCCESS"
});
mFolder.fetch(new Message[] { message }, fp, null);
assertEquals("abc",
Utility.fromUtf8(IOUtils.toByteArray(mimePart1.getBody().getInputStream())));
}
/**
* Test for proper operations on servers that return "NIL" for empty message bodies.
*/

View File

@ -39,12 +39,12 @@ public class MockTransport implements Transport {
// All flags defining debug or development code settings must be FALSE
// when code is checked in or released.
private static boolean DEBUG_LOG_STREAMS = true;
private static String LOG_TAG = "MockTransport";
private boolean mSslAllowed = false;
private boolean mTlsAllowed = false;
private boolean mOpen;
private boolean mInputOpen;
private int mConnectionSecurity;
@ -58,23 +58,23 @@ public class MockTransport implements Transport {
public static final int ACTION_INJECT_TEXT = 0;
public static final int ACTION_SERVER_CLOSE = 1;
public static final int ACTION_CLIENT_CLOSE = 2;
int mAction;
String mPattern;
String[] mResponses;
Transaction(String pattern, String[] responses) {
mAction = ACTION_INJECT_TEXT;
mPattern = pattern;
mResponses = responses;
}
Transaction(int otherType) {
mAction = otherType;
mPattern = null;
mResponses = null;
}
@Override
public String toString() {
switch (mAction) {
@ -89,7 +89,7 @@ public class MockTransport implements Transport {
}
}
}
private ArrayList<Transaction> mPairs = new ArrayList<Transaction>();
/**
@ -108,7 +108,7 @@ public class MockTransport implements Transport {
public void expect(String pattern, String response) {
expect(pattern, (response == null) ? null : new String[] { response });
}
/**
* Give the mock a pattern to wait for and a multi-line response to send back.
* @param pattern Java RegEx to wait for
@ -127,7 +127,7 @@ public class MockTransport implements Transport {
expect("^" + Pattern.quote(literal) + "$", responses);
}
/**
/**
* Tell the Mock Transport that we expect it to be closed. This will preserve
* the remaining entries in the expect() stream and allow us to "ride over" the close (which
* would normally reset everything).
@ -135,7 +135,7 @@ public class MockTransport implements Transport {
public void expectClose() {
mPairs.add(new Transaction(Transaction.ACTION_CLIENT_CLOSE));
}
private void sendResponse(String[] responses) {
for (String s : responses) {
mQueuedInput.add(s);
@ -145,7 +145,7 @@ public class MockTransport implements Transport {
public boolean canTrySslSecurity() {
return (mConnectionSecurity == CONNECTION_SECURITY_SSL);
}
public boolean canTryTlsSecurity() {
return (mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS);
}
@ -155,7 +155,7 @@ public class MockTransport implements Transport {
}
/**
* This simulates a condition where the server has closed its side, causing
* This simulates a condition where the server has closed its side, causing
* reads to fail.
*/
public void closeInputStream() {
@ -199,7 +199,7 @@ public class MockTransport implements Transport {
/**
* This normally serves as a pseudo-clone, for use by Imap. For the purposes of unit testing,
* until we need something more complex, we'll just return the actual MockTransport. Then we
* until we need something more complex, we'll just return the actual MockTransport. Then we
* don't have to worry about dealing with test metadata like the expects list or socket state.
*/
public Transport newInstanceWithConfiguration() {
@ -239,9 +239,9 @@ public class MockTransport implements Transport {
* from the mQueuedInput list, but if the list is empty, we also peek the expect list. This
* supports banners, multi-line responses, and any other cases where we respond without
* a specific expect pattern.
*
*
* If no response text is available, we assert (failing our test) as an underflow.
*
*
* Logs the read text if DEBUG_LOG_STREAMS is true.
*/
public String readLine() throws IOException {
@ -251,7 +251,7 @@ public class MockTransport implements Transport {
}
// if there's nothing to read, see if we can find a null-pattern response
if (0 == mQueuedInput.size()) {
Transaction pair = mPairs.get(0);
Transaction pair = mPairs.size() > 0 ? mPairs.get(0) : null;
if (pair != null && pair.mPattern == null) {
mPairs.remove(0);
sendResponse(pair.mResponses);
@ -278,7 +278,7 @@ public class MockTransport implements Transport {
public void setSoTimeout(int timeoutMilliseconds) /* throws SocketException */ {
}
public void setUri(URI uri, int defaultPort) {
SmtpSenderUnitTests.assertTrue("Don't call setUri on a mock transport", false);
}
@ -289,7 +289,7 @@ public class MockTransport implements Transport {
* If the string was expected, we push the corresponding responses into the mQueuedInput
* list, for subsequent calls to readLine(). If the string does not match, we assert
* the mismatch. If no string was expected, we assert it as an overflow.
*
*
* Logs the written text if DEBUG_LOG_STREAMS is true.
*/
public void writeLine(String s, String sensitiveReplacement) /* throws IOException */ {
@ -307,7 +307,7 @@ public class MockTransport implements Transport {
sendResponse(pair.mResponses);
}
}
/**
* This is an InputStream that satisfies the needs of getInputStream()
*/
@ -315,7 +315,7 @@ public class MockTransport implements Transport {
byte[] mNextLine = null;
int mNextIndex = 0;
/**
* Reads from the same input buffer as readLine()
*/
@ -324,11 +324,11 @@ public class MockTransport implements Transport {
if (!mInputOpen) {
throw new IOException();
}
if (mNextLine != null && mNextIndex < mNextLine.length) {
return mNextLine[mNextIndex++];
}
// previous line was exhausted so try to get another one
String next = readLine();
if (next == null) {
@ -340,12 +340,12 @@ public class MockTransport implements Transport {
if (mNextLine != null && mNextIndex < mNextLine.length) {
return mNextLine[mNextIndex++];
}
// no joy - throw an exception
throw new IOException();
// no joy - throw an exception
throw new IOException();
}
}
/**
* This is an OutputStream that satisfies the needs of getOutputStream()
*/