2009-03-04 03:32:22 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed 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 com.android.email.mail.store;
|
|
|
|
|
2010-05-17 20:13:56 +00:00
|
|
|
import com.android.email.Email;
|
2010-04-02 18:09:12 +00:00
|
|
|
import com.android.email.mail.Address;
|
2009-10-07 18:42:27 +00:00
|
|
|
import com.android.email.mail.FetchProfile;
|
2009-05-20 17:36:16 +00:00
|
|
|
import com.android.email.mail.Flag;
|
2009-04-15 19:58:19 +00:00
|
|
|
import com.android.email.mail.Folder;
|
2009-10-07 18:42:27 +00:00
|
|
|
import com.android.email.mail.Message;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.MessagingException;
|
2009-10-07 18:42:27 +00:00
|
|
|
import com.android.email.mail.Part;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.Transport;
|
2009-09-25 21:54:32 +00:00
|
|
|
import com.android.email.mail.Folder.FolderType;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.Folder.OpenMode;
|
2010-04-02 18:09:12 +00:00
|
|
|
import com.android.email.mail.Message.RecipientType;
|
2009-10-07 18:42:27 +00:00
|
|
|
import com.android.email.mail.internet.MimeUtility;
|
2010-04-02 18:09:12 +00:00
|
|
|
import com.android.email.mail.internet.TextBody;
|
|
|
|
import com.android.email.mail.store.ImapStore.ImapMessage;
|
2009-03-04 03:32:22 +00:00
|
|
|
import com.android.email.mail.transport.MockTransport;
|
|
|
|
|
|
|
|
import android.test.AndroidTestCase;
|
2010-02-01 23:53:46 +00:00
|
|
|
import android.test.MoreAsserts;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.test.suitebuilder.annotation.SmallTest;
|
2009-04-15 19:58:19 +00:00
|
|
|
|
2009-10-07 18:42:27 +00:00
|
|
|
import java.util.ArrayList;
|
2010-01-26 02:40:45 +00:00
|
|
|
import java.util.HashMap;
|
2009-03-04 03:32:22 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This is a series of unit tests for the ImapStore class. These tests must be locally
|
|
|
|
* complete - no server(s) required.
|
2009-10-07 18:42:27 +00:00
|
|
|
*
|
|
|
|
* To run these tests alone, use:
|
|
|
|
* $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
|
2009-03-04 03:32:22 +00:00
|
|
|
*/
|
|
|
|
@SmallTest
|
|
|
|
public class ImapStoreUnitTests extends AndroidTestCase {
|
2010-04-02 18:09:12 +00:00
|
|
|
private final static String[] NO_REPLY = new String[0];
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/* These values are provided by setUp() */
|
|
|
|
private ImapStore mStore = null;
|
|
|
|
private ImapStore.ImapFolder mFolder = null;
|
2010-02-26 06:48:11 +00:00
|
|
|
|
|
|
|
private int mNextTag;
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Setup code. We generate a lightweight ImapStore and ImapStore.ImapFolder.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
protected void setUp() throws Exception {
|
|
|
|
super.setUp();
|
2010-05-17 20:13:56 +00:00
|
|
|
Email.setTempDirectory(getContext());
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// These are needed so we can get at the inner classes
|
2009-03-27 00:05:25 +00:00
|
|
|
mStore = (ImapStore) ImapStore.newInstance("imap://user:password@server:999",
|
2009-04-14 03:07:56 +00:00
|
|
|
getContext(), null);
|
2009-03-04 03:32:22 +00:00
|
|
|
mFolder = (ImapStore.ImapFolder) mStore.getFolder("INBOX");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Confirms simple non-SSL non-TLS login
|
|
|
|
*/
|
|
|
|
public void testSimpleLogin() throws MessagingException {
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// try to open it
|
|
|
|
setupOpenFolder(mockTransport);
|
AI 146134: Add persistence API for remote stores & folders to use while
syncing. This provides a key-value store, per folder, that
can be used by network Stores to record persistent data such
as sync status, server keys, etc.
Note that, by definition, this only applies to remote folders
(e.g. IMAP, POP3). You'll see everywhere that LocalFolder is
passed null, and this is correct - LocalFolder *is* persistent
storage and does not need external help.
Note to reviewers: The core changes are Folder.java,
LocalStore.java, and LocalStoreUnitTests.java, so please give
them the bulk of your reviewer attention. The other files
are just following along with minor API changes. Of those,
the one worth close examination is MessagingController.java,
which is the only place in the system where remote Folders
are bonded with Local Folders and thus where this new API
comes into play.
Note to jham: Can you please take a look at
LocalStore.LocalFolder.setPersistentString() and recommend
better SQL foo than my primitive test-then-update-or-insert
logic, which is not transactional or threadsafe.
BUG=1786939
Automated import of CL 146134
2009-04-14 17:15:07 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
// TODO: inject specific facts in the initial folder SELECT and check them here
|
|
|
|
}
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
/**
|
|
|
|
* TODO: Test with SSL negotiation (faked)
|
|
|
|
* TODO: Test with SSL required but not supported
|
|
|
|
* TODO: Test with TLS negotiation (faked)
|
|
|
|
* TODO: Test with TLS required but not supported
|
|
|
|
* TODO: Test calling getMessageCount(), getMessages(), etc.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test the generation of the IMAP ID keys
|
2010-02-01 23:53:46 +00:00
|
|
|
*/
|
|
|
|
public void testImapIdBasic() {
|
|
|
|
// First test looks at operation of the outer API - we don't control any of the
|
|
|
|
// values; Just look for basic results.
|
|
|
|
|
|
|
|
// Strings we'll expect to find:
|
|
|
|
// name Android package name of the program
|
|
|
|
// os "android"
|
|
|
|
// os-version "version; build-id"
|
|
|
|
// vendor Vendor of the client/server
|
|
|
|
// x-android-device-model Model (Optional, so not tested here)
|
|
|
|
// x-android-net-operator Carrier (Unreliable, so not tested here)
|
|
|
|
// AGUID A device+account UID
|
2010-02-26 06:48:11 +00:00
|
|
|
String id = mStore.getImapId(getContext(), "user-name", "host-name", "IMAP4rev1 STARTTLS");
|
2010-02-01 23:53:46 +00:00
|
|
|
HashMap<String, String> map = tokenizeImapId(id);
|
|
|
|
assertEquals(getContext().getPackageName(), map.get("name"));
|
|
|
|
assertEquals("android", map.get("os"));
|
|
|
|
assertNotNull(map.get("os-version"));
|
|
|
|
assertNotNull(map.get("vendor"));
|
|
|
|
assertNotNull(map.get("AGUID"));
|
|
|
|
|
|
|
|
// Next, use the inner API to confirm operation of a couple of
|
|
|
|
// variants for release and non-release devices.
|
|
|
|
|
|
|
|
// simple API check - non-REL codename, non-empty version
|
|
|
|
id = mStore.makeCommonImapId("packageName", "version", "codeName",
|
|
|
|
"model", "id", "vendor", "network-operator");
|
|
|
|
map = tokenizeImapId(id);
|
|
|
|
assertEquals("packageName", map.get("name"));
|
|
|
|
assertEquals("android", map.get("os"));
|
|
|
|
assertEquals("version; id", map.get("os-version"));
|
|
|
|
assertEquals("vendor", map.get("vendor"));
|
|
|
|
assertEquals(null, map.get("x-android-device-model"));
|
|
|
|
assertEquals("network-operator", map.get("x-android-mobile-net-operator"));
|
|
|
|
assertEquals(null, map.get("AGUID"));
|
|
|
|
|
|
|
|
// simple API check - codename is REL, so use model name.
|
|
|
|
// also test empty version => 1.0 and empty network operator
|
|
|
|
id = mStore.makeCommonImapId("packageName", "", "REL",
|
|
|
|
"model", "id", "vendor", "");
|
|
|
|
map = tokenizeImapId(id);
|
|
|
|
assertEquals("packageName", map.get("name"));
|
|
|
|
assertEquals("android", map.get("os"));
|
|
|
|
assertEquals("1.0; id", map.get("os-version"));
|
|
|
|
assertEquals("vendor", map.get("vendor"));
|
|
|
|
assertEquals("model", map.get("x-android-device-model"));
|
|
|
|
assertEquals(null, map.get("x-android-mobile-net-operator"));
|
|
|
|
assertEquals(null, map.get("AGUID"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test of the internal generator for IMAP ID strings, specifically looking for proper
|
|
|
|
* filtering of illegal values. This is required because we cannot necessarily trust
|
|
|
|
* the external sources of some of this data (e.g. release labels).
|
2010-01-26 02:40:45 +00:00
|
|
|
*
|
2010-02-01 23:53:46 +00:00
|
|
|
* The (somewhat arbitrary) legal values are: a-z A-Z 0-9 - _ + = ; : . , / <space>
|
|
|
|
* The most important goal of the filters is to keep out control chars, (, ), and "
|
|
|
|
*/
|
|
|
|
public void testImapIdFiltering() {
|
|
|
|
String id = mStore.makeCommonImapId("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
|
|
"0123456789", "codeName",
|
|
|
|
"model", "-_+=;:.,// ",
|
|
|
|
"v(e)n\"d\ro\nr", // look for bad chars stripped out, leaving OK chars
|
|
|
|
"()\""); // look for bad chars stripped out, leaving nothing
|
|
|
|
HashMap<String, String> map = tokenizeImapId(id);
|
|
|
|
|
|
|
|
assertEquals("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", map.get("name"));
|
|
|
|
assertEquals("0123456789; -_+=;:.,// ", map.get("os-version"));
|
|
|
|
assertEquals("vendor", map.get("vendor"));
|
|
|
|
assertNull(map.get("x-android-mobile-net-operator"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that IMAP ID uid's are per-username
|
2010-01-26 02:40:45 +00:00
|
|
|
*/
|
2010-02-01 23:53:46 +00:00
|
|
|
public void testImapIdDeviceId() throws MessagingException {
|
|
|
|
ImapStore store1a = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
|
|
|
|
getContext(), null);
|
|
|
|
ImapStore store1b = (ImapStore) ImapStore.newInstance("imap://user1:password@server:999",
|
|
|
|
getContext(), null);
|
|
|
|
ImapStore store2 = (ImapStore) ImapStore.newInstance("imap://user2:password@server:999",
|
|
|
|
getContext(), null);
|
|
|
|
|
2010-02-26 06:48:11 +00:00
|
|
|
String id1a = mStore.getImapId(getContext(), "user1", "host-name", "IMAP4rev1");
|
|
|
|
String id1b = mStore.getImapId(getContext(), "user1", "host-name", "IMAP4rev1");
|
|
|
|
String id2 = mStore.getImapId(getContext(), "user2", "host-name", "IMAP4rev1");
|
2010-02-01 23:53:46 +00:00
|
|
|
|
|
|
|
String uid1a = tokenizeImapId(id1a).get("AGUID");
|
|
|
|
String uid1b = tokenizeImapId(id1b).get("AGUID");
|
|
|
|
String uid2 = tokenizeImapId(id2).get("AGUID");
|
|
|
|
|
|
|
|
assertEquals(uid1a, uid1b);
|
|
|
|
MoreAsserts.assertNotEqual(uid1a, uid2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to break an IMAP ID string into keys & values
|
|
|
|
* @param id the IMAP Id string (the part inside the parens)
|
|
|
|
* @return a map of key/value pairs
|
|
|
|
*/
|
|
|
|
private HashMap<String, String> tokenizeImapId(String id) {
|
2010-01-26 02:40:45 +00:00
|
|
|
// Instead of a true tokenizer, we'll use double-quote as the split.
|
2010-05-17 20:13:56 +00:00
|
|
|
// We can's use " " because there may be spaces inside the values.
|
2010-01-26 02:40:45 +00:00
|
|
|
String[] elements = id.split("\"");
|
|
|
|
HashMap<String, String> map = new HashMap<String, String>();
|
|
|
|
for (int i = 0; i < elements.length; ) {
|
|
|
|
// Because we split at quotes, we expect to find:
|
2010-02-01 23:53:46 +00:00
|
|
|
// [i] = null or one or more spaces
|
2010-01-26 02:40:45 +00:00
|
|
|
// [i+1] = key
|
|
|
|
// [i+2] = one or more spaces
|
|
|
|
// [i+3] = value
|
|
|
|
map.put(elements[i+1], elements[i+3]);
|
|
|
|
i += 4;
|
|
|
|
}
|
2010-02-01 23:53:46 +00:00
|
|
|
return map;
|
2010-01-26 02:40:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test non-NIL server response to IMAP ID. We should simply ignore it.
|
|
|
|
*/
|
|
|
|
public void testServerId() throws MessagingException {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
// try to open it
|
|
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
|
|
"* ID (\"name\" \"Cyrus\" \"version\" \"1.5\"" +
|
|
|
|
" \"os\" \"sunos\" \"os-version\" \"5.5\"" +
|
|
|
|
" \"support-url\" \"mailto:cyrus-bugs+@andrew.cmu.edu\")",
|
2010-03-18 17:11:08 +00:00
|
|
|
"OK"}, "READ-WRITE");
|
2010-01-26 02:40:45 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test OK response to IMAP ID with crummy text afterwards too.
|
|
|
|
*/
|
|
|
|
public void testImapIdOkParsing() throws MessagingException {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
// try to open it
|
|
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
|
|
"* ID NIL",
|
2010-03-18 17:11:08 +00:00
|
|
|
"OK [ID] bad-char-%"}, "READ-WRITE");
|
2010-01-26 02:40:45 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
}
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
/**
|
|
|
|
* Test BAD response to IMAP ID - also with bad parser chars
|
|
|
|
*/
|
|
|
|
public void testImapIdBad() throws MessagingException {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
// try to open it
|
|
|
|
setupOpenFolder(mockTransport, new String[] {
|
2010-03-18 17:11:08 +00:00
|
|
|
"BAD unknown command bad-char-%"}, "READ-WRITE");
|
2010-01-26 02:40:45 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
}
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* TODO: Test the operation of checkSettings()
|
|
|
|
* TODO: Test small Store & Folder functions that manage folders & namespace
|
2010-05-17 20:13:56 +00:00
|
|
|
*/
|
2009-05-20 17:36:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Test small Folder functions that don't really do anything in Imap
|
|
|
|
* TODO: Test all of the small Folder functions.
|
|
|
|
*/
|
|
|
|
public void testSmallFolderFunctions() throws MessagingException {
|
2009-08-27 06:12:02 +00:00
|
|
|
// getPermanentFlags() returns { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }
|
2009-05-20 17:36:16 +00:00
|
|
|
Flag[] flags = mFolder.getPermanentFlags();
|
2009-08-27 06:12:02 +00:00
|
|
|
assertEquals(3, flags.length);
|
2009-05-20 17:36:16 +00:00
|
|
|
// TODO: Write flags into hashset and compare them to a hashset and compare them
|
|
|
|
assertEquals(Flag.DELETED, flags[0]);
|
|
|
|
assertEquals(Flag.SEEN, flags[1]);
|
2009-08-27 06:12:02 +00:00
|
|
|
assertEquals(Flag.FLAGGED, flags[2]);
|
2009-09-25 21:54:32 +00:00
|
|
|
|
|
|
|
// canCreate() returns true
|
|
|
|
assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
|
|
|
|
assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
|
2009-05-20 17:36:16 +00:00
|
|
|
}
|
|
|
|
|
2009-04-15 19:58:19 +00:00
|
|
|
/**
|
|
|
|
* Lightweight test to confirm that IMAP hasn't implemented any folder roles yet.
|
2010-05-17 20:13:56 +00:00
|
|
|
*
|
2009-04-15 19:58:19 +00:00
|
|
|
* TODO: Test this with multiple folders provided by mock server
|
2009-05-01 23:36:34 +00:00
|
|
|
* TODO: Implement XLIST and then support this
|
2009-04-15 19:58:19 +00:00
|
|
|
*/
|
|
|
|
public void testNoFolderRolesYet() {
|
2010-05-17 20:13:56 +00:00
|
|
|
assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole());
|
2009-04-15 19:58:19 +00:00
|
|
|
}
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-05-01 23:36:34 +00:00
|
|
|
/**
|
|
|
|
* Lightweight test to confirm that IMAP isn't requesting structure prefetch.
|
|
|
|
*/
|
|
|
|
public void testNoStructurePrefetch() {
|
2010-05-17 20:13:56 +00:00
|
|
|
assertFalse(mStore.requireStructurePrefetch());
|
2009-05-01 23:36:34 +00:00
|
|
|
}
|
2010-05-17 20:13:56 +00:00
|
|
|
|
2009-05-01 23:36:34 +00:00
|
|
|
/**
|
|
|
|
* Lightweight test to confirm that IMAP is requesting sent-message-upload.
|
|
|
|
* TODO: Implement Gmail-specific cases and handle this server-side
|
|
|
|
*/
|
|
|
|
public void testSentUploadRequested() {
|
2010-05-17 20:13:56 +00:00
|
|
|
assertTrue(mStore.requireCopyMessageToSentFolder());
|
2009-05-01 23:36:34 +00:00
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* TODO: Test the process of opening and indexing a mailbox with one unread message in it.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2010-05-17 20:13:56 +00:00
|
|
|
* TODO: Test the scenario where the transport is "open" but not really (e.g. server closed).
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Set up a basic MockTransport. open it, and inject it into mStore
|
|
|
|
*/
|
|
|
|
private MockTransport openAndInjectMockTransport() {
|
|
|
|
// Create mock transport and inject it into the ImapStore that's already set up
|
|
|
|
MockTransport mockTransport = new MockTransport();
|
2009-09-29 22:28:43 +00:00
|
|
|
mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
|
2010-02-26 06:48:11 +00:00
|
|
|
mockTransport.setMockHost("mock.server.com");
|
2009-03-04 03:32:22 +00:00
|
|
|
mStore.setTransport(mockTransport);
|
|
|
|
return mockTransport;
|
|
|
|
}
|
2010-03-18 17:11:08 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
/**
|
|
|
|
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
|
2010-03-18 17:11:08 +00:00
|
|
|
*
|
2009-03-04 03:32:22 +00:00
|
|
|
* @param mockTransport the mock transport we're using
|
|
|
|
*/
|
|
|
|
private void setupOpenFolder(MockTransport mockTransport) {
|
2010-03-18 17:11:08 +00:00
|
|
|
setupOpenFolder(mockTransport, "READ-WRITE");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
|
|
|
|
*
|
|
|
|
* @param mockTransport the mock transport we're using
|
|
|
|
*/
|
|
|
|
private void setupOpenFolder(MockTransport mockTransport, String readWriteMode) {
|
2010-01-26 02:40:45 +00:00
|
|
|
setupOpenFolder(mockTransport, new String[] {
|
2010-03-18 17:11:08 +00:00
|
|
|
"* ID NIL", "OK"}, readWriteMode);
|
2010-01-26 02:40:45 +00:00
|
|
|
}
|
2010-03-18 17:11:08 +00:00
|
|
|
|
2010-01-26 02:40:45 +00:00
|
|
|
/**
|
|
|
|
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
|
|
|
|
* Also allows setting a custom IMAP ID.
|
2010-02-26 06:48:11 +00:00
|
|
|
*
|
|
|
|
* Also sets mNextTag, an int, which is useful if there are additional commands to inject.
|
2010-03-18 17:11:08 +00:00
|
|
|
*
|
2010-01-26 02:40:45 +00:00
|
|
|
* @param mockTransport the mock transport we're using
|
2010-02-26 06:48:11 +00:00
|
|
|
* @param imapIdResponse the expected series of responses to the IMAP ID command. Non-final
|
|
|
|
* lines should be tagged with *. The final response should be untagged (the correct
|
|
|
|
* tag will be added at runtime).
|
2010-03-18 17:11:08 +00:00
|
|
|
* @param "READ-WRITE" or "READ-ONLY"
|
2010-02-26 06:48:11 +00:00
|
|
|
* @return the next tag# to use
|
2010-01-26 02:40:45 +00:00
|
|
|
*/
|
2010-03-18 17:11:08 +00:00
|
|
|
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
|
|
|
|
String readWriteMode) {
|
2010-02-26 06:48:11 +00:00
|
|
|
// Fix the tag # of the ID response
|
|
|
|
String last = imapIdResponse[imapIdResponse.length-1];
|
|
|
|
last = "2 " + last;
|
|
|
|
imapIdResponse[imapIdResponse.length-1] = last;
|
|
|
|
// inject boilerplate commands that match our typical login
|
2009-03-04 03:32:22 +00:00
|
|
|
mockTransport.expect(null, "* OK Imap 2000 Ready To Assist You");
|
2010-02-26 06:48:11 +00:00
|
|
|
mockTransport.expect("1 CAPABILITY", new String[] {
|
|
|
|
"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED",
|
|
|
|
"1 OK CAPABILITY completed"});
|
|
|
|
mockTransport.expect("2 ID \\(.*\\)", imapIdResponse);
|
2010-05-17 20:13:56 +00:00
|
|
|
mockTransport.expect("3 LOGIN user \"password\"",
|
2010-02-26 06:48:11 +00:00
|
|
|
"3 OK user authenticated (Success)");
|
|
|
|
mockTransport.expect("4 SELECT \"INBOX\"", new String[] {
|
2009-03-04 03:32:22 +00:00
|
|
|
"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)",
|
|
|
|
"* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen \\*)]",
|
|
|
|
"* 0 EXISTS",
|
|
|
|
"* 0 RECENT",
|
|
|
|
"* OK [UNSEEN 0]",
|
|
|
|
"* OK [UIDNEXT 1]",
|
2010-03-18 17:11:08 +00:00
|
|
|
"4 OK [" + readWriteMode + "] INBOX selected. (Success)"});
|
2010-02-26 06:48:11 +00:00
|
|
|
mNextTag = 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a tag for use in setting up expect strings. Typically this is called in pairs,
|
|
|
|
* first as getNextTag(false) when emitting the command, then as getNextTag(true) when
|
|
|
|
* emitting the final line of the expected response.
|
|
|
|
* @param advance true to increment mNextTag for the subsequence command
|
|
|
|
* @return a string containing the current tag
|
|
|
|
*/
|
|
|
|
public String getNextTag(boolean advance) {
|
|
|
|
if (advance) ++mNextTag;
|
|
|
|
return Integer.toString(mNextTag);
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-03-18 17:11:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that servers reporting READ-WRITE mode are parsed properly
|
|
|
|
* Note: the READ_WRITE mode passed to folder.open() does not affect the test
|
|
|
|
*/
|
|
|
|
public void testReadWrite() throws MessagingException {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock, "READ-WRITE");
|
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
assertEquals(OpenMode.READ_WRITE, mFolder.getMode());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that servers reporting READ-ONLY mode are parsed properly
|
|
|
|
* Note: the READ_ONLY mode passed to folder.open() does not affect the test
|
|
|
|
*/
|
|
|
|
public void testReadOnly() throws MessagingException {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock, "READ-ONLY");
|
|
|
|
mFolder.open(OpenMode.READ_ONLY, null);
|
|
|
|
assertEquals(OpenMode.READ_ONLY, mFolder.getMode());
|
|
|
|
}
|
|
|
|
|
2009-05-19 21:54:49 +00:00
|
|
|
/**
|
|
|
|
* Test for getUnreadMessageCount with quoted string in the middle of response.
|
|
|
|
*/
|
|
|
|
public void testGetUnreadMessageCountWithQuotedString() throws Exception {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock);
|
2010-02-26 06:48:11 +00:00
|
|
|
mock.expect(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
2009-05-19 21:54:49 +00:00
|
|
|
"* STATUS \"INBOX\" (UNSEEN 2)",
|
2010-02-26 06:48:11 +00:00
|
|
|
getNextTag(true) + " OK STATUS completed"});
|
2009-05-19 21:54:49 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
int unreadCount = mFolder.getUnreadMessageCount();
|
|
|
|
assertEquals("getUnreadMessageCount with quoted string", 2, unreadCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test for getUnreadMessageCount with literal string in the middle of response.
|
|
|
|
*/
|
|
|
|
public void testGetUnreadMessageCountWithLiteralString() throws Exception {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock);
|
2010-02-26 06:48:11 +00:00
|
|
|
mock.expect(getNextTag(false) + " STATUS \"INBOX\" \\(UNSEEN\\)", new String[] {
|
2009-05-19 21:54:49 +00:00
|
|
|
"* STATUS {5}",
|
|
|
|
"INBOX (UNSEEN 10)",
|
2010-02-26 06:48:11 +00:00
|
|
|
getNextTag(true) + " OK STATUS completed"});
|
2009-05-19 21:54:49 +00:00
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
int unreadCount = mFolder.getUnreadMessageCount();
|
|
|
|
assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
|
|
|
|
}
|
2009-10-07 18:42:27 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Test for proper operations on servers that return "NIL" for empty message bodies.
|
|
|
|
*/
|
|
|
|
public void testNilMessage() throws MessagingException {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock);
|
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
|
|
|
|
// Prepare to pull structure and peek body text - this is like the "large message"
|
|
|
|
// loop in MessagingController.synchronizeMailboxGeneric()
|
|
|
|
FetchProfile fp = new FetchProfile();fp.clear();
|
|
|
|
fp.add(FetchProfile.Item.STRUCTURE);
|
|
|
|
Message message1 = mFolder.createMessage("1");
|
2010-02-26 06:48:11 +00:00
|
|
|
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
|
2009-10-07 18:42:27 +00:00
|
|
|
"* 1 FETCH (UID 1 BODYSTRUCTURE (TEXT PLAIN NIL NIL NIL 7BIT 0 0 NIL NIL NIL))",
|
2010-02-26 06:48:11 +00:00
|
|
|
getNextTag(true) + " OK SUCCESS"
|
2009-10-07 18:42:27 +00:00
|
|
|
});
|
|
|
|
mFolder.fetch(new Message[] { message1 }, fp, null);
|
|
|
|
|
|
|
|
// The expected result for an empty body is:
|
|
|
|
// * 1 FETCH (UID 1 BODY[TEXT] {0})
|
|
|
|
// But some servers are returning NIL for the empty body:
|
|
|
|
// * 1 FETCH (UID 1 BODY[TEXT] NIL)
|
|
|
|
// Because this breaks our little parser, fetch() skips over empty parts.
|
|
|
|
// The rest of this test is confirming that this is the case.
|
|
|
|
|
2010-02-26 06:48:11 +00:00
|
|
|
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
|
2009-10-07 18:42:27 +00:00
|
|
|
"* 1 FETCH (UID 1 BODY[TEXT] NIL)",
|
2010-02-26 06:48:11 +00:00
|
|
|
getNextTag(true) + " OK SUCCESS"
|
2009-10-07 18:42:27 +00:00
|
|
|
});
|
|
|
|
ArrayList<Part> viewables = new ArrayList<Part>();
|
|
|
|
ArrayList<Part> attachments = new ArrayList<Part>();
|
|
|
|
MimeUtility.collectParts(message1, viewables, attachments);
|
|
|
|
assertTrue(viewables.size() == 1);
|
|
|
|
Part emptyBodyPart = viewables.get(0);
|
|
|
|
fp.clear();
|
|
|
|
fp.add(emptyBodyPart);
|
|
|
|
mFolder.fetch(new Message[] { message1 }, fp, null);
|
|
|
|
|
|
|
|
// If this wasn't working properly, there would be an attempted interpretation
|
|
|
|
// of the empty part's NIL and possibly a crash.
|
|
|
|
|
|
|
|
// If this worked properly, the "empty" body can now be retrieved
|
|
|
|
viewables = new ArrayList<Part>();
|
|
|
|
attachments = new ArrayList<Part>();
|
|
|
|
MimeUtility.collectParts(message1, viewables, attachments);
|
|
|
|
assertTrue(viewables.size() == 1);
|
|
|
|
emptyBodyPart = viewables.get(0);
|
|
|
|
String text = MimeUtility.getTextFromPart(emptyBodyPart);
|
|
|
|
assertNull(text);
|
|
|
|
}
|
2010-02-24 01:29:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Confirm the IMAP parser won't crash when seeing an excess FETCH response line without UID.
|
|
|
|
*
|
|
|
|
* <p>We've observed that the secure.emailsrvr.com email server returns an excess FETCH response
|
|
|
|
* for a UID FETCH command. These excess responses doesn't have the UID field in it, even
|
|
|
|
* though we request, which led the response parser to crash. We fixed it by ignoring response
|
|
|
|
* lines that don't have UID. This test is to make sure this case.
|
|
|
|
*/
|
|
|
|
public void testExcessFetchResult() throws MessagingException {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock);
|
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
|
|
|
|
// Create a message, and make sure it's not "SEEN".
|
|
|
|
Message message1 = mFolder.createMessage("1");
|
|
|
|
assertFalse(message1.isSet(Flag.SEEN));
|
|
|
|
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
fp.clear();
|
|
|
|
fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID FLAGS\\)",
|
|
|
|
new String[] {
|
|
|
|
"* 1 FETCH (UID 1 FLAGS (\\Seen))",
|
|
|
|
"* 2 FETCH (FLAGS (\\Seen))",
|
|
|
|
getNextTag(true) + " OK SUCCESS"
|
|
|
|
});
|
|
|
|
|
|
|
|
// Shouldn't crash
|
|
|
|
mFolder.fetch(new Message[] { message1 }, fp, null);
|
|
|
|
|
|
|
|
// And the message is "SEEN".
|
|
|
|
assertTrue(message1.isSet(Flag.SEEN));
|
|
|
|
}
|
2010-04-02 18:09:12 +00:00
|
|
|
|
|
|
|
public void testAppendMessages() throws Exception {
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
setupOpenFolder(mock);
|
|
|
|
mFolder.open(OpenMode.READ_WRITE, null);
|
|
|
|
|
|
|
|
ImapMessage message = (ImapMessage) mFolder.createMessage("1");
|
|
|
|
message.setFrom(new Address("me@test.com"));
|
|
|
|
message.setRecipient(RecipientType.TO, new Address("you@test.com"));
|
|
|
|
message.setMessageId("<message.id@test.com>");
|
|
|
|
message.setFlagDirectlyForTest(Flag.SEEN, true);
|
|
|
|
message.setBody(new TextBody("Test Body"));
|
|
|
|
|
|
|
|
// + go ahead
|
|
|
|
// * 12345 EXISTS
|
|
|
|
// OK [APPENDUID 627684530 17] (Success)
|
|
|
|
|
|
|
|
mock.expect(getNextTag(false) + " APPEND \\\"INBOX\\\" \\(\\\\Seen\\) \\{166\\}",
|
|
|
|
new String[] {"+ go ahead"});
|
|
|
|
|
|
|
|
mock.expectLiterally("From: me@test.com", NO_REPLY);
|
|
|
|
mock.expectLiterally("To: you@test.com", NO_REPLY);
|
|
|
|
mock.expectLiterally("Message-ID: <message.id@test.com>", NO_REPLY);
|
|
|
|
mock.expectLiterally("Content-Type: text/plain;", NO_REPLY);
|
|
|
|
mock.expectLiterally(" charset=utf-8", NO_REPLY);
|
|
|
|
mock.expectLiterally("Content-Transfer-Encoding: base64", NO_REPLY);
|
|
|
|
mock.expectLiterally("", NO_REPLY);
|
|
|
|
mock.expectLiterally("VGVzdCBCb2R5", NO_REPLY);
|
|
|
|
mock.expectLiterally("", new String[] {
|
|
|
|
"* 7 EXISTS",
|
|
|
|
getNextTag(true) + " OK [APPENDUID 1234567 13] (Success)"
|
|
|
|
});
|
|
|
|
|
|
|
|
mFolder.appendMessages(new Message[] {message});
|
|
|
|
mock.close();
|
|
|
|
|
|
|
|
assertEquals("13", message.getUid());
|
|
|
|
assertEquals(7, mFolder.getMessageCount());
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|