2378 lines
93 KiB
Java
2378 lines
93 KiB
Java
/*
|
|
* 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;
|
|
|
|
import android.content.Context;
|
|
import android.content.ContextWrapper;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.test.InstrumentationTestCase;
|
|
import android.test.MoreAsserts;
|
|
import android.test.suitebuilder.annotation.SmallTest;
|
|
import android.test.suitebuilder.annotation.Suppress;
|
|
|
|
import com.android.email.DBTestHelper;
|
|
import com.android.email.MockSharedPreferences;
|
|
import com.android.email.MockVendorPolicy;
|
|
import com.android.email.mail.store.ImapStore.ImapMessage;
|
|
import com.android.email.mail.store.imap.ImapResponse;
|
|
import com.android.email.mail.store.imap.ImapTestUtils;
|
|
import com.android.email.mail.transport.MockTransport;
|
|
import com.android.emailcommon.TempDirectory;
|
|
import com.android.emailcommon.VendorPolicyLoader;
|
|
import com.android.emailcommon.internet.MimeBodyPart;
|
|
import com.android.emailcommon.internet.MimeMultipart;
|
|
import com.android.emailcommon.internet.MimeUtility;
|
|
import com.android.emailcommon.internet.TextBody;
|
|
import com.android.emailcommon.mail.Address;
|
|
import com.android.emailcommon.mail.AuthenticationFailedException;
|
|
import com.android.emailcommon.mail.Body;
|
|
import com.android.emailcommon.mail.FetchProfile;
|
|
import com.android.emailcommon.mail.Flag;
|
|
import com.android.emailcommon.mail.Folder;
|
|
import com.android.emailcommon.mail.Folder.FolderType;
|
|
import com.android.emailcommon.mail.Folder.OpenMode;
|
|
import com.android.emailcommon.mail.Message;
|
|
import com.android.emailcommon.mail.Message.RecipientType;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
import com.android.emailcommon.mail.Part;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.HostAuth;
|
|
import com.android.emailcommon.provider.Mailbox;
|
|
import com.android.emailcommon.utility.Utility;
|
|
|
|
import org.apache.commons.io.IOUtils;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* This is a series of unit tests for the ImapStore class. These tests must be locally
|
|
* complete - no server(s) required.
|
|
*
|
|
* To run these tests alone, use:
|
|
* $ runtest -c com.android.email.mail.store.ImapStoreUnitTests email
|
|
*
|
|
* TODO Check if callback is really called
|
|
* TODO test for BAD response in various places?
|
|
* TODO test for BYE response in various places?
|
|
*/
|
|
@Suppress
|
|
@SmallTest
|
|
public class ImapStoreUnitTests extends InstrumentationTestCase {
|
|
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-";
|
|
/**
|
|
* Flag bits to specify whether or not a folder can be selected. This corresponds to
|
|
* {@link Mailbox#FLAG_ACCEPTS_MOVED_MAIL} and {@link Mailbox#FLAG_HOLDS_MAIL}.
|
|
*/
|
|
private final static int SELECTABLE_BITS = 0x18;
|
|
|
|
private final static ImapResponse CAPABILITY_RESPONSE = ImapTestUtils.parseResponse(
|
|
"* CAPABILITY IMAP4rev1 STARTTLS");
|
|
|
|
/* These values are provided by setUp() */
|
|
private ImapStore mStore = null;
|
|
private ImapFolder mFolder = null;
|
|
private Context mTestContext;
|
|
private HostAuth mHostAuth;
|
|
|
|
/** The tag for the current IMAP command; used for mock transport responses */
|
|
private int mTag;
|
|
// Fields specific to the CopyMessages tests
|
|
private MockTransport mCopyMock;
|
|
private Folder mCopyToFolder;
|
|
private Message[] mCopyMessages;
|
|
|
|
/**
|
|
* A wrapper to provide a wrapper to a Context which has already been mocked.
|
|
* This allows additional methods to delegate to the original, real context, in cases
|
|
* where the mocked behavior is insufficient.
|
|
*/
|
|
private class SecondaryMockContext extends ContextWrapper {
|
|
private final Context mUnderlying;
|
|
|
|
public SecondaryMockContext(Context mocked, Context underlying) {
|
|
super(mocked);
|
|
mUnderlying = underlying;
|
|
}
|
|
|
|
// TODO: eliminate the need for these method.
|
|
@Override
|
|
public Context createPackageContext(String packageName, int flags)
|
|
throws NameNotFoundException {
|
|
return mUnderlying.createPackageContext(packageName, flags);
|
|
}
|
|
|
|
@Override
|
|
public SharedPreferences getSharedPreferences(String name, int mode) {
|
|
return new MockSharedPreferences();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup code. We generate a lightweight ImapStore and ImapStore.ImapFolder.
|
|
*/
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
Context realContext = getInstrumentation().getTargetContext();
|
|
ImapStore.sImapId = ImapStore.makeCommonImapId(realContext.getPackageName(),
|
|
Build.VERSION.RELEASE, Build.VERSION.CODENAME,
|
|
Build.MODEL, Build.ID, Build.MANUFACTURER,
|
|
"FakeNetworkOperator");
|
|
mTestContext = new SecondaryMockContext(
|
|
DBTestHelper.ProviderContextSetupHelper.getProviderContext(realContext),
|
|
realContext);
|
|
MockVendorPolicy.inject(mTestContext);
|
|
|
|
TempDirectory.setTempDirectory(mTestContext);
|
|
|
|
// These are needed so we can get at the inner classes
|
|
HostAuth testAuth = new HostAuth();
|
|
Account testAccount = new Account();
|
|
|
|
testAuth.setLogin("user", "password");
|
|
testAuth.setConnection("imap", "server", 999);
|
|
testAccount.mHostAuthRecv = testAuth;
|
|
mStore = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
|
|
mFolder = (ImapFolder) mStore.getFolder(FOLDER_NAME);
|
|
resetTag();
|
|
}
|
|
|
|
public void testJoinMessageUids() throws Exception {
|
|
assertEquals("", ImapStore.joinMessageUids(new Message[] {}));
|
|
assertEquals("a", ImapStore.joinMessageUids(new Message[] {
|
|
mFolder.createMessage("a")
|
|
}));
|
|
assertEquals("a,XX", ImapStore.joinMessageUids(new Message[] {
|
|
mFolder.createMessage("a"),
|
|
mFolder.createMessage("XX"),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Confirms simple non-SSL non-TLS login
|
|
*/
|
|
public void testSimpleLogin() throws MessagingException {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// try to open it
|
|
setupOpenFolder(mockTransport);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
// TODO: inject specific facts in the initial folder SELECT and check them here
|
|
}
|
|
|
|
/**
|
|
* Test simple login with failed authentication
|
|
*/
|
|
public void testLoginFailure() throws Exception {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
expectLogin(mockTransport, false, false, false, new String[] {"* iD nIL", "oK"},
|
|
"nO authentication failed");
|
|
|
|
try {
|
|
mStore.getConnection().open();
|
|
fail("Didn't throw AuthenticationFailedException");
|
|
} catch (AuthenticationFailedException expected) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test simple TLS open
|
|
*/
|
|
public void testTlsOpen() throws MessagingException {
|
|
|
|
MockTransport mockTransport = openAndInjectMockTransport(HostAuth.FLAG_TLS,
|
|
false);
|
|
|
|
// try to open it, with STARTTLS
|
|
expectLogin(mockTransport, true, false, false,
|
|
new String[] {"* iD nIL", "oK"}, "oK user authenticated (Success)");
|
|
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 [" + "rEAD-wRITE" + "] " +
|
|
FOLDER_ENCODED + " selected. (Success)"});
|
|
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
assertTrue(mockTransport.isTlsStarted());
|
|
}
|
|
|
|
/**
|
|
* TODO: Test with SSL negotiation (faked)
|
|
* TODO: Test with SSL required but not supported
|
|
* TODO: Test with TLS required but not supported
|
|
*/
|
|
|
|
/**
|
|
* Test the generation of the IMAP ID keys
|
|
*/
|
|
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
|
|
String id = ImapStore.getImapId(mTestContext, "user-name", "host-name",
|
|
CAPABILITY_RESPONSE.flatten());
|
|
HashMap<String, String> map = tokenizeImapId(id);
|
|
assertEquals(mTestContext.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 = ImapStore.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 = ImapStore.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 for the interaction between {@link ImapStore#getImapId} and a vendor policy.
|
|
*/
|
|
public void testImapIdWithVendorPolicy() {
|
|
try {
|
|
MockVendorPolicy.inject(mTestContext);
|
|
|
|
// Prepare mock result
|
|
Bundle result = new Bundle();
|
|
result.putString("getImapId", "\"test-key\" \"test-value\"");
|
|
MockVendorPolicy.mockResult = result;
|
|
|
|
// Invoke
|
|
String id = ImapStore.getImapId(mTestContext, "user-name", "host-name",
|
|
ImapTestUtils.parseResponse("* CAPABILITY IMAP4rev1 XXX YYY Z").flatten());
|
|
|
|
// Check the result
|
|
assertEquals("test-value", tokenizeImapId(id).get("test-key"));
|
|
|
|
// Verify what's passed to the policy
|
|
assertEquals("getImapId", MockVendorPolicy.passedPolicy);
|
|
assertEquals("user-name", MockVendorPolicy.passedBundle.getString("getImapId.user"));
|
|
assertEquals("host-name", MockVendorPolicy.passedBundle.getString("getImapId.host"));
|
|
assertEquals("[CAPABILITY,IMAP4rev1,XXX,YYY,Z]",
|
|
MockVendorPolicy.passedBundle.getString("getImapId.capabilities"));
|
|
} finally {
|
|
VendorPolicyLoader.clearInstanceForTest();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*
|
|
* 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 = ImapStore.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
|
|
*/
|
|
public void testImapIdDeviceId() throws MessagingException {
|
|
HostAuth testAuth;
|
|
Account testAccount;
|
|
|
|
// store 1a
|
|
testAuth = new HostAuth();
|
|
testAuth.setLogin("user1", "password");
|
|
testAuth.setConnection("imap", "server", 999);
|
|
testAccount = new Account();
|
|
testAccount.mHostAuthRecv = testAuth;
|
|
ImapStore testStore1A = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
|
|
|
|
// store 1b
|
|
testAuth = new HostAuth();
|
|
testAuth.setLogin("user1", "password");
|
|
testAuth.setConnection("imap", "server", 999);
|
|
testAccount = new Account();
|
|
testAccount.mHostAuthRecv = testAuth;
|
|
ImapStore testStore1B = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
|
|
|
|
// store 2
|
|
testAuth = new HostAuth();
|
|
testAuth.setLogin("user2", "password");
|
|
testAuth.setConnection("imap", "server", 999);
|
|
testAccount = new Account();
|
|
testAccount.mHostAuthRecv = testAuth;
|
|
ImapStore testStore2 = (ImapStore) ImapStore.newInstance(testAccount, mTestContext);
|
|
|
|
String capabilities = CAPABILITY_RESPONSE.flatten();
|
|
String id1a = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities);
|
|
String id1b = ImapStore.getImapId(mTestContext, "user1", "host-name", capabilities);
|
|
String id2 = ImapStore.getImapId(mTestContext, "user2", "host-name", capabilities);
|
|
|
|
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) {
|
|
// Instead of a true tokenizer, we'll use double-quote as the split.
|
|
// We can's use " " because there may be spaces inside the values.
|
|
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:
|
|
// [i] = null or one or more spaces
|
|
// [i+1] = key
|
|
// [i+2] = one or more spaces
|
|
// [i+3] = value
|
|
map.put(elements[i+1], elements[i+3]);
|
|
i += 4;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Test non-NIL server response to IMAP ID. We should simply ignore it.
|
|
*/
|
|
public void testServerId() throws MessagingException {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// 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\")",
|
|
"oK"}, "rEAD-wRITE");
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Test OK response to IMAP ID with crummy text afterwards too.
|
|
*/
|
|
public void testImapIdOkParsing() throws MessagingException {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// try to open it
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
"* iD nIL",
|
|
"oK [iD] bad-char-%"}, "rEAD-wRITE");
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Test BAD response to IMAP ID - also with bad parser chars
|
|
*/
|
|
public void testImapIdBad() throws MessagingException {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// try to open it
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
"bAD unknown command bad-char-%"}, "rEAD-wRITE");
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Confirm that when IMAP ID is not in capability, it is not sent/received.
|
|
* This supports RFC 2971 section 3, and is important because certain servers
|
|
* (e.g. imap.vodafone.net.nz) do not process the unexpected ID command properly.
|
|
*/
|
|
public void testImapIdNotSupported() throws MessagingException {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// try to open it
|
|
setupOpenFolder(mockTransport, null, "rEAD-wRITE");
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Confirm that the non-conformant IMAP ID result seen on imap.secureserver.net fails
|
|
* to properly parse.
|
|
* 2 ID ("name" "com.google.android.email")
|
|
* * ID( "name" "Godaddy IMAP" ... "version" "3.1.0")
|
|
* 2 OK ID completed
|
|
*/
|
|
public void testImapIdSecureServerParseFail() {
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
|
|
// configure mock server to return malformed ID response
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
"* ID( \"name\" \"Godaddy IMAP\" \"version\" \"3.1.0\")",
|
|
"oK"}, "rEAD-wRITE");
|
|
try {
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
fail("Expected MessagingException");
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Confirm that the connections to *.secureserver.net never send IMAP ID (see
|
|
* testImapIdSecureServerParseFail() for the reason why.)
|
|
*/
|
|
public void testImapIdSecureServerNotSent() throws MessagingException {
|
|
// Note, this is injected into mStore (which we don't use for this test)
|
|
MockTransport mockTransport = openAndInjectMockTransport();
|
|
mockTransport.setHost("eMail.sEcurEserVer.nEt");
|
|
|
|
// Prime the expects pump as if the server wants IMAP ID, but we should not actually expect
|
|
// to send it, because the login code in the store should never actually send it (to this
|
|
// particular server). This sequence is a minimized version of expectLogin().
|
|
|
|
// Respond to the initial connection
|
|
mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You");
|
|
// Return "ID" in the capability
|
|
expectCapability(mockTransport, true, false);
|
|
// No TLS
|
|
// No ID (the special case for this server)
|
|
// LOGIN
|
|
mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"",
|
|
getNextTag(true) + " " + "oK user authenticated (Success)");
|
|
// SELECT
|
|
expectSelect(mockTransport, FOLDER_ENCODED, "rEAD-wRITE");
|
|
|
|
// Now open the folder. Although the server indicates ID in the capabilities,
|
|
// we are not expecting the store to send the ID command (to this particular server).
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Test small Folder functions that don't really do anything in Imap
|
|
*/
|
|
public void testSmallFolderFunctions() {
|
|
// canCreate() returns true
|
|
assertTrue(mFolder.canCreate(FolderType.HOLDS_FOLDERS));
|
|
assertTrue(mFolder.canCreate(FolderType.HOLDS_MESSAGES));
|
|
}
|
|
|
|
/**
|
|
* Lightweight test to confirm that IMAP hasn't implemented any folder roles yet.
|
|
*
|
|
* TODO: Test this with multiple folders provided by mock server
|
|
* TODO: Implement XLIST and then support this
|
|
*/
|
|
public void testNoFolderRolesYet() {
|
|
assertEquals(Folder.FolderRole.UNKNOWN, mFolder.getRole());
|
|
}
|
|
|
|
/**
|
|
* Lightweight test to confirm that IMAP is requesting sent-message-upload.
|
|
* TODO: Implement Gmail-specific cases and handle this server-side
|
|
*/
|
|
public void testSentUploadRequested() {
|
|
assertTrue(mStore.requireCopyMessageToSentFolder());
|
|
}
|
|
|
|
/**
|
|
* TODO: Test the process of opening and indexing a mailbox with one unread message in it.
|
|
*/
|
|
|
|
/**
|
|
* TODO: Test the scenario where the transport is "open" but not really (e.g. server closed).
|
|
/**
|
|
* Set up a basic MockTransport. open it, and inject it into mStore
|
|
*/
|
|
private MockTransport openAndInjectMockTransport() {
|
|
return openAndInjectMockTransport(HostAuth.FLAG_NONE, false);
|
|
}
|
|
|
|
/**
|
|
* Set up a MockTransport with security settings
|
|
*/
|
|
private MockTransport openAndInjectMockTransport(int connectionSecurity,
|
|
boolean trustAllCertificates) {
|
|
// Create mock transport and inject it into the ImapStore that's already set up
|
|
MockTransport mockTransport = MockTransport.createMockTransport(mTestContext);
|
|
mockTransport.setSecurity(connectionSecurity, trustAllCertificates);
|
|
mockTransport.setHost("mock.server.com");
|
|
mStore.setTransportForTest(mockTransport);
|
|
return mockTransport;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
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) {
|
|
setupOpenFolder(mockTransport, new String[] {
|
|
"* iD nIL", "oK"}, readWriteMode, false);
|
|
}
|
|
|
|
/**
|
|
* Helper which stuffs the mock with enough strings to satisfy a call to ImapFolder.open()
|
|
* Also allows setting a custom IMAP ID.
|
|
*
|
|
* Also sets mNextTag, an int, which is useful if there are additional commands to inject.
|
|
*
|
|
* @param mockTransport the mock transport we're using
|
|
* @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). Pass "null" to test w/o IMAP ID.
|
|
* @param readWriteMode "READ-WRITE" or "READ-ONLY"
|
|
*/
|
|
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
|
|
String readWriteMode) {
|
|
setupOpenFolder(mockTransport, imapIdResponse, readWriteMode, false);
|
|
}
|
|
|
|
private void setupOpenFolder(MockTransport mockTransport, String[] imapIdResponse,
|
|
String readWriteMode, boolean withUidPlus) {
|
|
expectLogin(mockTransport, imapIdResponse, withUidPlus);
|
|
expectSelect(mockTransport, FOLDER_ENCODED, readWriteMode);
|
|
}
|
|
|
|
/**
|
|
* Helper which stuffs the mock with the strings to satisfy a typical SELECT.
|
|
* @param mockTransport the mock transport we're using
|
|
* @param readWriteMode "READ-WRITE" or "READ-ONLY"
|
|
*/
|
|
private void expectSelect(MockTransport mockTransport, String folder, String readWriteMode) {
|
|
mockTransport.expect(
|
|
getNextTag(false) + " SELECT \"" + folder + "\"", 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 + " selected. (Success)"});
|
|
}
|
|
|
|
private void expectLogin(MockTransport mockTransport) {
|
|
expectLogin(mockTransport, new String[] {"* iD nIL", "oK"}, false);
|
|
}
|
|
|
|
private void expectLogin(MockTransport mockTransport, String[] imapIdResponse,
|
|
boolean withUidPlus) {
|
|
expectLogin(mockTransport, false, (imapIdResponse != null), withUidPlus, imapIdResponse,
|
|
"oK user authenticated (Success)");
|
|
}
|
|
|
|
private void expectLogin(MockTransport mockTransport, boolean startTls, boolean withId,
|
|
boolean withUidPlus, String[] imapIdResponse, String loginResponse) {
|
|
// inject boilerplate commands that match our typical login
|
|
mockTransport.expect(null, "* oK Imap 2000 Ready To Assist You");
|
|
|
|
expectCapability(mockTransport, withId, withUidPlus);
|
|
|
|
// TLS (if expected)
|
|
if (startTls) {
|
|
mockTransport.expect(getNextTag(false) + " STARTTLS",
|
|
getNextTag(true) + " Ok starting TLS");
|
|
mockTransport.expectStartTls();
|
|
// After switching to TLS the client must re-query for capability
|
|
expectCapability(mockTransport, withId, withUidPlus);
|
|
}
|
|
|
|
// ID
|
|
if (withId) {
|
|
String expectedNextTag = getNextTag(false);
|
|
// Fix the tag # of the ID response
|
|
String last = imapIdResponse[imapIdResponse.length-1];
|
|
last = expectedNextTag + " " + last;
|
|
imapIdResponse[imapIdResponse.length-1] = last;
|
|
mockTransport.expect(getNextTag(false) + " ID \\(.*\\)", imapIdResponse);
|
|
getNextTag(true); // Advance the tag for ID response.
|
|
}
|
|
|
|
// LOGIN
|
|
mockTransport.expect(getNextTag(false) + " LOGIN user \"password\"",
|
|
getNextTag(true) + " " + loginResponse);
|
|
}
|
|
|
|
private void expectCapability(MockTransport mockTransport, boolean withId,
|
|
boolean withUidPlus) {
|
|
String capabilityList = "* cAPABILITY iMAP4rev1 sTARTTLS aUTH=gSSAPI lOGINDISABLED";
|
|
capabilityList += withId ? " iD" : "";
|
|
capabilityList += withUidPlus ? " UiDPlUs" : "";
|
|
|
|
mockTransport.expect(getNextTag(false) + " CAPABILITY", new String[] {
|
|
capabilityList,
|
|
getNextTag(true) + " oK CAPABILITY completed"});
|
|
}
|
|
|
|
private void expectNoop(MockTransport mockTransport, boolean ok) {
|
|
String response = ok ? " oK success" : " nO timeout";
|
|
mockTransport.expect(getNextTag(false) + " NOOP",
|
|
new String[] {getNextTag(true) + response});
|
|
}
|
|
|
|
/**
|
|
* 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) ++mTag;
|
|
return Integer.toString(mTag);
|
|
}
|
|
|
|
/**
|
|
* Resets the tag back to it's starting value. Do this after the test connection has been
|
|
* closed.
|
|
*/
|
|
private int resetTag() {
|
|
return resetTag(1);
|
|
}
|
|
|
|
private int resetTag(int tag) {
|
|
int oldTag = mTag;
|
|
mTag = tag;
|
|
return oldTag;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
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);
|
|
assertEquals(OpenMode.READ_ONLY, mFolder.getMode());
|
|
}
|
|
|
|
/**
|
|
* Test for getUnreadMessageCount with quoted string in the middle of response.
|
|
*/
|
|
public void testGetUnreadMessageCountWithQuotedString() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
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);
|
|
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);
|
|
mock.expect(
|
|
getNextTag(false) + " STATUS \"" + FOLDER_ENCODED + "\" \\(UNSEEN\\)",
|
|
new String[] {
|
|
"* sTATUS {5}",
|
|
FOLDER_ENCODED + " (uNSEEN 10)",
|
|
getNextTag(true) + " oK STATUS completed"});
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
int unreadCount = mFolder.getUnreadMessageCount();
|
|
assertEquals("getUnreadMessageCount with literal string", 10, unreadCount);
|
|
}
|
|
|
|
public void testFetchFlagEnvelope() throws MessagingException {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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));
|
|
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
/**
|
|
* Test for fetching simple BODYSTRUCTURE.
|
|
*/
|
|
public void testFetchBodyStructureSimple() throws Exception {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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\" nIL" +
|
|
" nIL nIL nIL 18 3 nIL nIL nIL))",
|
|
getNextTag(true) + " oK sUCCESS"
|
|
});
|
|
mFolder.fetch(new Message[] { message }, fp, null);
|
|
|
|
// Check mime structure...
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"text/plain"},
|
|
message.getHeader("Content-Type")
|
|
);
|
|
assertNull(message.getHeader("Content-Transfer-Encoding"));
|
|
assertNull(message.getHeader("Content-ID"));
|
|
MoreAsserts.assertEquals(
|
|
new String[] {";\n size=18"},
|
|
message.getHeader("Content-Disposition")
|
|
);
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"TEXT"},
|
|
message.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
/**
|
|
* Test for fetching complex muiltipart BODYSTRUCTURE.
|
|
*/
|
|
public void testFetchBodyStructureMultipart() throws Exception {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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\" () {20}",
|
|
"long content id#@!@#" +
|
|
" NIL \"7BIT\" 18 3 NIL NIL NIL)" +
|
|
"(\"IMAGE\" \"PNG\" (\"NAME\" {10}",
|
|
"device.png) NIL NIL \"BASE64\" {6}",
|
|
"117840 NIL (\"aTTACHMENT\" (\"fILENAME\" \"device.png\")) NIL)" +
|
|
"(\"TEXT\" \"HTML\" () NIL NIL \"7BIT\" 100 NIL 123 (\"aTTACHMENT\"" +
|
|
"(\"fILENAME\" {15}",
|
|
"attachment.html \"SIZE\" 555)) NIL)" +
|
|
"((\"TEXT\" \"HTML\" NIL NIL \"BASE64\")(\"XXX\" \"YYY\"))" + // Nested
|
|
"\"mIXED\" (\"bOUNDARY\" \"00032556278a7005e40486d159ca\") NIL NIL))",
|
|
getNextTag(true) + " oK SUCCESS"
|
|
});
|
|
mFolder.fetch(new Message[] { message }, fp, null);
|
|
|
|
// Check mime structure...
|
|
final Body body = message.getBody();
|
|
assertTrue(body instanceof MimeMultipart);
|
|
MimeMultipart mimeMultipart = (MimeMultipart) body;
|
|
assertEquals(4, mimeMultipart.getCount());
|
|
assertEquals("mixed", mimeMultipart.getSubTypeForTest());
|
|
|
|
final Part part1 = mimeMultipart.getBodyPart(0);
|
|
final Part part2 = mimeMultipart.getBodyPart(1);
|
|
final Part part3 = mimeMultipart.getBodyPart(2);
|
|
final Part part4 = mimeMultipart.getBodyPart(3);
|
|
assertTrue(part1 instanceof MimeBodyPart);
|
|
assertTrue(part2 instanceof MimeBodyPart);
|
|
assertTrue(part3 instanceof MimeBodyPart);
|
|
assertTrue(part4 instanceof MimeBodyPart);
|
|
|
|
final MimeBodyPart mimePart1 = (MimeBodyPart) part1; // text/plain
|
|
final MimeBodyPart mimePart2 = (MimeBodyPart) part2; // image/png
|
|
final MimeBodyPart mimePart3 = (MimeBodyPart) part3; // text/html
|
|
final MimeBodyPart mimePart4 = (MimeBodyPart) part4; // Nested
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"1"},
|
|
part1.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"2"},
|
|
part2.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"3"},
|
|
part3.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"text/plain"},
|
|
part1.getHeader("Content-Type")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"image/png;\n NAME=\"device.png\""},
|
|
part2.getHeader("Content-Type")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"text/html"},
|
|
part3.getHeader("Content-Type")
|
|
);
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"long content id#@!@#"},
|
|
part1.getHeader("Content-ID")
|
|
);
|
|
assertNull(part2.getHeader("Content-ID"));
|
|
assertNull(part3.getHeader("Content-ID"));
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"7BIT"},
|
|
part1.getHeader("Content-Transfer-Encoding")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"BASE64"},
|
|
part2.getHeader("Content-Transfer-Encoding")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"7BIT"},
|
|
part3.getHeader("Content-Transfer-Encoding")
|
|
);
|
|
|
|
MoreAsserts.assertEquals(
|
|
new String[] {";\n size=18"},
|
|
part1.getHeader("Content-Disposition")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"attachment;\n filename=\"device.png\";\n size=117840"},
|
|
part2.getHeader("Content-Disposition")
|
|
);
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"attachment;\n filename=\"attachment.html\";\n size=\"555\""},
|
|
part3.getHeader("Content-Disposition")
|
|
);
|
|
|
|
// Check the nested parts.
|
|
final Body part4body = part4.getBody();
|
|
assertTrue(part4body instanceof MimeMultipart);
|
|
MimeMultipart mimeMultipartPart4 = (MimeMultipart) part4body;
|
|
assertEquals(2, mimeMultipartPart4.getCount());
|
|
|
|
final MimeBodyPart mimePart41 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(0);
|
|
final MimeBodyPart mimePart42 = (MimeBodyPart) mimeMultipartPart4.getBodyPart(1);
|
|
|
|
MoreAsserts.assertEquals(new String[] {"4.1"},
|
|
mimePart41.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
MoreAsserts.assertEquals(new String[] {"4.2"},
|
|
mimePart42.getHeader("X-Android-Attachment-StoreData")
|
|
);
|
|
}
|
|
|
|
public void testFetchBodySane() throws MessagingException {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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]);
|
|
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
public void testFetchBody() throws MessagingException {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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]);
|
|
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
public void testFetchAttachment() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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())));
|
|
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// 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");
|
|
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODYSTRUCTURE\\)", new String[] {
|
|
"* 1 fETCH (uID 1 bODYSTRUCTURE (tEXT pLAIN nIL nIL nIL 7bIT 0 0 nIL nIL nIL))",
|
|
getNextTag(true) + " oK SUCCESS"
|
|
});
|
|
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.
|
|
|
|
mock.expect(getNextTag(false) + " UID FETCH 1 \\(UID BODY.PEEK\\[TEXT\\]\\)", new String[] {
|
|
"* 1 fETCH (uID 1 bODY[tEXT] nIL)",
|
|
getNextTag(true) + " oK SUCCESS"
|
|
});
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// 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));
|
|
}
|
|
|
|
|
|
private ImapMessage prepareForAppendTest(MockTransport mock, String response) throws Exception {
|
|
ImapMessage message = (ImapMessage) mFolder.createMessage("initial uid");
|
|
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 \\\"" + FOLDER_ENCODED + "\\\" \\(\\\\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) + " " + response
|
|
});
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Test for APPEND when the response has APPENDUID.
|
|
*/
|
|
public void testAppendMessages() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
ImapMessage message = prepareForAppendTest(mock, "oK [aPPENDUID 1234567 13] (Success)");
|
|
|
|
mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false);
|
|
|
|
assertEquals("13", message.getUid());
|
|
assertEquals(7, mFolder.getMessageCount());
|
|
}
|
|
|
|
/**
|
|
* Test for APPEND when the response doesn't have APPENDUID.
|
|
*/
|
|
public void testAppendMessagesNoAppendUid() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
ImapMessage message = prepareForAppendTest(mock, "OK Success");
|
|
|
|
// First try w/o parenthesis
|
|
mock.expectLiterally(
|
|
getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id@test.com>",
|
|
new String[] {
|
|
"* sEARCH 321",
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
// If that fails, then try w/ parenthesis
|
|
mock.expectLiterally(
|
|
getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id@test.com>)",
|
|
new String[] {
|
|
"* sEARCH 321",
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
|
|
mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false);
|
|
|
|
assertEquals("321", message.getUid());
|
|
}
|
|
|
|
/**
|
|
* Test for append failure.
|
|
*
|
|
* We don't check the response for APPEND. We just SEARCH for the message-id to get the UID.
|
|
* If append has failed, the SEARCH command returns no UID, and the UID of the message is left
|
|
* unset.
|
|
*/
|
|
public void testAppendFailure() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
ImapMessage message = prepareForAppendTest(mock, "NO No space left on the server.");
|
|
assertEquals("initial uid", message.getUid());
|
|
// First try w/o parenthesis
|
|
mock.expectLiterally(
|
|
getNextTag(false) + " UID SEARCH HEADER MESSAGE-ID <message.id@test.com>",
|
|
new String[] {
|
|
"* sEARCH", // not found
|
|
getNextTag(true) + " oK Search completed."
|
|
});
|
|
// If that fails, then try w/ parenthesis
|
|
mock.expectLiterally(
|
|
getNextTag(false) + " UID SEARCH (HEADER MESSAGE-ID <message.id@test.com>)",
|
|
new String[] {
|
|
"* sEARCH", // not found
|
|
getNextTag(true) + " oK Search completed."
|
|
});
|
|
|
|
mFolder.appendMessage(getInstrumentation().getTargetContext(), message, false);
|
|
|
|
// Shouldn't have changed
|
|
assertEquals("initial uid", message.getUid());
|
|
}
|
|
|
|
public void testGetAllFolders() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
expectLogin(mock);
|
|
|
|
expectNoop(mock, true);
|
|
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.updateFolders();
|
|
ImapFolder testFolder;
|
|
|
|
testFolder = (ImapFolder) folders[0];
|
|
assertEquals("INBOX", testFolder.getName());
|
|
assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
|
|
|
|
testFolder = (ImapFolder) folders[1];
|
|
assertEquals("no select", testFolder.getName());
|
|
assertEquals(0, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
|
|
|
|
testFolder = (ImapFolder) folders[2];
|
|
assertEquals("\u65E5\u672C\u8A9E", testFolder.getName());
|
|
assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
|
|
|
|
testFolder = (ImapFolder) folders[3];
|
|
assertEquals("Drafts", testFolder.getName());
|
|
assertEquals(SELECTABLE_BITS, testFolder.mMailbox.mFlags & SELECTABLE_BITS);
|
|
// TODO test with path prefix
|
|
// TODO: Test NO response.
|
|
}
|
|
|
|
public void testEncodeFolderName() {
|
|
// null prefix
|
|
assertEquals("",
|
|
ImapStore.encodeFolderName("", null));
|
|
assertEquals("a",
|
|
ImapStore.encodeFolderName("a", null));
|
|
assertEquals("XYZ",
|
|
ImapStore.encodeFolderName("XYZ", null));
|
|
assertEquals("&ZeVnLIqe-",
|
|
ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", null));
|
|
assertEquals("!&ZeVnLIqe-!",
|
|
ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", null));
|
|
// empty prefix (same as a null prefix)
|
|
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!", ""));
|
|
// defined prefix
|
|
assertEquals("[Gmail]/",
|
|
ImapStore.encodeFolderName("", "[Gmail]/"));
|
|
assertEquals("[Gmail]/a",
|
|
ImapStore.encodeFolderName("a", "[Gmail]/"));
|
|
assertEquals("[Gmail]/XYZ",
|
|
ImapStore.encodeFolderName("XYZ", "[Gmail]/"));
|
|
assertEquals("[Gmail]/&ZeVnLIqe-",
|
|
ImapStore.encodeFolderName("\u65E5\u672C\u8A9E", "[Gmail]/"));
|
|
assertEquals("[Gmail]/!&ZeVnLIqe-!",
|
|
ImapStore.encodeFolderName("!\u65E5\u672C\u8A9E!", "[Gmail]/"));
|
|
// Add prefix to special mailbox "INBOX" [case insensitive), no affect
|
|
assertEquals("INBOX",
|
|
ImapStore.encodeFolderName("INBOX", "[Gmail]/"));
|
|
assertEquals("inbox",
|
|
ImapStore.encodeFolderName("inbox", "[Gmail]/"));
|
|
assertEquals("InBoX",
|
|
ImapStore.encodeFolderName("InBoX", "[Gmail]/"));
|
|
}
|
|
|
|
public void testDecodeFolderName() {
|
|
// null prefix
|
|
assertEquals("",
|
|
ImapStore.decodeFolderName("", null));
|
|
assertEquals("a",
|
|
ImapStore.decodeFolderName("a", null));
|
|
assertEquals("XYZ",
|
|
ImapStore.decodeFolderName("XYZ", null));
|
|
assertEquals("\u65E5\u672C\u8A9E",
|
|
ImapStore.decodeFolderName("&ZeVnLIqe-", null));
|
|
assertEquals("!\u65E5\u672C\u8A9E!",
|
|
ImapStore.decodeFolderName("!&ZeVnLIqe-!", null));
|
|
// empty prefix (same as a null prefix)
|
|
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-!", ""));
|
|
// defined prefix; prefix found, prefix removed
|
|
assertEquals("",
|
|
ImapStore.decodeFolderName("[Gmail]/", "[Gmail]/"));
|
|
assertEquals("a",
|
|
ImapStore.decodeFolderName("[Gmail]/a", "[Gmail]/"));
|
|
assertEquals("XYZ",
|
|
ImapStore.decodeFolderName("[Gmail]/XYZ", "[Gmail]/"));
|
|
assertEquals("\u65E5\u672C\u8A9E",
|
|
ImapStore.decodeFolderName("[Gmail]/&ZeVnLIqe-", "[Gmail]/"));
|
|
assertEquals("!\u65E5\u672C\u8A9E!",
|
|
ImapStore.decodeFolderName("[Gmail]/!&ZeVnLIqe-!", "[Gmail]/"));
|
|
// defined prefix; prefix not found, no affect
|
|
assertEquals("INBOX/",
|
|
ImapStore.decodeFolderName("INBOX/", "[Gmail]/"));
|
|
assertEquals("INBOX/a",
|
|
ImapStore.decodeFolderName("INBOX/a", "[Gmail]/"));
|
|
assertEquals("INBOX/XYZ",
|
|
ImapStore.decodeFolderName("INBOX/XYZ", "[Gmail]/"));
|
|
assertEquals("INBOX/\u65E5\u672C\u8A9E",
|
|
ImapStore.decodeFolderName("INBOX/&ZeVnLIqe-", "[Gmail]/"));
|
|
assertEquals("INBOX/!\u65E5\u672C\u8A9E!",
|
|
ImapStore.decodeFolderName("INBOX/!&ZeVnLIqe-!", "[Gmail]/"));
|
|
}
|
|
|
|
public void testEnsurePrefixIsValid() {
|
|
// Test mPathSeparator == null
|
|
mStore.mPathSeparator = null;
|
|
mStore.mPathPrefix = null;
|
|
mStore.ensurePrefixIsValid();
|
|
assertNull(mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("", mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "foo";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo", mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "foo.";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo.", mStore.mPathPrefix);
|
|
|
|
// Test mPathSeparator == ""
|
|
mStore.mPathSeparator = "";
|
|
mStore.mPathPrefix = null;
|
|
mStore.ensurePrefixIsValid();
|
|
assertNull(mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("", mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "foo";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo", mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "foo.";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo.", mStore.mPathPrefix);
|
|
|
|
// Test mPathSeparator is non-empty
|
|
mStore.mPathSeparator = ".";
|
|
mStore.mPathPrefix = null;
|
|
mStore.ensurePrefixIsValid();
|
|
assertNull(mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("", mStore.mPathPrefix);
|
|
|
|
mStore.mPathPrefix = "foo";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo.", mStore.mPathPrefix);
|
|
|
|
// Trailing separator; path separator NOT appended
|
|
mStore.mPathPrefix = "foo.";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo.", mStore.mPathPrefix);
|
|
|
|
// Trailing punctuation has no affect; path separator still appended
|
|
mStore.mPathPrefix = "foo/";
|
|
mStore.ensurePrefixIsValid();
|
|
assertEquals("foo/.", mStore.mPathPrefix);
|
|
}
|
|
|
|
public void testOpen() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
expectLogin(mock);
|
|
|
|
final Folder folder = mStore.getFolder("test");
|
|
|
|
// Not exist
|
|
mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
|
|
new String[] {
|
|
getNextTag(true) + " nO no such mailbox"
|
|
});
|
|
try {
|
|
folder.open(OpenMode.READ_WRITE);
|
|
fail();
|
|
} catch (MessagingException expected) {
|
|
}
|
|
|
|
// READ-WRITE
|
|
expectNoop(mock, true); // Need it because we reuse the connection.
|
|
mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
|
|
new String[] {
|
|
"* 1 eXISTS",
|
|
getNextTag(true) + " oK [rEAD-wRITE]"
|
|
});
|
|
|
|
folder.open(OpenMode.READ_WRITE);
|
|
assertTrue(folder.exists());
|
|
assertEquals(1, folder.getMessageCount());
|
|
assertEquals(OpenMode.READ_WRITE, folder.getMode());
|
|
|
|
assertTrue(folder.isOpen());
|
|
folder.close(false);
|
|
assertFalse(folder.isOpen());
|
|
|
|
// READ-ONLY
|
|
expectNoop(mock, true); // Need it because we reuse the connection.
|
|
mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
|
|
new String[] {
|
|
"* 2 eXISTS",
|
|
getNextTag(true) + " oK [rEAD-oNLY]"
|
|
});
|
|
|
|
folder.open(OpenMode.READ_WRITE);
|
|
assertTrue(folder.exists());
|
|
assertEquals(2, folder.getMessageCount());
|
|
assertEquals(OpenMode.READ_ONLY, folder.getMode());
|
|
|
|
// Try to re-open as read-write. Should send SELECT again.
|
|
expectNoop(mock, true); // Need it because we reuse the connection.
|
|
mock.expect(getNextTag(false) + " SELECT \\\"test\\\"",
|
|
new String[] {
|
|
"* 15 eXISTS",
|
|
getNextTag(true) + " oK selected"
|
|
});
|
|
|
|
folder.open(OpenMode.READ_WRITE);
|
|
assertTrue(folder.exists());
|
|
assertEquals(15, folder.getMessageCount());
|
|
assertEquals(OpenMode.READ_WRITE, folder.getMode());
|
|
}
|
|
|
|
public void testExists() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
expectLogin(mock);
|
|
|
|
// 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
|
|
expectNoop(mock, true);
|
|
|
|
// 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);
|
|
|
|
// 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
|
|
expectNoop(mock, true);
|
|
|
|
// Failure
|
|
mock.expect(getNextTag(false) + " CREATE \\\"&ZeVnLIqe-\\\"",
|
|
new String[] {
|
|
getNextTag(true) + " nO Can't create folder"
|
|
});
|
|
|
|
assertFalse(folder.create(FolderType.HOLDS_MESSAGES));
|
|
}
|
|
|
|
private void setupCopyMessages(boolean withUidPlus) throws Exception {
|
|
mCopyMock = openAndInjectMockTransport();
|
|
setupOpenFolder(mCopyMock, new String[] {"* iD nIL", "oK"}, "rEAD-wRITE", withUidPlus);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
mCopyToFolder = mStore.getFolder("\u65E5\u672C\u8A9E");
|
|
Message m1 = mFolder.createMessage("11");
|
|
m1.setMessageId("<4D8978AE.0000005D@m58.foo.com>");
|
|
Message m2 = mFolder.createMessage("12");
|
|
m2.setMessageId("<549373104MSOSI1:145OSIMS@bar.com>");
|
|
mCopyMessages = new Message[] { m1, m2 };
|
|
}
|
|
|
|
/**
|
|
* Returns the pattern for the IMAP request to copy messages.
|
|
*/
|
|
private String getCopyMessagesPattern() {
|
|
return getNextTag(false) + " UID COPY 11\\,12 \\\"&ZeVnLIqe-\\\"";
|
|
}
|
|
|
|
/**
|
|
* Returns the pattern for the IMAP request to search for messages based on Message-Id.
|
|
*/
|
|
private String getSearchMessagesPattern(String messageId) {
|
|
return getNextTag(false) + " UID SEARCH HEADER Message-Id \"" + messageId + "\"";
|
|
}
|
|
|
|
/**
|
|
* Counts the number of times the callback methods are invoked.
|
|
*/
|
|
private static class MessageUpdateCallbackCounter implements Folder.MessageUpdateCallbacks {
|
|
int messageNotFoundCalled;
|
|
int messageUidChangeCalled;
|
|
|
|
@Override
|
|
public void onMessageNotFound(Message message) {
|
|
++messageNotFoundCalled;
|
|
}
|
|
@Override
|
|
public void onMessageUidChange(Message message, String newUid) {
|
|
++messageUidChangeCalled;
|
|
}
|
|
}
|
|
|
|
// TODO Test additional degenerate cases; src msg not found, ...
|
|
// Golden case; successful copy with UIDCOPY result
|
|
public void testCopyMessages1() throws Exception {
|
|
setupCopyMessages(true);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
"* Ok COPY in progress",
|
|
getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed"
|
|
});
|
|
|
|
MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
|
|
|
|
assertEquals(0, cb.messageNotFoundCalled);
|
|
assertEquals(2, cb.messageUidChangeCalled);
|
|
}
|
|
|
|
// Degenerate case; NO, un-tagged response works
|
|
public void testCopyMessages2() throws Exception {
|
|
setupCopyMessages(true);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
"* No Some error occured during the copy",
|
|
getNextTag(true) + " oK [COPYUID 777 11,12 45,46] UID COPY completed"
|
|
});
|
|
|
|
MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
|
|
|
|
assertEquals(0, cb.messageNotFoundCalled);
|
|
assertEquals(2, cb.messageUidChangeCalled);
|
|
}
|
|
|
|
// Degenerate case; NO, tagged response throws MessagingException
|
|
public void testCopyMessages3() throws Exception {
|
|
try {
|
|
setupCopyMessages(false);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
getNextTag(true) + " No copy did not finish"
|
|
});
|
|
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
|
|
|
|
fail("MessagingException expected.");
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
// Degenerate case; BAD, un-tagged response throws MessagingException
|
|
public void testCopyMessages4() throws Exception {
|
|
try {
|
|
setupCopyMessages(true);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
"* BAD failed for some reason",
|
|
getNextTag(true) + " Ok copy completed"
|
|
});
|
|
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
|
|
|
|
fail("MessagingException expected.");
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
// Degenerate case; BAD, tagged response throws MessagingException
|
|
public void testCopyMessages5() throws Exception {
|
|
try {
|
|
setupCopyMessages(false);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
getNextTag(true) + " BaD copy completed"
|
|
});
|
|
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, null);
|
|
|
|
fail("MessagingException expected.");
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
// Golden case; successful copy getting UIDs via search
|
|
public void testCopyMessages6() throws Exception {
|
|
setupCopyMessages(false);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
getNextTag(true) + " oK UID COPY completed",
|
|
});
|
|
// New connection, so, we need to login again & the tag count gets reset
|
|
int saveTag = resetTag();
|
|
expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
|
|
// Select destination folder
|
|
expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
|
|
// Perform searches
|
|
mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"),
|
|
new String[] {
|
|
"* SeArCh 777",
|
|
getNextTag(true) + " oK UID SEARCH completed (1 msgs in 3.14159 secs)",
|
|
});
|
|
mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"),
|
|
new String[] {
|
|
"* sEaRcH 1818",
|
|
getNextTag(true) + " oK UID SEARCH completed (1 msgs in 2.71828 secs)",
|
|
});
|
|
// Resume commands on the initial connection
|
|
resetTag(saveTag);
|
|
// Select the original folder
|
|
expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
|
|
|
|
MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
|
|
|
|
assertEquals(0, cb.messageNotFoundCalled);
|
|
assertEquals(2, cb.messageUidChangeCalled);
|
|
}
|
|
|
|
// Degenerate case; searches turn up nothing
|
|
public void testCopyMessages7() throws Exception {
|
|
setupCopyMessages(false);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
getNextTag(true) + " oK UID COPY completed",
|
|
});
|
|
// New connection, so, we need to login again & the tag count gets reset
|
|
int saveTag = resetTag();
|
|
expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
|
|
// Select destination folder
|
|
expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
|
|
// Perform searches
|
|
mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"),
|
|
new String[] {
|
|
"* SeArCh",
|
|
getNextTag(true) + " oK UID SEARCH completed (0 msgs in 6.02214 secs)",
|
|
});
|
|
mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"),
|
|
new String[] {
|
|
"* sEaRcH",
|
|
getNextTag(true) + " oK UID SEARCH completed (0 msgs in 2.99792 secs)",
|
|
});
|
|
// Resume commands on the initial connection
|
|
resetTag(saveTag);
|
|
// Select the original folder
|
|
expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
|
|
|
|
MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
|
|
|
|
assertEquals(0, cb.messageNotFoundCalled);
|
|
assertEquals(0, cb.messageUidChangeCalled);
|
|
}
|
|
|
|
// Degenerate case; search causes an exception; must be eaten
|
|
public void testCopyMessages8() throws Exception {
|
|
setupCopyMessages(false);
|
|
mCopyMock.expect(getCopyMessagesPattern(),
|
|
new String[] {
|
|
getNextTag(true) + " oK UID COPY completed",
|
|
});
|
|
// New connection, so, we need to login again & the tag count gets reset
|
|
int saveTag = resetTag();
|
|
expectLogin(mCopyMock, new String[] {"* iD nIL", "oK"}, false);
|
|
// Select destination folder
|
|
expectSelect(mCopyMock, "&ZeVnLIqe-", "rEAD-wRITE");
|
|
// Perform searches
|
|
mCopyMock.expect(getSearchMessagesPattern("<4D8978AE.0000005D@m58.foo.com>"),
|
|
new String[] {
|
|
getNextTag(true) + " BaD search failed"
|
|
});
|
|
mCopyMock.expect(getSearchMessagesPattern("<549373104MSOSI1:145OSIMS@bar.com>"),
|
|
new String[] {
|
|
getNextTag(true) + " BaD search failed"
|
|
});
|
|
// Resume commands on the initial connection
|
|
resetTag(saveTag);
|
|
// Select the original folder
|
|
expectSelect(mCopyMock, FOLDER_ENCODED, "rEAD-wRITE");
|
|
|
|
MessageUpdateCallbackCounter cb = new MessageUpdateCallbackCounter();
|
|
mFolder.copyMessages(mCopyMessages, mCopyToFolder, cb);
|
|
|
|
assertEquals(0, cb.messageNotFoundCalled);
|
|
assertEquals(0, cb.messageUidChangeCalled);
|
|
}
|
|
|
|
public void testGetUnreadMessageCount() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
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);
|
|
|
|
mock.expect(getNextTag(false) + " EXPUNGE",
|
|
new String[] {
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
|
|
mFolder.expunge();
|
|
|
|
// TODO: Test NO response. (permission denied)
|
|
}
|
|
|
|
public void testSetFlags() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
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);
|
|
|
|
// TODO: Test NO response. (src message not found)
|
|
}
|
|
|
|
public void testSearchForUids() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
// Single results
|
|
mock.expect(
|
|
getNextTag(false) + " UID SEARCH X",
|
|
new String[] {
|
|
"* sEARCH 1",
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
MoreAsserts.assertEquals(new String[] {
|
|
"1"
|
|
}, mFolder.searchForUids("X"));
|
|
|
|
// Multiple results, including SEARCH with no UIDs.
|
|
mock.expect(
|
|
getNextTag(false) + " UID SEARCH UID 123",
|
|
new String[] {
|
|
"* sEARCH 123 4 567",
|
|
"* search",
|
|
"* sEARCH 0",
|
|
"* SEARCH",
|
|
"* sEARCH 100 200 300",
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
MoreAsserts.assertEquals(new String[] {
|
|
"123", "4", "567", "0", "100", "200", "300"
|
|
}, mFolder.searchForUids("UID 123"));
|
|
|
|
// NO result
|
|
mock.expect(
|
|
getNextTag(false) + " UID SEARCH SOME CRITERIA",
|
|
new String[] {
|
|
getNextTag(true) + " nO not found"
|
|
});
|
|
MoreAsserts.assertEquals(new String[] {
|
|
}, mFolder.searchForUids("SOME CRITERIA"));
|
|
|
|
// OK result, but result is empty. (Probably against RFC)
|
|
mock.expect(
|
|
getNextTag(false) + " UID SEARCH SOME CRITERIA",
|
|
new String[] {
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
MoreAsserts.assertEquals(new String[] {
|
|
}, mFolder.searchForUids("SOME CRITERIA"));
|
|
|
|
// OK result with empty search response.
|
|
mock.expect(
|
|
getNextTag(false) + " UID SEARCH SOME CRITERIA",
|
|
new String[] {
|
|
"* search",
|
|
getNextTag(true) + " oK success"
|
|
});
|
|
MoreAsserts.assertEquals(new String[] {
|
|
}, mFolder.searchForUids("SOME CRITERIA"));
|
|
}
|
|
|
|
|
|
public void testGetMessage() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// 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));
|
|
}
|
|
|
|
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]) );
|
|
}
|
|
|
|
/**
|
|
* Test for {@link ImapStore#getConnection}
|
|
*/
|
|
public void testGetConnection() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
// Start: No pooled connections.
|
|
assertEquals(0, mStore.getConnectionPoolForTest().size());
|
|
|
|
// Get 1st connection.
|
|
final ImapConnection con1 = mStore.getConnection();
|
|
assertNotNull(con1);
|
|
assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
|
|
assertFalse(con1.isTransportOpenForTest()); // Transport not open yet.
|
|
|
|
// Open con1
|
|
expectLogin(mock);
|
|
con1.open();
|
|
assertTrue(con1.isTransportOpenForTest());
|
|
|
|
// Get 2nd connection.
|
|
final ImapConnection con2 = mStore.getConnection();
|
|
assertNotNull(con2);
|
|
assertEquals(0, mStore.getConnectionPoolForTest().size()); // Pool size not changed.
|
|
assertFalse(con2.isTransportOpenForTest()); // Transport not open yet.
|
|
|
|
// con1 != con2
|
|
assertNotSame(con1, con2);
|
|
|
|
// New connection, so, we need to login again & the tag count gets reset
|
|
int saveTag = resetTag();
|
|
|
|
// Open con2
|
|
expectLogin(mock);
|
|
con2.open();
|
|
assertTrue(con1.isTransportOpenForTest());
|
|
|
|
// Now we have two open connections: con1 and con2
|
|
|
|
// Save con1 in the pool.
|
|
mStore.poolConnection(con1);
|
|
assertEquals(1, mStore.getConnectionPoolForTest().size());
|
|
|
|
// Get another connection. Should get con1, after verifying the connection.
|
|
saveTag = resetTag(saveTag);
|
|
mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + " oK success"});
|
|
|
|
final ImapConnection con1b = mStore.getConnection();
|
|
assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
|
|
assertSame(con1, con1b);
|
|
assertTrue(con1.isTransportOpenForTest()); // We opened it.
|
|
|
|
// Save con2.
|
|
mStore.poolConnection(con2);
|
|
assertEquals(1, mStore.getConnectionPoolForTest().size());
|
|
|
|
// Resume con2 tags ...
|
|
resetTag(saveTag);
|
|
|
|
// Try to get connection, but this time, connection gets closed.
|
|
mock.expect(getNextTag(false) + " NOOP", new String[] {getNextTag(true) + "* bYE bye"});
|
|
final ImapConnection con3 = mStore.getConnection();
|
|
assertNotNull(con3);
|
|
assertEquals(0, mStore.getConnectionPoolForTest().size()); // No connections left in pool
|
|
|
|
// It should be a new connection.
|
|
assertNotSame(con1, con3);
|
|
assertNotSame(con2, con3);
|
|
}
|
|
|
|
public void testCheckSettings() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
|
|
expectLogin(mock);
|
|
mStore.checkSettings();
|
|
|
|
resetTag();
|
|
expectLogin(mock, false, false, false,
|
|
new String[] {"* iD nIL", "oK"}, "nO authentication failed");
|
|
try {
|
|
mStore.checkSettings();
|
|
fail();
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
// Compatibility tests...
|
|
|
|
/**
|
|
* Getting an ALERT with a % mark in the message, which crashed the old parser.
|
|
*/
|
|
public void testQuotaAlert() throws Exception {
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
expectLogin(mock);
|
|
|
|
// Success
|
|
Folder folder = mStore.getFolder("INBOX");
|
|
|
|
// The following response was copied from an actual bug...
|
|
mock.expect(getNextTag(false) + " SELECT \"INBOX\"", new String[] {
|
|
"* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk $Forwarded Junk" +
|
|
" $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old)",
|
|
"* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen NonJunk" +
|
|
" $Forwarded Junk $Label4 $Label1 $Label2 $Label3 $Label5 $MDNSent Old \\*)]",
|
|
"* 6406 EXISTS",
|
|
"* 0 RECENT",
|
|
"* OK [UNSEEN 5338]",
|
|
"* OK [UIDVALIDITY 1055957975]",
|
|
"* OK [UIDNEXT 449625]",
|
|
"* NO [ALERT] Mailbox is at 98% of quota",
|
|
getNextTag(true) + " OK [READ-WRITE] Completed"});
|
|
folder.open(OpenMode.READ_WRITE); // shouldn't crash.
|
|
assertEquals(6406, folder.getMessageCount());
|
|
}
|
|
|
|
/**
|
|
* Apparently some servers send a size in the wrong format. e.g. 123E
|
|
*/
|
|
public void testFetchBodyStructureMalformed() throws Exception {
|
|
final MockTransport mock = openAndInjectMockTransport();
|
|
setupOpenFolder(mock);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
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\" ()" +
|
|
" NIL NIL NIL 123E 3))", // 123E isn't a number!
|
|
getNextTag(true) + " OK SUCCESS"
|
|
});
|
|
mFolder.fetch(new Message[] { message }, fp, null);
|
|
|
|
// Check mime structure...
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"text/plain"},
|
|
message.getHeader("Content-Type")
|
|
);
|
|
assertNull(message.getHeader("Content-Transfer-Encoding"));
|
|
assertNull(message.getHeader("Content-ID"));
|
|
|
|
// Doesn't have size=xxx
|
|
assertNull(message.getHeader("Content-Disposition"));
|
|
}
|
|
|
|
/**
|
|
* Folder name with special chars in it.
|
|
*
|
|
* Gmail puts the folder name in the OK response, which crashed the old parser if there's a
|
|
* special char in the folder name.
|
|
*/
|
|
public void testFolderNameWithSpecialChars() throws Exception {
|
|
final String FOLDER_1 = "@u88**%_St";
|
|
final String FOLDER_1_QUOTED = Pattern.quote(FOLDER_1);
|
|
final String FOLDER_2 = "folder test_06";
|
|
|
|
MockTransport mock = openAndInjectMockTransport();
|
|
expectLogin(mock);
|
|
|
|
// List folders.
|
|
expectNoop(mock, true);
|
|
mock.expect(getNextTag(false) + " LIST \"\" \"\\*\"",
|
|
new String[] {
|
|
"* LIST () \"/\" \"" + FOLDER_1 + "\"",
|
|
"* LIST () \"/\" \"" + FOLDER_2 + "\"",
|
|
getNextTag(true) + " OK SUCCESS"
|
|
});
|
|
final Folder[] folders = mStore.updateFolders();
|
|
|
|
ArrayList<String> list = new ArrayList<String>();
|
|
for (Folder f : folders) {
|
|
list.add(f.getName());
|
|
}
|
|
MoreAsserts.assertEquals(
|
|
new String[] {"INBOX", FOLDER_2, FOLDER_1},
|
|
list.toArray(new String[0])
|
|
);
|
|
|
|
// Try to open the folders.
|
|
expectNoop(mock, true);
|
|
mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_1_QUOTED + "\"", 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 [READ-WRITE] " + FOLDER_1});
|
|
folders[2].open(OpenMode.READ_WRITE);
|
|
folders[2].close(false);
|
|
|
|
expectNoop(mock, true);
|
|
mock.expect(getNextTag(false) + " SELECT \"" + FOLDER_2 + "\"", 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 [READ-WRITE] " + FOLDER_2});
|
|
folders[1].open(OpenMode.READ_WRITE);
|
|
folders[1].close(false);
|
|
}
|
|
|
|
/**
|
|
* Callback for {@link #runAndExpectMessagingException}.
|
|
*/
|
|
private interface RunAndExpectMessagingExceptionTarget {
|
|
public void run(MockTransport mockTransport) throws Exception;
|
|
}
|
|
|
|
/**
|
|
* Set up the usual mock transport, open the folder,
|
|
* run {@link RunAndExpectMessagingExceptionTarget} and make sure a {@link MessagingException}
|
|
* is thrown.
|
|
*/
|
|
private void runAndExpectMessagingException(RunAndExpectMessagingExceptionTarget target)
|
|
throws Exception {
|
|
try {
|
|
final MockTransport mockTransport = openAndInjectMockTransport();
|
|
setupOpenFolder(mockTransport);
|
|
mFolder.open(OpenMode.READ_WRITE);
|
|
|
|
target.run(mockTransport);
|
|
|
|
fail("MessagingException expected.");
|
|
} catch (MessagingException expected) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testFetchIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
|
|
final Message message = mFolder.createMessage("1");
|
|
final FetchProfile fp = new FetchProfile();
|
|
fp.add(FetchProfile.Item.STRUCTURE);
|
|
|
|
mFolder.fetch(new Message[] { message }, fp, null);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testUnreadMessageCountIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
|
|
mFolder.getUnreadMessageCount();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testCopyMessagesIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
|
|
final Message message = mFolder.createMessage("1");
|
|
final Folder folder = mStore.getFolder("test");
|
|
|
|
mFolder.copyMessages(new Message[] { message }, folder, null);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testSearchForUidsIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
|
|
mFolder.getMessage("uid");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testExpungeIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
|
|
mFolder.expunge();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make sure that IOExceptions are always converted to MessagingException.
|
|
*/
|
|
public void testOpenIOException() throws Exception {
|
|
runAndExpectMessagingException(new RunAndExpectMessagingExceptionTarget() {
|
|
@Override
|
|
public void run(MockTransport mockTransport) throws Exception {
|
|
mockTransport.expectIOException();
|
|
final Folder folder = mStore.getFolder("test");
|
|
folder.open(OpenMode.READ_WRITE);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Creates a folder & mailbox */
|
|
private ImapFolder createFolder(long id, String displayName, String serverId, char delimiter) {
|
|
ImapFolder folder = new ImapFolder(null, serverId);
|
|
Mailbox mailbox = new Mailbox();
|
|
mailbox.mId = id;
|
|
mailbox.mDisplayName = displayName;
|
|
mailbox.mServerId = serverId;
|
|
mailbox.mDelimiter = delimiter;
|
|
mailbox.mFlags = 0xAAAAAAA8;
|
|
folder.mMailbox = mailbox;
|
|
return folder;
|
|
}
|
|
|
|
/** Tests creating folder hierarchies */
|
|
public void testCreateHierarchy() {
|
|
HashMap<String, ImapFolder> testMap = new HashMap<String, ImapFolder>();
|
|
|
|
// Create hierarchy
|
|
// |-INBOX
|
|
// | +-b
|
|
// |-a
|
|
// | |-b
|
|
// | |-c
|
|
// | +-d
|
|
// | +-b
|
|
// | +-b
|
|
// +-g
|
|
ImapFolder[] folders = {
|
|
createFolder(1L, "INBOX", "INBOX", '/'),
|
|
createFolder(2L, "b", "INBOX/b", '/'),
|
|
createFolder(3L, "a", "a", '/'),
|
|
createFolder(4L, "b", "a/b", '/'),
|
|
createFolder(5L, "c", "a/c", '/'),
|
|
createFolder(6L, "d", "a/d", '/'),
|
|
createFolder(7L, "b", "a/d/b", '/'),
|
|
createFolder(8L, "b", "a/d/b/b", '/'),
|
|
createFolder(9L, "g", "g", '/'),
|
|
};
|
|
for (ImapFolder folder : folders) {
|
|
testMap.put(folder.getName(), folder);
|
|
}
|
|
|
|
ImapStore.createHierarchy(testMap);
|
|
// 'INBOX'
|
|
assertEquals(-1L, folders[0].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAAB, folders[0].mMailbox.mFlags);
|
|
// 'INBOX/b'
|
|
assertEquals(1L, folders[1].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAA8, folders[1].mMailbox.mFlags);
|
|
// 'a'
|
|
assertEquals(-1L, folders[2].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAAB, folders[2].mMailbox.mFlags);
|
|
// 'a/b'
|
|
assertEquals(3L, folders[3].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAA8, folders[3].mMailbox.mFlags);
|
|
// 'a/c'
|
|
assertEquals(3L, folders[4].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAA8, folders[4].mMailbox.mFlags);
|
|
// 'a/d'
|
|
assertEquals(3L, folders[5].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAAB, folders[5].mMailbox.mFlags);
|
|
// 'a/d/b'
|
|
assertEquals(6L, folders[6].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAAB, folders[6].mMailbox.mFlags);
|
|
// 'a/d/b/b'
|
|
assertEquals(7L, folders[7].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAA8, folders[7].mMailbox.mFlags);
|
|
// 'g'
|
|
assertEquals(-1L, folders[8].mMailbox.mParentKey);
|
|
assertEquals(0xAAAAAAA8, folders[8].mMailbox.mFlags);
|
|
}
|
|
}
|