DO NOT MERGE: More test for ImapStore/ImapFolder.

- Also, fixed a potential crash in getMessages().
It could happen when a client is gettign a message list while
another client is removing messages.

Backport of I04b1de6bc384cffb7a5286bcec0a349a3d62a623

Change-Id: I227ecbf5bd68c999ba0ab8cd50ef798ef4ef35e4
This commit is contained in:
Makoto Onuki 2010-05-18 13:41:14 -07:00
parent a599ee773d
commit cd0b60e97a
2 changed files with 315 additions and 22 deletions

View File

@ -107,7 +107,8 @@ public class ImapStore extends Store {
/**
* Charset used for converting folder names to and from UTF-7 as defined by RFC 3501.
*/
private Charset mModifiedUtf7Charset;
private static final Charset MODIFIED_UTF_7_CHARSET =
new CharsetProvider().charsetForName("X-RFC-3501");
/**
* Cache of ImapFolder objects. ImapFolders are attached to a given folder on the server
@ -179,8 +180,6 @@ public class ImapStore extends Store {
if ((uri.getPath() != null) && (uri.getPath().length() > 0)) {
mPathPrefix = uri.getPath().substring(1);
}
mModifiedUtf7Charset = new CharsetProvider().charsetForName("X-RFC-3501");
}
/**
@ -433,9 +432,9 @@ public class ImapStore extends Store {
mConnections.offer(connection);
}
private String encodeFolderName(String name) {
/* package */ static String encodeFolderName(String name) {
try {
ByteBuffer bb = mModifiedUtf7Charset.encode(name);
ByteBuffer bb = MODIFIED_UTF_7_CHARSET.encode(name);
byte[] b = new byte[bb.limit()];
bb.get(b);
return new String(b, "US-ASCII");
@ -449,14 +448,14 @@ public class ImapStore extends Store {
}
}
private String decodeFolderName(String name) {
/* package */ static String decodeFolderName(String name) {
/*
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
* decoder and return the Unicode String.
*/
try {
byte[] encoded = name.getBytes("US-ASCII");
CharBuffer cb = mModifiedUtf7Charset.decode(ByteBuffer.wrap(encoded));
CharBuffer cb = MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(encoded));
return cb.toString();
}
catch (UnsupportedEncodingException uee) {
@ -753,6 +752,8 @@ public class ImapStore extends Store {
listener.messageRetrieved(message);
}
}
} catch (ImapException e) {
return new Message[0]; // No messages found.
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}

View File

@ -52,11 +52,23 @@ import java.util.HashMap;
*
* To run these tests alone, use:
* $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
*
* TODO Check if callback is really called
*/
@SmallTest
public class ImapStoreUnitTests extends AndroidTestCase {
private final static String[] NO_REPLY = new String[0];
/**
* Default folder name. In order to test for encoding, we use a non-ascii name.
*/
private final static String FOLDER_NAME = "\u65E5";
/**
* Folder name encoded in UTF-7.
*/
private final static String FOLDER_ENCODED = "&ZeU-";
/* These values are provided by setUp() */
private ImapStore mStore = null;
private ImapStore.ImapFolder mFolder = null;
@ -74,7 +86,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
// These are needed so we can get at the inner classes
mStore = (ImapStore) ImapStore.newInstance("imap://user:password@server:999",
getContext(), null);
mFolder = (ImapStore.ImapFolder) mStore.getFolder("INBOX");
mFolder = (ImapStore.ImapFolder) mStore.getFolder(FOLDER_NAME);
}
/**
@ -358,6 +370,20 @@ public class ImapStoreUnitTests extends AndroidTestCase {
*/
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
String readWriteMode) {
expectLogin(mockTransport, imapIdResponse);
mockTransport.expect(
getNextTag(false) + " SELECT \"" + FOLDER_ENCODED + "\"", new String[] {
"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
"* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
"* 0 EXISTS",
"* 0 RECENT",
"* OK [UNSEEN 0]",
"* OK [UIDNEXT 1]",
getNextTag(true) + " OK [" + readWriteMode + "] " +
FOLDER_ENCODED + " selected. (Success)"});
}
private void expectLogin(MockTransport mockTransport, String[] imapIdResponse) {
// Fix the tag # of the ID response
String last = imapIdResponse[imapIdResponse.length-1];
last = "2 " + last;
@ -370,15 +396,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
mockTransport.expect("2 ID \\(.*\\)", imapIdResponse);
mockTransport.expect("3 LOGIN user \"password\"",
"3 OK user authenticated (Success)");
mockTransport.expect("4 SELECT \"INBOX\"", new String[] {
"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
"* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
"* 0 EXISTS",
"* 0 RECENT",
"* OK [UNSEEN 0]",
"* OK [UIDNEXT 1]",
"4 OK [" + readWriteMode + "] INBOX selected. (Success)"});
mNextTag = 5;
mNextTag = 4;
}
/**
@ -421,8 +439,10 @@ public class ImapStoreUnitTests extends AndroidTestCase {
public void testGetUnreadMessageCountWithQuotedString() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mock.expect(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
"* STATUS \"INBOX\" (UNSEEN 2)",
mock.expect(
getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
new String[] {
"* STATUS \"" + FOLDER_ENCODED + "\" (UNSEEN 2)",
getNextTag(true) + " OK STATUS completed"});
mFolder.open(OpenMode.READ_WRITE, null);
int unreadCount = mFolder.getUnreadMessageCount();
@ -435,9 +455,11 @@ public class ImapStoreUnitTests extends AndroidTestCase {
public void testGetUnreadMessageCountWithLiteralString() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mock.expect(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
mock.expect(
getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
new String[] {
"* STATUS {5}",
"INBOX (UNSEEN 10)",
FOLDER_ENCODED + " (UNSEEN 10)",
getNextTag(true) + " OK STATUS completed"});
mFolder.open(OpenMode.READ_WRITE, null);
int unreadCount = mFolder.getUnreadMessageCount();
@ -764,7 +786,8 @@ public class ImapStoreUnitTests extends AndroidTestCase {
// * 12345 EXISTS
// OK [APPENDUID 627684530 17] (Success)
mock.expect(getNextTag(false) + " APPEND \\\"INBOX\\\" \\(\\\\Seen\\) \\{166\\}",
mock.expect(getNextTag(false) +
" APPEND \\\"" + FOLDER_ENCODED + "\\\" \\(\\\\Seen\\) \\{166\\}",
new String[] {"+ go ahead"});
mock.expectLiterally("From: me@test.com", NO_REPLY);
@ -786,4 +809,273 @@ public class ImapStoreUnitTests extends AndroidTestCase {
assertEquals("13", message.getUid());
assertEquals(7, mFolder.getMessageCount());
}
public void testGetPersonalNamespaces() throws Exception {
MockTransport mock = openAndInjectMockTransport();
expectLogin(mock, new String[] {"* ID NIL", "OK"});
mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
new String[] {
"* LIST (\\HasNoChildren) \"/\" \"inbox\"",
"* LIST (\\hasnochildren) \"/\" \"Drafts\"",
"* LIST (\\noselect) \"/\" \"no select\"",
"* LIST (\\HasNoChildren) \"/\" \"&ZeVnLIqe-\"", // Japanese folder name
getNextTag(true) + " OK SUCCESS"
});
Folder[] folders = mStore.getPersonalNamespaces();
ArrayList<String> list = new ArrayList<String>();
for (Folder f : folders) {
list.add(f.getName());
}
MoreAsserts.assertEquals(
new String[] {"Drafts", "\u65E5\u672C\u8A9E", "INBOX"},
list.toArray(new String[0])
);
// TODO test with path prefix
}
public void testEncodeFolderName() {
assertEquals("", ImapStore.encodeFolderName(""));
assertEquals("a", ImapStore.encodeFolderName("a"));
assertEquals("XYZ", ImapStore.encodeFolderName("XYZ"));
assertEquals("&ZeVnLIqe-", ImapStore.encodeFolderName("\u65E5\u672C\u8A9E"));
assertEquals("!&ZeVnLIqe-!", ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!"));
}
public void testDecodeFolderName() {
assertEquals("", ImapStore.decodeFolderName(""));
assertEquals("a", ImapStore.decodeFolderName("a"));
assertEquals("XYZ", ImapStore.decodeFolderName("XYZ"));
assertEquals("\u65E5\u672C\u8A9E", ImapStore.decodeFolderName("&ZeVnLIqe-"));
assertEquals("!\u65E5\u672C\u8A9E!", ImapStore.decodeFolderName("!&ZeVnLIqe-!"));
}
public void testExists() throws Exception {
MockTransport mock = openAndInjectMockTransport();
expectLogin(mock, new String[] {"* ID NIL", "OK"});
// Folder exists
Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
mock.expect(getNextTag(false) + " STATUS \\\"&ZeVnLIqe-\\\" \\(UIDVALIDITY\\)",
new String[] {
"* STATUS \"&ZeVnLIqe-\" (MESSAGES 10)",
getNextTag(true) + " OK SUCCESS"
});
assertTrue(folder.exists());
// Connection verification
mock.expect(getNextTag(false) + " NOOP",
new String[] {
getNextTag(true) + " OK success"
});
// Doesn't exist
folder = mStore.getFolder("no such folder");
mock.expect(getNextTag(false) + " STATUS \\\"no such folder\\\" \\(UIDVALIDITY\\)",
new String[] {
getNextTag(true) + " NO No such folder!"
});
assertFalse(folder.exists());
}
public void testCreate() throws Exception {
MockTransport mock = openAndInjectMockTransport();
expectLogin(mock, new String[] {"* ID NIL", "OK"});
// Success
Folder folder = mStore.getFolder("\u65E5\u672C\u8A9E");
assertTrue(folder.canCreate(FolderType.HOLDS_MESSAGES));
mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
new String[] {
getNextTag(true) + " OK Success"
});
assertTrue(folder.create(FolderType.HOLDS_MESSAGES));
// Connection verification
mock.expect(getNextTag(false) + " NOOP",
new String[] {
getNextTag(true) + " OK success"
});
// Failure
mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
new String[] {
getNextTag(true) + " NO Can't create folder"
});
assertFalse(folder.create(FolderType.HOLDS_MESSAGES));
}
public void testCopy() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
Folder folderTo = mStore.getFolder("\u65E5\u672C\u8A9E");
Message[] messages = new Message[] {
mFolder.createMessage("11"),
mFolder.createMessage("12"),
};
mock.expect(getNextTag(false) + " UID COPY 11\\,12 \\\"&ZeVnLIqe-\\\"",
new String[] {
getNextTag(true) + " OK copy completed"
});
mFolder.copyMessages(messages, folderTo, null);
}
public void testGetUnreadMessageCount() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
mock.expect(getNextTag(false) + " STATUS \\\"" + FOLDER_ENCODED + "\\\" \\(UNSEEN\\)",
new String[] {
"* STATUS \"" + FOLDER_ENCODED + "\" (X 1 UNSEEN 123)",
getNextTag(true) + " OK copy completed"
});
assertEquals(123, mFolder.getUnreadMessageCount());
}
public void testExpunge() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
mock.expect(getNextTag(false) + " EXPUNGE",
new String[] {
getNextTag(true) + " OK success"
});
mFolder.expunge();
}
public void testSetFlags() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
Message[] messages = new Message[] {
mFolder.createMessage("11"),
mFolder.createMessage("12"),
};
// Set
mock.expect(
getNextTag(false) + " UID STORE 11\\,12 \\+FLAGS.SILENT \\(\\\\Flagged \\\\Seen\\)",
new String[] {
getNextTag(true) + " OK success"
});
mFolder.setFlags(messages, new Flag[] {Flag.FLAGGED, Flag.SEEN}, true);
// Clear
mock.expect(
getNextTag(false) + " UID STORE 11\\,12 \\-FLAGS.SILENT \\(\\\\Deleted\\)",
new String[] {
getNextTag(true) + " OK success"
});
mFolder.setFlags(messages, new Flag[] {Flag.DELETED}, false);
}
public void testGetMessage() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
// Found
mock.expect(
getNextTag(false) + " UID SEARCH UID 123",
new String[] {
"* SEARCH 123",
getNextTag(true) + " OK success"
});
assertEquals("123", mFolder.getMessage("123").getUid());
// Not found
mock.expect(
getNextTag(false) + " UID SEARCH UID 123",
new String[] {
getNextTag(true) + " NO not found"
});
assertNull(mFolder.getMessage("123"));
}
/** Test for getMessages(int, int, MessageRetrievalListener) */
public void testGetMessages1() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
// Found
mock.expect(
getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
new String[] {
"* SEARCH 3 4",
getNextTag(true) + " OK success"
});
checkMessageUids(new String[] {"3", "4"}, mFolder.getMessages(3, 5, null));
// Not found
mock.expect(
getNextTag(false) + " UID SEARCH 3:5 NOT DELETED",
new String[] {
getNextTag(true) + " NO not found"
});
checkMessageUids(new String[] {}, mFolder.getMessages(3, 5, null));
}
/**
* Test for getMessages(String[] uids, MessageRetrievalListener) where uids != null.
* (testGetMessages3() covers the case where uids == null.)
*/
public void testGetMessages2() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
// No command will be sent
checkMessageUids(new String[] {"3", "4", "5"},
mFolder.getMessages(new String[] {"3", "4", "5"}, null));
checkMessageUids(new String[] {},
mFolder.getMessages(new String[] {}, null));
}
/**
* Test for getMessages(MessageRetrievalListener), which is the same as
* getMessages(String[] uids, MessageRetrievalListener) where uids == null.
*/
public void testGetMessages3() throws Exception {
MockTransport mock = openAndInjectMockTransport();
setupOpenFolder(mock);
mFolder.open(OpenMode.READ_WRITE, null);
mock.expect(
getNextTag(false) + " UID SEARCH 1:\\* NOT DELETED",
new String[] {
"* SEARCH 3 4 5",
getNextTag(true) + " OK success"
});
checkMessageUids(new String[] {"3", "4", "5"},
mFolder.getMessages(null));
}
private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) {
ArrayList<String> list = new ArrayList<String>();
for (Message m : actualMessages) {
list.add(m.getUid());
}
MoreAsserts.assertEquals(expectedUids, list.toArray(new String[0]) );
}
}